你的词汇有点奇怪。大多数解析器旨在识别语言的语法。通常,语言定义定义了一些终端的概念,并明确排除了“空白”,它由终端文本之间无趣的文本序列组成,通常包括空白、制表符和各种独立的注释。因此解析中使用的“终端”一词通常表示“那些不是空格的语言原子”。您已将其隐含地定义为包含空格,我认为这引起了您的悲伤。
从这个角度来看,避免将解析器使用的语法定义与空格混淆的最简单方法是简单地让词法分析器不将空格传递给解析器。然后你的语法不需要指出它们是如何处理的(是的,这样做的语法真的很混乱),解析器不必担心它们,它们也不会出现在树中。
如果您正在构建编译器或解释器,那么忽略空格是最简单的。
如果您正在构建一个重新工程解析器(请参阅我们的DMS 软件重新工程工具包,那么在 AST 中捕获(至少)注释很重要,因为最终需要从构造的 AST 重新生成文本,如果重新生成的文本包含评论也是。[您可以通过其他方式做到这一点,但它们并不那么容易]。
DMS 词法分析器在内部生成“微”标记,这是您对语言标记、空格和注释的概念。它丢弃了空白微令牌,因为它们根本不添加任何东西(参见上面的讨论)。如您所料,它将常规标记传递给解析器。它将注释标记粘合到前面或后面的语言标记上,具体取决于标记类型和遇到的位置;对于 C,一个 /* ... */ 在一个标记附加到它之前看到,一个 // ... 注释附加到前面的标记(这里没有讨论一些更微妙的细节)。然后解析仍然只看到语言标记,所以语法没有不必要的复杂,如果所有附加到标记的信息都放在树中,注释就顺其自然了。
现在,人们经常想要“抽象”语法树;他们想省略诸如“(”和“)”之类的东西。我在上面描述的方案甚至对这些具体的标记都附加了注释。现在有一个复杂的情况:如果您将 ( .. ) 标记留在树之外,附加的注释就会消失。哎呀。因此,DMS 解析器做了一件复杂的事情:附加到在树中具有逻辑位置但实际上不存在的标记(“消除终端”)的注释被提升到父树节点,并带有一个注释,说明它们属于丢失的子标记。是的,实现这确实是一个 PITA。好消息是我们只需要在 DMS 的通用解析机器中执行一次,它适用于很多很多语言。但这意味着您必须愿意构建一个不寻常的(“重新设计”)解析器,
编辑:目前尚不清楚为什么 OP 想要这个,但他坚持要在树中捕获空白。由于他没有告诉我们原因,我猜测:他想要令牌/树节点的精确列信息。这并不难做到:教词法分析器跟踪位置(行/列),并用开始/结束位置标记每个标记(也包括注释等微标记),然后让解析器将该信息存储在那个树。这种方式也避免了在树中保留空格。(DMS 也这样做,因为在报告问题时,精确的信息很有用,并且在重新生成代码时,通常需要将代码放回其原始位置(至少同一列))。
EDIT2:如果 OP 坚持要捕获空白,他可能会考虑探索无扫描 GLR 解析。这会保留输入流中的每个字符,包括空格。