在标记以下字符串 40 println "Hello ",(5+6-4) 时,“-4”显示单个标记而不是单独的标记

While tokenizing the following string 40 println "Hello ",(5+6-4), "-4" is showing a single token and not separate one

我正在 java 中为自定义基础语言编写词法分析器。对于以下行 40 打印"Hello ",(5+6-4) 我希望输出为

40
println
"Hello "
,
(
5
+
6
-
4
)

其他一切都很好,但出于某种原因,我得到了 - 和 4 一起“-4”作为标记。

使用正则表达式:

对于数字 -?[0-9]+
特殊运算符/字符:[\[|\]|/|.|$|*|-|+|=|>|<|#|(|)|%|,|!|||&|||{|}]

没有前导“-”的数字正则表达式在 ?[0-9]+

开头的字符 89 处显示错误
dangling Exception in thread "main" java.util.regex.PatternSyntaxException: Dangling meta character '?' near index 89 ((?<Reserved>\bPRINTLN\b|\bPRINT\b|\bINTEGER\b|\bINPUT\b|\bEND\b|\bLET\b))|((?<Constants>?[0-9]+))|((?<Special>[\[|\]|/|.|$|*|-|+|=|>|<|#|(|)|%|,|!|||&|||{|}]))|((?<Literals>"[^"]*"))|((?<Identifiers>\w+))

我将正则表达式存储在一个字符串中并使用命名捕获分组来识别标记。

我们无法真正知道发生了什么,因为您没有向我们展示您的分词器。但是,我可以猜测:您的分词器具有在查找特殊字符之前先查找数字文字的逻辑,并且它接受负数作为数字文字。如您所见,这会产生歧义。

简单的解决方案是只接受正数字文字作为标记,并将 - 视为否定其参数的一元运算符。所以 "-4+5" 会出现四个标记:"-""4""+""5"。然后,表达式解析器需要了解 - 何时是一元运算符,何时是二元运算符,并采取相应的行动。一个优点是您的程序现在可以处理像 "-(2+3)" 这样的表达式 [我不知道它是否会这样做]。

另一种可能性是分词器是上下文敏感的,并且根据前一个分词表现不同。因此,如果 - 位于字符串的开头或跟在 ( 或另一个运算符之后,并且如果其后跟一个数字,则 - 被视为负整数中的减号文字。如果它遵循任何其他内容,则必须单独处理。大多数编译器都可以标记他们的输入而不必跟踪先前的标记,但也有例外。在 Ada 中,这让编译器编写者有些头疼:

Record_Type'('A','B','C');

通常,单引号后跟一个字符再跟另一个单引号是字符文字,但 Ada 编译器必须特别注意不要将 '(' 视为字符文字。第一个引号在这里意味着不同的东西。典型的解决方案是跟踪先前的令牌是否是标识符。 (我曾经维护过一个Ada编译器,所以对这个案例比较熟悉。)

Java可能是另一个例子:

Map<String, List<String>> myMap;

在这里,编译器必须注意它如何处理 >>。通常,这是一个单一的标记(意思是右移),但它必须被视为两个 > 分隔符。我没有看到任何 Java 编译器代码,所以我不知道他们是如何处理它的(他们是将它标记为单个 >> 并在以后理顺,还是基于上下文? ).

无论如何,我抛出这些只是为了指出有时标记化 确实 必须了解上下文。所以如果采用第二种方案,也不会闻所未闻。但这确实让事情变得有点困难。

(?<Constants>?[0-9]+) - 正则表达式中的这一部分似乎是问题所在。捕获组名称后面的 ? 是悬空的。

此外,不需要使用 |.

分隔字符 class 成员

根据您分享的错误,以下是您想要的:

    String regex = "((?<Reserved>\bPRINTLN\b|\bPRINT\b|\bINTEGER\b|\bINPUT\b|\bEND\b|\bLET\b))|((?<Constants>[0-9]+))|((?<Special>[\[\]/.$*\-+=><#()%,!|&{|}]))|((?<Literals>\"[^\"]*\"))|((?<Identifiers>\w+))";
    String s = "40 println \"Hello \",(5+6-4) ";
    Matcher matcher = Pattern.compile(regex).matcher(s);
    while(matcher.find()) {
        System.out.println(matcher.group());
    }

我已经删除了上面提到的悬空?删除了用于内部分隔的|s字符 class 和 转义字符 class 内的 - (或者您可以将 - 移到字符 class).