手写解析器对于所有编译器高手,我想写一个递归下降解析器,而且我只想用代码来完成。无需从其他语法生成词法分析器和解析器,并告诉我阅读龙书,我最终会到达那里。我想深入了解为一种相当简单的语言(比如CSS)实现词法分析器和解析器的细节。我想做对。这可能最终会成为一系列问题,但现在我开始使用词法分析器。CSS的标记规则可以在这里找到。我发现自己在写这样的代码(希望你能从这个片段中推断出其余的):publicCssTokenReadNext(){intval;while((val=_reader.Read())!=-1){varc=(char)val;switch(_stack.Top){caseParserState.Init:if(c==''){继续;//忽略}elseif(c=='.'){_stack.Transition(ParserState.SubIdent,ParserState.Init);}休息;caseParserState.SubIdent:if(c=='-'){_token.Append(c);}}_stack.Transition(ParserState.SubNMBegin);休息;这个叫什么?我离合理的事情有多远?我试图在效率和易用性方面取得平衡,使用堆栈来实现某种状态机效果很好,但我不确定如何进行。我拥有的是一个输入流,我可以一次从中读取1个字符。我现在不做任何观点,我只是阅读角色并尝试根据当前状态做一些事情。我真的很想进入编写可重用代码片段的心态。此Transition方法目前通过弹出堆栈的当前状态,然后以相反的顺序压入参数来实现此目的。这样,当我写Transition(ParserState.SubIdent,ParserState.Init)时,它会“调用”一个子程序SubIdent,完成后会回到Init状态。解析器将以大致相同的方式实现,目前像这样的单个大方法中的所有内容允许我在找到令牌时轻松返回令牌,但它也迫使我将所有内容保存在单个大方法中间。有没有一种很好的方法可以将这些标记化规则拆分为单独的方法?你正在写的是一个下推自动机。这通常比您编写词法分析器所需的功能更强大,如果您正在为CSS等现代语言编写词法分析器,那绝对是多余的。递归下降解析器类似于下推自动机,但递归下降解析器更易于编写和理解。大多数解析器生成器生成下推自动机。词法分析器几乎总是写成有限状态机,就像您的代码一样,只是您摆脱了“堆栈”对象。有限状态机与正则表达式密切相关(实际上,它们可以相互比较)。在设计这样的解析器时,通常从正则表达式开始并使用它们来创建确定性有限自动机,并在转换中使用一些额外的代码来记录每个标记的开始和结束。有工具可以做到这一点。lex工具及其后代工具广为人知,并已被翻译成多种语言。ANTLR工具链也有一个词法分析器组件。我的首选工具是支持它的平台上的ragel。大多数时候手动编写词法分析器没有什么好处,这些工具生成的代码可能更快、更可靠。如果您想自己编写词法分析器,一个好的词法分析器通常如下所示:functionreadToken()//注意:每次只返回一个标记while!eofc=peekChar()ifcinA-Za-zreturnreadIdentifier()elseifcin0-9返回readInteger()elseifcin'nrtvf'nextChar()...returnEOFfunctionreadIdentifier()ident=""while!eofc=nextChar()ifcinA-Za-z0-9ident.append(c)elsereturnToken(Identifier,ident)//或者也许...returnIdentifier(ident)然后您可以将解析器编写为递归下降解析器。不要试图将lexer/parser阶段合并为一个,它会使代码混乱。(根据Parsec作者的说法,它也更慢)。如果您要从头开始编写所有代码,我肯定会考虑使用递归适当的解析器。在您的帖子中,您并没有真正说明在解析了源代码后您将如何处理令牌流。我建议你做一些事情1.扫描器/词法分析器的良好设计,这将是你的解析器源代码的标志。2.接下来是解析器,如果您有良好的源语言ebnf,它通常可以很好地转换为递归解析器。3.你真正需要的另一种数据结构是符号表。这可以像哈希表一样简单,也可以像表示复杂记录结构的树结构一样复杂。我认为使用CSS可能介于两者之间。4.最后,您要处理代码生成。你有很多选择。使用解释器,您可能只是在解析代码时即时解释。更好的方法可能是生成一个i-code,然后你可以编写一个iterpreter,甚至是一个编译器。当然,对于.NET平台,您可以直接生成IL(可能不适用于CSS:))供参考,我不认为您对深层理论很在意,我不怪您。如果您不介意Pascal,那么在没有复杂代码的情况下获得基础知识的一个很好的起点是JackCrenshaw的“让我们构建一个编译器”http://compilers.iecc.com/crenshaw/祝你好运,我相信你会喜欢这个项目.您需要根据BNF/EBNF编写自己的递归下降解析器。我最近不得不自己写,这个页面帮助很大。我不确定“仅使用代码”是什么意思。你是说你想知道如何编写你自己的递归解析器吗?如果你想这样做,你需要先掌握你的语法。一旦有了EBNF/BNF,就可以很容易地从中编写解析器。当我编写解析器时,我做的第一件事就是读取所有内容并标记文本。所以我基本上得到了一个我将其视为堆栈的令牌数组。为了减少从堆栈中拉出一个值然后在不需要时重新打开它的冗长/开销,您可以使用peek方法简单地返回堆栈上的顶部值而不弹出它。更新根据您的评论,我不得不从头开始用Javascript编写递归下降解析器。您可以在此处查看解析器。只需搜索约束功能。我编写了自己的标记化函数来标记输入。我还编写了另一个便利函数(peek,我之前提到过)。这里的parser是按照EBNF来解析的。这花了我一些时间才弄清楚,因为我已经好几年没写过解析器了(我上次写它是在学校!),但相信我,一旦你明白了,你就明白了。我希望我的例子能让你继续前进。另一个更新我也意识到我的例子可能不是你想要的,因为你可能正在使用一个shift-reduce解析器。您提到现在您正在尝试编写分词器。就我而言,我确实用Javascript编写了自己的分词器。它可能不够健壮,但足以满足我的需求。函数标记化(选项){varstr=options.str;vardelimiters=options.delimiters.split("");varreturnDelimiters=options.returnDelimiters||错误的;varreturnEmptyTokens=options.returnEmptyTokens||错误的;vartokens=newArray();varlastTokenIndex=0;for(vari=0;i根据您的代码,看起来您正在同时读取、标记化和解析-我假设这是shift-reduce解析器所做的?我所拥有的流程从标记化开始构建令牌堆栈,然后通过递归下降解析器发送。看起来您想实现一个“shift-reduce”解析器,您可以在其中显式构建令牌堆栈。通常的替代方法是“递归下降”解析器,其中过程调用的深度在实际硬件堆栈上使用它们自己的局部变量构建相同的令牌堆栈。在shift-reduce中,术语“reduce”是指在堆栈上执行的操作上显式维护的令牌。对于例如,如果堆栈的顶部已成为Term、Operator、Term,则可以应用归约规则,从而导致Expression代替模式。归约规则明确编码在shift-reduce解析器使用的数据结构中;因此,所有归约规则都可以在源代码的同一个地方找到。与递归下降相比,shift-reduce方法带来了一些好处。在主观层面上,我认为shift-reduce更易于阅读和维护。更客观地说,shift-reduce允许在出现意外标记时来自解析器的更多信息错误消息。具体来说,因为shift-reduce解析器对进行“归约”的规则有明确的编码,所以解析器可以很容易地扩展以清楚地表达可以合法遵循哪些类型的标记。(例如,“;预期”)。不能轻易扩展递归下降实现来做同样的事情。一本关于这两种解析器的好书这本书,以及实现各种shift-reduce的权衡是ThomasW.Parsons的“编译器构造简介”。Shift-reduce有时称为“自下而上”解析,而递归下降有时称为“自上而下”解析。在所使用的类比中,具有最高优先级的节点(例如,乘法表达式中的“因子”)被称为位于解析的“底部”。这与“递归下降”的“下降”中使用的相同类比是一致的。如果你想使用解析器来处理格式错误的表达式,你真的需要一个递归下降解析器。更容易使错误处理和报告可用。文学方面,我会推荐一些尼克劳斯·维尔斯的旧作。他知道怎么写。算法+数据结构=程序是我用的,不过你可以在网上找他的编译器构造。以上就是《C#学习教程:手写解析器》分享的全部内容。如果对您有用,需要了解更多C#学习教程,希望您多加关注---本文收集自网络,不代表立场。如涉及侵权请点击右侧联系管理员删除。如需转载请注明出处:
