我正在使用解析器 - 组合器库在Scala中编写一个简单的函数式编程语言。
语法在此处指定:https://github.com/hejfelix/Frase/blob/master/src/main/scala/it/vigtig/lambda/ParserLike.scala
我无法通过实现解决一件事:如何将语法定义与转换为AST节点分开?
直接在解析器源中使用接近人类可读的语法真的很酷,特别是考虑到我是项目ATM上唯一的程序员,它可以作为文档。
如何分离语法和特定于AST的代码?
答案 0 :(得分:11)
这是一个很好的问题,而且在我提出一个对我来说效果很好的解决方案之前,我已经挣扎了很长时间。
构建解析器时,我使用两种不同的语法树:
具体语法树或CST:这是文本的树形式,与文本具有1:1的对应关系。文本中出现的所有内容也将出现在CST中。
抽象语法树或AST:由于不必要的文本细节(如大括号,标点符号等)已被删除,因此不必与文本保持1:1的对应关系。 #39; t出现在AST。
因此,从输入文本到AST有两个步骤:第一步是将输入字符串解析为CST;第二步是将CST转换为AST,丢掉不必要的细节。
GRAPH_SHORTEST_PATH
这是我使用解析器组合器的地方。 我不会在这个阶段对树结构进行任何操作。 CST的结构完全取决于所使用的组合器。每个组合器生成一个特定形状的子树,在这个阶段我永远不会改变。没有任何动作附加到组合器,因此语法定义是干净的,没有任何AST信息。
String -> CST
这是我按摩解析树,提取重要内容,忽略其余部分的地方。这也是我经常执行上下文敏感检查的地方(例如:检查函数定义没有重复的参数名称),将这些细节保留在实际的解析阶段之外。
示例:这是我使用此方法构建的JSON解析器:
答案 1 :(得分:2)
原则上,所有AST转换都有特定的类型。您可以在其他地方定义它们并从语法定义中使用它们。它会在某种程度上澄清事情。或者,您可以将语法定义定义为"按名称传递"在通话时评估的函数,然后在转换中使用它们。
基本上任何语言都允许您通过在某处定义事物并在其他任何地方引用它们来打破复杂性。由于scala允许您将函数作为值,因此更容易。
答案 2 :(得分:1)
直接“构建”解析器源会有一个接近人类可读的语法真的很酷......
我想知道那种“接近人类可读的语法”是什么?
如何将语法定义与转换为AST节点分开?
你拥有的是手写的Packrat Parser。
我可能错了,但我理解这个问题是使用独立语法定义来构建解析器的请求。然后使用该解析器获取已解析源的语法树。
因此,语法可能是EBNF或PEG或CFG或“你自己的”语法,对吗?
总之...
EBNFParser
。给定有效语法的语法树,您可以返回带有键的关联列表(作为元标识符)并将语法规则附加到它们。
foreach grammar key add matching grammar rule
这意味着您需要选择RuleName标识的语法规则,并将其规则添加到“Constructed Parser”。
最后:你有一个“Constructed Parser”,由个别的“语法规则”组成,能够解析给定语法定义的Source。
通过1
Grammar -> GrammarParser -> GrammarTree -> GrammarRules -> ConstructedParserForGrammar
通过2
Source -> ConstructedParserForGrammar -> Syntax Tree -> Transformations...
换句话说:从BNF到自动构建的Packrat解析器非常困难。
答案 3 :(得分:0)
自此提交
https://github.com/scala/scala-parser-combinators/commit/33792d3380791ddafb41760604857d2fc43e54e1
Parser组合器链接到一个准确解决我的问题的帖子。这是imho,是我问题最准确的答案。
这里的帖子https://enear.github.io/2016/03/31/parser-combinators/首先进入一个具体的语法树(lexing)然后产生一个AST。
我把它留在这里是因为它为接受的答案添加了一个例子。