使用scala解析器组合器解析语言

时间:2014-04-30 07:09:43

标签: scala parsing

我有以下模板:

#foo(args)# // START CONTAINER1
  #foo(foo <- foos)(args)# // BLOCK STARTS HERE (`args` can be on either side of `block`)
     #bar(args)# // START CONTAINER2
     #.bar# // END CONTAINER2
  #.foo# // END BLOCK
#.foo# // END CONTAINER1

*注意#.foo#如何关闭每个容器/块

我在这里看到的问题是,没有任何独特的id代表每个块,所以我必须跟踪有多少个容器开启/关闭器(#foo# / #.foo#)以便具有内部容器的END CONTAINER哈希的块不会将解析器混淆为结束块。

我如何使用Scala的解析器以这样的语言解析块?


我从这开始:

def maybeBlockMaybeJustContainer:Content = {
  (openingHash ~ identifier ~ opt(args) ~> opt(blockName) <~ opt(args) ~ closingHash) ~ 
      opt(content) ~
  openHash ~ dot ~ identifier ~ closingHash ^^ ...
}

我也在考虑预处理但不知道从哪里开始。

1 个答案:

答案 0 :(得分:2)

对于您的语言构造类似于BNF的形式

//Each of these is of type Parser (or String, which will be implicity converted to Parser when needed).
lazy val container = containerHeader ~ containerBody ~ containerEnd
lazy val containerHeader = hash ~ identifier ~ opt(args) ~ hash
lazy val containerBody = rep(block)
....
lazy val identifier = regex(new Regex("[a-zA-Z0-9-]+"))
lazy val hash = "#"

如果您的解析器接受一个字符串,那么该解析器定义的语言成员中的字符串。

这是context-free language的解析器。无上下文语言包括a[x]Sb[x]形式的语言,其中[x]表示前一个符号已存在x次,其中x未被语法定义,而是不同对于每个字符串。 (如果为语法定义x,那么语言将是有限的,并且所有有限语言都是常规的。)

这意味着该语言允许嵌套或递归组件,例如块和容器。

如果您开始解析容器,然后解析该容器内的块,则在完全解析块之前,您将无法完成解析包含。对于您所用语言的所有字符串都是如此。

一旦你定义了语法,并且它正确地接受并拒绝了测试用例,那么你可以将它连接到你的AST

lazy val identifier:Parser[Identifier] = regex(new Regex("[a-zA-Z0-9-]+")) ^^ {case s => Identifier(s)}

请注意,现在它的类型为Parser[Identifier],即如果解析正确,将返回Identifier的解析器。

用于更复杂的情况
lazy val container:Parser[Container] = containerHeader ~ containerBody ~ containerEnd ^^ {case head ~ body ~ end => Container(head.identifier,body)}

如果有任何需求扩展,请告诉我。