1

我刚刚发现了我观察到的一些非常令人困惑的行为的根本原因。这是一个测试:

@Test
public void test2() {
    Terminals terminals = Terminals.caseInsensitive(new String[] {}, new String[] { "true", "false" });
    Object result = terminals.tokenizer().parse("d");
    System.out.println("Result: " +  result);
}

这输出:

Result: d

我期待返回的解析器terminals.tokenizer()不返回任何内容,因为“d”不是有效的关键字或运算符。

我关心的原因是因为我希望我自己的解析器的优先级低于返回的优先级terminals.tokenizer()

public static final Parser<?> INSTANCE =
        Parsers.or(
                STRING_TOKENIZER,
                NUMBER_TOKENIZER,
                WHITESPACE_TOKENIZER,
                (Parser<Token>)TERMINALS.tokenizer(),
                IDENTIFIER_TOKENIZER);

以上IDENTIFIER_TOKENIZER从未使用过,因为TERMINALS.tokenizer()总是匹配。

为什么要Terminals.tokenizer()标记未注册的运算符/关键字?我该如何解决这个问题?

4

3 回答 3

3

在即将发布的 jParsec 2.2 版本中,API 使终端的功能更加清晰:http: //jparsec.github.io/jparsec/apidocs/org/codehaus/jparsec/Terminals.Builder.html

如果不首先提供定义“单词”的扫描仪,您甚至无法定义您的关键字。

该实现首先使用提供的单词扫描器查找所有单词,然后识别扫描单词上的特殊关键字。

那么,为什么要这样做呢?

  1. 如果您不需要不区分大小写,则可以将关键字作为“运算符”传递。是的,你没有看错。可以同样使用Terminals.token(op)Terminals.token(keyword)获取它们的令牌级别解析器。运算符与关键字的区别仅在于关键字是“特殊”词。它们是否碰巧是字母字符或其他可打印字符只是按照惯例。
  2. 另一种方法是将您的文字扫描仪精确定义为Parsers.or(Scanners.string("keyword1"), Scanners.string("keyword2"), ...). 然后终端不会尝试标记任何其他内容。
  3. 以上假设您要进行两阶段解析。但这是可选的。您的测试表明您没有使用Parser.from(tokenizer, delim). 如果不需要两阶段解析,它可以很简单: or(stringCaseInsensitive("true"), stringCaseInsensitive("false"))

更多关于第 3 点的内容。两阶段解析在 jParsec 中产生了一些额外的警告,这些警告在其他解析器组合器(如 Haskell 的 Parsec)中找不到。在 Haskell 中,字符串与字符列表没有什么不同。因此,通过特殊封装它们确实没有任何好处。many(char 'x')解析一个字符串就好了。

在 Java 中,String 不是一个 List 或 char 数组。如果我们采用相同的方法,将每个字符装箱到一个 Character 对象中,这样就可以无缝地统一字符级和令牌级解析器,这将是非常低效的。

现在这就解释了为什么我们有字符级解析器。但是使用令牌级解析器是完全可选的(我的意思是Terminals,Parser.from()Parser.lexer())。

您可以仅使用字符级解析器(也称为扫描仪)创建功能齐全的解析器。

例如:Scanners.string("true").or(Scanners.string("false")).sepEndBy1(delim)

于 2014-12-11T15:59:52.170 回答
2

从以下文档Tokenizer#caseInsensitive

org.codehaus.jparsec.Terminals

public static Terminals caseInsensitive(String[] ops, String[] keywords)

返回一个 Terminals 对象,用于对具有在 ops 中指定的名称的运算符进行词法分析和解析,以及对关键字进行词法分析和不区分大小写的分析。关键字和运算符被定义为带有 Tokens.Tag.RESERVED 标记的 Tokens.Fragment。不在关键字中的词被定义为带有 Tokens.Tag.IDENTIFIER 标签的 Fragment。单词定义为以 [_a - zA - Z] 开头的字母数字字符串,后面有 0 个或多个 [0 - 9_a - zA - Z]。

实际上,result您的解析器返回的是一个Fragment根据其类型进行标记的对象。在您的情况下,d被标记为IDENTIFIER预期的。

我不清楚你想要达到什么目标。能否请您提供一个测试用例?

于 2014-06-25T13:41:21.913 回答
1

http://blog.csdn.net/efijki/article/details/46975979

上面的博客文章解释了如何定义自己的标签。我知道是中文的。您只需要查看代码。特别是 withTag() 和 patchTag() 部分。

于 2014-11-03T03:38:32.547 回答