C# lexer与解析器之间的通信

C# lexer与解析器之间的通信,c#,c++,parsing,compiler-construction,lexer,C#,C++,Parsing,Compiler Construction,Lexer,每次我编写一个简单的lexer和parser时,我都会遇到同样的问题:lexer和parser应该如何通信?我看到四种不同的方法: lexer急切地将整个输入字符串转换为令牌向量。一旦这样做了,向量就被提供给解析器,解析器将其转换为树。这是迄今为止实现的最简单的解决方案,但由于所有令牌都存储在内存中,因此会浪费大量空间 每次lexer找到一个令牌时,它都会调用解析器上的一个函数,传递当前令牌。根据我的经验,只有当解析器可以像LALR解析器一样自然地实现为状态机时,这才有效。相比之下,我认为它对递

每次我编写一个简单的lexer和parser时,我都会遇到同样的问题:lexer和parser应该如何通信?我看到四种不同的方法:

  • lexer急切地将整个输入字符串转换为令牌向量。一旦这样做了,向量就被提供给解析器,解析器将其转换为树。这是迄今为止实现的最简单的解决方案,但由于所有令牌都存储在内存中,因此会浪费大量空间

  • 每次lexer找到一个令牌时,它都会调用解析器上的一个函数,传递当前令牌。根据我的经验,只有当解析器可以像LALR解析器一样自然地实现为状态机时,这才有效。相比之下,我认为它对递归下降解析器根本不起作用

  • 每次解析器需要令牌时,它都会向lexer请求下一个令牌。这是非常容易在C++中实现的,因为代码< >屈服>代码>,但是C++中很难没有。
  • lexer和解析器通过异步队列进行通信。这通常被称为“生产者/消费者”,它应该大大简化词法分析器和解析器之间的通信。它在多核上是否也优于其他解决方案?还是词法分析太琐碎了


  • 我的分析正确吗?还有其他我没想到的方法吗?在现实世界的编译器中使用了什么?如果像Eric Lippert这样的编译器编写者能够解释这个问题,那就太酷了。

    我在正在进行的toy buildsystem项目中处理它的方法是使用一个“file reader”类,带有一个函数
    bool next\u标记(std::string&,const std::set&)
    。此类包含一行输入(用于带有行号的错误报告目的)。函数接受一个
    std::string
    引用以放入令牌,以及一个
    std::set
    包含“令牌结束”字符。我的输入类是parser和lexer,但如果您需要更多的想象力,可以轻松地将其拆分。因此,解析函数只需调用
    next_token
    ,就可以完成它们的工作,包括非常详细的错误输出

    如果您需要保留逐字输入,则需要将读取的每一行存储在
    向量中
    或其他内容中,但不能单独和/或双y存储每个标记

    我所说的代码位于此处:


    (搜索
    ::next_token
    extract_nectar
    函数是where is all begins)

    lexer-parser关系比的最一般情况更简单,因为通常通信是单向的;解析器不需要将信息发送回lexer。这就是为什么“急切生成”方法有效的原因(虽然这确实意味着可以更早地放弃输入,但会带来一些损失)

    正如您所观察到的,如果lexer或parser可以以可重新命名的风格编写,那么另一个可以被视为一个简单的子例程。这始终可以作为源代码转换来实现,将局部变量转换为对象插槽

    虽然C++对协同程序没有语言支持,但特别是可以使用库支持。Unix
    setcontext
    系列是一个选项;另一种方法是使用多线程,但使用同步队列(基本上是单线程,但在两个线程之间进行切换)。

    < P>也考虑到1,您不需要它的LeX令牌,例如如果存在错误,并且,您可能会在内存或I/O带宽上运行过低。我相信最好的解决方案是由Bison等工具生成的解析器使用的,解析器调用lexer来获取下一个令牌。最小化空间需求和内存带宽需求


    这不值得。词法分析和解析本质上是同步的——只是没有足够的处理来证明通信成本的合理性。此外,通常您会同时解析/lex多个文件-这已经可以一次最大化所有核心。

    虽然我不会将上面的大部分内容归类为错误,但我确实认为有几项是误导性的

  • 与其他选项相比,在运行解析器之前对整个输入进行词法分析具有许多优势。实现方式各不相同,但一般来说,此操作所需的内存不是问题,尤其是当您考虑要用于报告编译错误的信息类型时。

    • 利益
      • 在错误报告过程中可能提供更多信息
      • 以允许在解析之前进行词法分析的方式编写的语言更易于指定和编写编译器
    • 缺点
      • 有些语言需要上下文敏感的词法分析器,在解析阶段之前根本无法操作
    语言实现注意:这是我首选的策略,因为它产生可分离的代码,最适合翻译为实现语言的IDE

    解析器实现注意:我用ANTLR v3试验了这种策略的内存开销。C目标每个令牌使用130多个字节,Java目标每个令牌使用大约44个字节。通过修改C#目标,我展示了用每个令牌仅8个字节来完全表示令牌化输入是可能的,这使得这种策略对于非常大的源文件也是可行的

    语言设计注意:我鼓励设计新语言的人以允许这种解析策略的方式进行设计,不管他们最终是否选择它作为参考编译器

  • 看起来您已经描述了一个“推”版本,我通常认为它是一个“拉”解析器,就像您在#3中描述的那样。我的工作重点一直是LL解析,所以这对我来说不是一个真正的选择。如果有贝内菲,我会感到惊讶