JParsec中如何在parser规则里引用lexer规则

Java 的 Parser combinator 中最有名应该是 JParsec 了。随着 Java 8 的发布,我们也可以用 lambda 表达式来写规则了。刚开始的时候我以为它跟 C# 下面的 Sprache 会比较像,API 应该很容易。结果我错了。Sprache 的思想跟《Monadic Parser Combinators》这篇论文里的提到的设计方法如出一辙,是不区分 lexer 和 parser 的。但是 JParsec 区分。如果在 parser 对应的规则里面使用 lexer 的规则,会导致异常。可以参看 ParserState 类:


@Override CharSequence characters() {
throw new IllegalStateException(
"Cannot scan characters on tokens.");
}


如果你在尝试用 JParsec 写规则时遇到了不知道怎么在 parser 规则中引用 lexer 规则的问题,恭喜你,看完下面的代码你会应该知道怎么做了。

整体示意图:

[img]http://dl2.iteye.com/upload/attachment/0110/1769/d46f2200-a7b4-31c8-8512-a79586bc666e.jpeg[/img]

比如解析一段 key/value 文本:

[code]"update": "2014-10-28"[/code]

为了解析双引号的部分写一个 lexer 规则:


private static final
Parser STRING_LITERAL =
Scanners.DOUBLE_QUOTE_STRING
.map(patchTag(Tag.STRING))
.label("string literal");

enum Tag {
STRING
}

private static
Map patchTag(Object tag) {
return str -> new Tokens.Fragment(str, tag);
}


除 patchTag 函数和 Tag 枚举之外其它的都是 JParsec 的 API。patchTag 的作用就让 lexer 在运行的时候将 STRING_LITERAL 规则所解析出来的字符串打上 STRING 这个 tag。这样在后面的 parser 规则部分用这个 tag 将字符串规则取出来:


private final
Parser stringLiteral =
Parsers.token(withTag(Tag.STRING))
// 除掉引号
.map(s -> s.substring(1, s.length() - 1))
.label("string literal");

private static TokenMap
withTag(Object tag) {
return token -> {
Object value = token.value();
if (value instanceof Tokens.Fragment) {
Tokens.Fragment fragment =
(Tokens.Fragment) value;
if (tag.equals(fragment.tag())) {
return fragment.text();
}
}
return null;
};
}


这样就可以做一个规则来匹配 "update": "2014-10-28" 这样的文本了:


private Parser keyValue =
Parsers.sequence(
stringLiteral,
OPERATORS.token(":"),
stringLiteral,
(k, ignored, v) ->
new StatAstKeyValue(k, v))
.label("keyValue");

private static final
Terminals OPERATORS =
Terminals.operators("{", "}", ",", ":");


文本中的空格部分要求在 lexer 中额外定义一个空白字符的规则:


private static final
Parser WHITESPACES = Scanners.WHITESPACES;


为了达到忽略空白字符的目的,我们可以用 Parser#skipMany() 这样的 combinator,也可以用 JParsec 的官方教程上的做法:


private Parser>
TERMINALS = TOKENIZER.lexer(
WHITESPACES.or(Parsers.always())
);


这里将 WHITESPACES.or(Parsers.always()) 作为分隔符来分隔 TOKENIZER 解析出来的 token。如果不加 Parsers.always(),会导致可以解析

[code]"k" : "v"[/code]

但不能解析

[code]"k": "v"[/code]

注意第 2 个例子中冒号的前面没有空格。

你可能感兴趣的:(Java)