编写编译器编译器-使用和功能简介


10

这是一系列问题的一部分,这些问题的重点是抽象项目的姐妹项目,该项目旨在以框架的形式抽象语言设计中使用的概念。姊妹项目称为OILexer,旨在从语法文件构造解析器,而不在匹配项上使用代码注入。

与这些问题相关的其他一些页面,与结构类型相关,可以在这里查看,以及在这里找到易用性。可以在此处找到与有关框架和适当发布位置的查询相关的元主题。

我现在要开始从给定的语法中提取解析树,然后是递归下降解析器,该解析器使用DFA识别前向路径(类似于ANTLR 4的LL(*)),所以我想我会打开它来获得洞察力。

在解析器编译器中,哪种功能比较理想?

到目前为止,这里是实现的简要概述:

  1. 范本
  2. 提前预测,知道在给定点上什么是有效的。
  3. 规则“非文字化”将规则中的文字取下来,并解析它们来自哪个标记。
  4. 非确定自动机
  5. 确定性自动机
  6. 简单的词法状态机,用于令牌识别
  7. 令牌自动化方法:
    • 扫描-用于注释:注释:=“ / *” Scan(“ * /”);
    • 减-对标识符有用:标识符:=减(IdentifierBody,关键字);
      • 确保标识符不接受关键字。
    • 编码-将自动化编码为基数N转换的X系列计数。
      • UnicodeEscape:=“ \\ u” BaseEncode(IdentifierCharNoEscape,16,4);
        • 使用十六进制4转换以十六进制形式对Unicode进行转义。此与:[0-9A-Fa-f] {4}之间的区别是使用Encode进行的自动化将所允许的十六进制值集限制为IdentifierCharNoEscape的范围。因此,如果给它\ u005c,则编码版本将不接受该值。这样的事情有一个严重的警告:谨慎使用。最终的自动化可能非常复杂。

没有实现的是CST生成,我需要调整确定性自动化以继承适当的上下文才能使此工作正常进行。

对于感兴趣的人,我已经上传了T *y♯项目原始形式的漂亮印刷品。每个文件都应该链接到其他文件,我开始链接各个规则来遵循它们,但是这花了太长时间(自动化起来会更简单!)

如果需要更多上下文,请相应地发布。

编辑5-14-2013:我已经编写了代码,可以在给定语言下为状态机创建GraphViz图。 这是AssemblyPart的GraphViz有向图。语言描述中链接的成员在其相对文件夹中应具有rulename.txt,以及该规则的图。自从我发布示例以来,某些语言描述已更改,这是由于简化了语法。这是一个有趣的graphviz图像


8
墙上的文字。不要以这种错误的方式,我感谢一个彻底解释的问题。在这种情况下,它太冗长了。从我收集到的信息中,您正在询问语法解析器应包含哪些功能,或者如何从零开始创建功能?请编辑以回答以下问题(您无需重写,只需在摘要末尾附加内容即可):您的问题是什么?在可能的解决方案中,您受到什么约束(必须快,必须为LL *等)?
尼尔,

1
我想了解功能集。重点是易用性。困难在于找到一个不了解该项目的人,对项目有深刻的见识,以便他们了解项目的重点。我不是在问“怎么做”,而是在问与可用性有关的问题。感谢您提出有关如何简化问题的建议。
小艾伦·克拉克·科普兰(Allen Clark Copeland Jr)

1
对我而言,该项目的目的不是很明显。例如,自yacc以来,我们已经看到了很多解析器生成器。您的OILexer有什么不同?有什么新功能?
Ingo,

1
该项目的目的是简化解析器的生成。与YACC / Bison和FLEX / LEX类似。主要区别是避免这些程序涉及的复杂性。主要目标是使事情简单明了。这就是为什么我创建了一种没有奇数部分的格式的原因,而其目标是使其类似于常规编程:仅特定于语言开发。标记的名称后使用':='定义,规则的名称后使用:::定义规则。模板使用'<'和'>'作为参数,后跟“ :: =”,因为它们共享规则语法。
小艾伦·克拉克·科普兰(Allen Clark Copeland Jr)

3
这种对解析的狂妄关注似乎放错了位置。这是一个很好解决的问题,几乎无法确定处理编程语言所需的条件。Google为我撰写了有关“解析后的生活”的文章。
Ira Baxter

Answers:


5

这是一个很好的问题。

我最近一直在进行大量的解析,恕我直言,一些关键功能包括:

  • 编程API-因此可以在编程语言中使用它,理想情况下,只需导入一个库即可。它也可以具有GUI或类似BNF的界面,但是编程的界面是关键,因为您可以重复使用工具,IDE,静态分析,测试,语言抽象工具,程序员熟悉程度,文档生成器,构建过程,等等。此外,您可以与小型解析器进行交互播放,从而极大地减少了学习难度。这些原因将其放在“重要功能”列表的顶部。

  • 错误报告,如@guysherman所述。当发现错误时,我想知道错误在哪里以及错误发生时的情况。不幸的是,我无法找到很好的资源来解释在进行回溯时如何产生体面的错误。(尽管请注意下面的@ Sk-logic的评论)。

  • 部分结果。当解析失败时,我希望能够从错误位置之前的输入部分中看到成功解析的内容。

  • 抽象。您永远无法内置足够的功能,而用户将始终需要更多功能,因此,试图预先弄清所有可能的功能注定会失败。这是模板的意思吗?

  • 我同意您的#2(超前预测)。我认为这有助于生成良好的错误报告。还有其他用途吗?

  • 支持在解析发生时构建解析树,也许是:

    • 具体的语法树,其树的结构直接对应于语法,并包括用于以后阶段错误报告的布局信息。在这种情况下,客户端不必做任何事情就能获得正确的树结构-它应直接取决于语法。
    • 抽象语法树。在这种情况下,用户可以摆弄所有解析树
  • 某种日志记录。我不确定这件事。可能是为了显示对解析器尝试过的规则的跟踪,或者是为了跟踪垃圾标记(例如空格或注释),以防万一(例如)要使用标记生成HTML文档的情况。

  • 处理上下文相关语言的能力。也不确定这是多么重要-在实践中,使用上下文无关的语法解析语言的超集,然后在以后的其他遍历中检查上下文相关的约束,似乎更干净。

  • 自定义错误消息,以便我可以在特定情况下调整错误报告,也许可以更快地了解和解决问题。

另一方面,我认为纠错并不是特别重要-尽管我对当前的进展不是最新的。我注意到的问题是该工具提供的潜在更正是:1)太多,2)与实际的错误不符,因此没有太大帮助。希望这种情况会有所改善(或者可能已经有所改善)。


我对问题正文进行了编辑,在项目符号中包含指向“模板”的PrecedenceHelper链接。它允许使用参数数组元组,因此,如果您有四个参数(每个参数数组),则必须在四个参数集中使用模板。
小艾伦·克拉克·科普兰(3

构造CST的主要原因是解析文件的整体布局。如果要对文档进行漂亮的打印,最好的选择是使用CST,因为AST的名称暗示缺少信息来处理CST捕获的奇数行距。如果CST不错,那么转换CST通常非常容易。
小艾伦·克拉克·科普兰(

我再次编辑了有关可使用的内置函数的问题。
小艾伦·克拉克·科普兰(

我认为我在表达我对模板/功能的观点方面做得并不出色:我的意思是因为您将永远无法拥有足够的模板/功能,所以系统不应试图提前弄清它们:用户需要能够创建他自己。

1
我发现一种方法对于无限回溯(Packrat)的错误报告特别有用:每个生产规则都带有错误消息(用“ blah-blah-blah Expected”字样)注释,并且在失败时,此类消息以相同的方式存储在流中作为记忆标记。如果所有故障都不可恢复(解析在到达流的末尾之前终止),则最右边的错误消息(或此类消息的集合)是最合适的。这是最容易的事情,当然,有一些方法可以通过添加更多注释来进一步完善它。
SK-logic

5

我没有语言设计方面的经验,但是当我为游戏引擎创建IDE时,我曾经写过一次解析器。

我认为,对于最终用户而言,重要的是有意义的错误消息。我知道,这并不是一个特别令人震惊的问题,但是向后追溯,其主要含义之一是您需要能够避免误报。误报从何而来?它们来自解析器,在出现第一个错误时跌落并且从未恢复。C / C ++为此而臭名昭著(尽管较新的编译器更聪明)。

那么,您需要什么呢?我认为,您不仅要知道一点什么是无效的,还需要知道如何处理无效的内容并进行最小的更改以使其有效-这样您就可以继续解析而不会产生与错误有关的错误使您的递归下降变得困惑。能够构建能够生成此信息的解析器,不仅为您提供了非常强大的解析器,而且还为使用该解析器的软件打开了一些很棒的功能。

我意识到我可能会建议一些非常困难或非常明显的建议,如果是这样的话,对不起。如果这不是您要找的东西,我会很乐意删除我的答案。


这是我计划要做的一件事。为了帮助我了解该域,我的一个朋友建议手动编写一个实际的解析器,以掌握自动进行解析的能力。我很快意识到了一件事情:解析器很复杂,我们手工做的事情简化了流程。规则和模板具有相同的语法;但是,有些语言元素在“模板”中有效,但在规则中无效,存在处理此任务的内部状态。这让我想到了一个主意:规则应该能够指定路径辅助工具,以使子规则的共享更加容易。
小艾伦·克拉克·科普兰(Allen Clark Copeland Jr)

...这应该很容易延续到自动化中,但是会要求自动化具有基于状态的条件。我会做一些工作,然后再回覆您。ANTLR使用有限状态自动化来处理诸如“ T” *的周期,因为在规则中有800多种变化时,减少量应更清晰地表示为状态,因此我将使用它来处理大部分解析过程。快速以标准if / else格式作为意大利面条代码。)
艾伦·克拉克·科普兰(Allen Clark Copeland Jr),

0

语法不能有“不要留下递归规则”之类的限制。荒谬的是,当今广泛使用的工具都具有这种功能,并且只能理解糟糕的LL语法-在yacc正确地将近50年之后。

右递归的示例(使用yacc语法):

list: 
      elem                  { $$ = singleton($1); }
    | elem ',' list         { $$ = cons($1, $2);  }
    ;

左递归的示例(使用yacc synatx):

funapp:
    term                    { $$ = $1; }
    | funapp term           { $$ = application($1, $2); }
    ;

现在,这可能可以“重构”为其他形式,但是在两种情况下,特定类型的递归只是“正确”的编写方式,因为(在示例语言中)列表是右递归的,而函数应用程序是左递归的。

可以从高级工具中期望它们支持自然的方式来写下内容,而不是要求人们“重构”工具所施加的所有内容,以使它们左/右递归。


是的,没有这样的任意限制真是太好了。但是,不能用重复运算符(例如正则表达式*+量词)代替的左递归示例是什么?我自由地承认我在这一领域的知识有限,但是我从未遇到过无法重构为重复的左递归。而且我发现重复版本也更加清晰(尽管这只是个人喜好)。

1
@MattFenwick看到我的编辑。注意语法的直接性如何导致简单而自然的语义动作(例如,用于创建语法树)。虽然有重复,(这是不是在YACC提供,顺便说一句),我想你经常需要检查是否有一个空的列表,单等
英戈

感谢您的答复。我想我现在更好地理解了-我更愿意将list = sepBy1(',', elem)和一起编写这些示例funapp = term{+}(当然sepBy1+将按照向左/向右递归的方式实现,并生成标准语法树)。因此,并不是我认为左递归和右递归不好,只是我觉得它们是低级的,并希望在可能的情况下使用更高级别的抽象来使情况更清晰。再次感谢!

1
不客气@MattFenwick。但这也许是一个品味问题。对我而言,递归是(至少在语言上都是固有的递归或完全无趣的语言)思考它的更自然的方式。而且,树是一种递归数据结构,因此我认为无需回到迭代来模拟递归。但是,当然,偏好是不同的。
Ingo 2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.