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<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(),会导致可以解析

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

但不能解析

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

注意第 2 个例子中冒号的前面没有空格。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
java精神(基于函数式组合子逻辑的javaparser框架) 一。 释名。 为什么叫精神? 如果你熟悉c++,那么你可能知道一个叫做”spirit”的parser库。它利用c++的模板元编程能力,使用c++语言本身提供了一个递归下降文法解析的框架。 我这介绍的jparsec库,就是一个java面的递归下降文法解析框架。 不过,它并非是spirit的java版本。 Jparsec的蓝本来自Haskell语言的parsec库。Parsec是一个基于monad的parser组合子库。 这个库的目的是要在java提供一个类似parsec, spirit的库,这种组合子库并非c++的专利,java/c#也可以做到。这个库还将在java5.0上被改写,类型安全上它将也不再逊色于c++。 那么,为什么叫“函数式”呢?java是面向对象的嘛。 如果你使用过haskell, lisp等语言,这个函数式不用解释你也知道是怎么回事了。 如果你是一个老牌的c++/java程序员,那么这还要稍微解释一下。当然如果您对这些虚头八脑的名词不感兴趣,那么,你尽可以跳过这一章,不知道什么是“函数式”,并不会影响你对这个库的理解的。 C++这几年随着gp的普及,“函数式”这个老孔乙己逐渐又被人从角落面拽了出来。一个c++程序员所熟悉的“函数式”很可能是stl的for_each, transform,count_if这些函数。 怎么说呢,就象我不能否定str.length()这个调用属于OO一样,我也无法说for_each, transform不是函数式。 但是,“函数式”的精髓不在于此。 一般归纳起来,就像我们说OO是什么多态,封装,继承一样,“函数式”的特征被总结为: 1。无副作用。 2。高阶函数。 3。延迟计算 而最最有意义的(至少我认为如此),是基于高阶函数的函数组合能力。一些人把这叫做glue。 简短地说,什么让函数式编程如此强大?是用简单的函数组合出复杂函数的能力。 我可以想象,说到这,你还是一头雾水。“什么是组合?1+1不是也把两个1组合成2了吗?new A(new B(), new C())不也是从B和C组合成A了?”

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值