首先对源文件进行标记,然后在单独的传递中将每个标记推送到AST中是否有任何好处?
我正在解析的语言不无上下文;某些令牌在不同的情境中会有不同的含义。
因此,如果我进行无上下文标记化,那么我的令牌名称将没有多大意义 - 我会像COMMENT_START_OR_IDENTIFIER_START
那样使用令牌。
我还可以进行上下文感知标记化,然后将每个符号转换为正确的标记 - COMMENT_START
和IDENTIFIER_START
作为单独的标记,尽管它们都由相同的字符串组成。< / p>
但是那时我想知道在一次传递中构建AST是不是更好,而不是首先创建线性的标记流,然后将 解析为AST?
答案 0 :(得分:3)
令牌化器不应该关心令牌的语法用途。它只需要识别令牌(并且可能将它与语义值相关联,尽管它应该完全足以将它与源字符串相关联。)因此,只要清楚令牌的边界是什么 - 那个是,哪些来源字符构成了令牌 - 它没有做出丝毫不同的&#34; name&#34;令牌是。
在两个不同的语法上下文中,相同的字符序列具有不同的语义这一事实并不足以使语法上下文敏感。这是一个愚蠢的例子:语法描述了一个表达式列表,其中每个表达式都可以选择用数字标识,如下所示:
27: a * b
可以使用数字表达式的值:
28: $27 + 4
这里的整数可能是表达式标识符或常量。但是,标记器并不需要关心;它只返回一个INTEGER。
program: %empty
| program line
;
line : INTEGER ':' expr
| expr
;
expr : term
| expr '*' expr
| expr '+' expr
/* ... */
;
term : IDENTIFIER
| INTEGER { /* a constant */ }
| '$' INTEGER { /* an expression identifier */ }
;
即使令牌化依赖于语法上下文,语法仍然可以无上下文,但编写单独的词法分析器可能并不那么容易。例如,有许多语言可以将正则表达式直接用作常量,由 / 包围,即使 / 也是算术运算符。 Javascript和Awk只是两个例子。 [注1]
实际上为这些语言编写CFG有点棘手,但它肯定是可能的。正是由于这些问题,无扫描器解析器已经变得流行,并且无扫描器语法将证明该语言仍然是无上下文的。 (不过可能不再是LR(1)了。)
也可以使用传统的yacc / lex解析器,但由于tokeniser和解析器是由不同的工具独立生成的,因此在两者之间共享解析器/扫描器状态并不容易。在典型的实现中,用于这种语言的扫描器将保持足够的状态来做出关于/
意味着什么,不的决定,以便让解析器知道,而是为了成为能够在正则表达式的持续时间内切换标记化规则。生成的解析器可能看起来没有上下文,但解析的语言仍然是。
C ++是上下文相关语言的一个真实示例,因为在C ++中,无法确定标识符后面的&lt; 是模板括号还是小于号,而不知道是否前面的标识符是否是模板标识符,只有通过在符号表中查找标识符才能实现。 (或者更糟糕的是,可以安排决定要求任意复杂的计算。)
答案 1 :(得分:1)
许多解析库和代码生成器使用标记化阶段来提高性能。这是因为很容易使用简单的状态机实现标记化而无需回溯。如果尝试并且无法匹配不同的生产规则,则包含标记化的简单递归下降解析器可以执行相同输入的大量重新分析。
尽管如此,许多PEG解析库仍然混合了标记化和解析阶段,并使用memoization等技术来加速解析。
在我自己的解析库(https://github.com/cdiggins/myna-parser)中,我将两个阶段结合起来,不使用memoization。相反,我依赖于仔细构建语法来最小化反向跟踪。