@Override CharSequence characters() { throw new IllegalStateException( "Cannot scan characters on tokens."); }
如果你在尝试用 JParsec 写规则时遇到了不知道怎么在 parser 规则中引用 lexer 规则的问题,恭喜你,看完下面的代码你会应该知道怎么做了。
整体示意图:
比如解析一段 key/value 文本:
"update": "2014-10-28"
为了解析双引号的部分写一个 lexer 规则:
private static final Parser<Tokens.Fragment> STRING_LITERAL = Scanners.DOUBLE_QUOTE_STRING .map(patchTag(Tag.STRING)) .label("string literal"); enum Tag { STRING } private static Map<String, Tokens.Fragment> 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<String> stringLiteral = Parsers.token(withTag(Tag.STRING)) // 除掉引号 .map(s -> s.substring(1, s.length() - 1)) .label("string literal"); private static TokenMap<String> 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<StatAstKeyValue> 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<Void> WHITESPACES = Scanners.WHITESPACES;
为了达到忽略空白字符的目的,我们可以用 Parser#skipMany() 这样的 combinator,也可以用 JParsec 的官方教程上的做法:
private Parser<List<Token>> TERMINALS = TOKENIZER.lexer( WHITESPACES.or(Parsers.always()) );
这里将 WHITESPACES.or(Parsers.always()) 作为分隔符来分隔 TOKENIZER 解析出来的 token。如果不加 Parsers.always(),会导致可以解析
"k" : "v"
但不能解析
"k": "v"
注意第 2 个例子中冒号的前面没有空格。