如何使用解析器组合器进行条件检查

时间:2011-06-22 02:02:08

标签: scala parser-combinators

我正在尝试编写一个简单的html模板引擎(为了好玩),并且想要解析这样的结构

一个。普通行是HTML

B中。如果一行以$开头,则将其视为java代码行

$ if (isSuper) {
    <span>Are you wearing red underwear?</span>
$ }

℃。如果${}包含多行,则其中的所有代码都应该是java代码。

d。如果一行以$include开头,则在该行上做一些技巧(调用另一个模板)

$include anotherTemplate(id, name)

这将创建anotherTemplate的新实例,并将其称为render()方法

电子。除$include之外还会有更多“命令”,例如$def$val

如何在解析器组合器中表达这一点?实际上它是一个条件叉

代表1.和2.,我有这样的事情:

'$' ~> ( '{' ~> upto('}') <~ '}' |  not('{') <~ newline )

其中upto是从Scalate Scamel解析器借来的(我刚开始阅读并且不太了解)

我使用not('{')来区分$....代码行与${...}块。但这很麻烦,不会扩展到其他“命令”

那我怎么能这样做?

1 个答案:

答案 0 :(得分:6)

您对not的使用是多余的。 |方法实现了有序选项;第二件事只有在第一件事失败时才会尝试。这应该可以解决问题:

def directive: Parser[Directive] =
  ( '$' ~>
    ( '{' ~> javaStuff <~ '}'
    | "include" ~> includeDirective
    | "def"     ~> defDirective
    | "val"     ~> valDirective
    | javaDirective
    )
  | htmlDirective
  )

def templateFile: Parser[List[Directive]] = (directive <~ '\n').*

为了更快地解析和更好的错误消息,您应该尽可能频繁地“提交”解析器。我认为这是您在使用not('{')时尝试获得的内容。

现在,如果上面的解析器看到'$'后跟'{',然后看到javaStuff,它就会回溯并考虑其余四个'$' - 替代品中的每一个按顺序排列(includedefval,最后是javaDirective),然后回溯到{{1}之前在尝试使用令人困惑的错误消息之前尝试'$'。但是,如果我们看到htmlDirective,我们知道其他替代方案都不可能成功,那么我们为什么要检查它们呢?同样,以'{'开头的行永远不能是'$'

我们希望像htmlDirective这样的东西成为没有回溯的点;如果after - '{'解析器失败并想要回溯,我们应该将其停在轨道上并将导致回溯的失败直接传播给用户作为错误。

执行此操作的方法是使用'{'。当应用于解析器commit时,此函数/组合器会查看p中出现的ParseResult并将其更改为p(完全放弃信号) )如果它原来是Error(回溯信号),则保持不变。通过适当使用Failurecommit解析器变为:

directive

当我第一次学习使用解析库时,我发现查看the source code for Parsers真的很有帮助;它使这些东西更清晰一些。

(其他一些提示:def directive: Parser[Directive] = ( '$' ~> commit( '{' ~> commit(javaStuff <~ '}') | "include" ~> commit(includeDirective) | "def" ~> commit(defDirective) | "val" ~> commit(valDirective | javaDirective ) | htmlDirective ) append的目的是决定将一系列解析备选方案中的哪个失败传播给用户。暂时忽略这些失败。另外,我在你进行更多练习之前,我不会过分担心ParseResult#append / >> / flatMap;到时候,请阅读Daniel Sobral's explanation。最后,我从来没有必须使用into,你可能也不会。快乐解析!)

希望这有帮助。