4

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

A. 普通行是 HTML

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

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

C.如果${}换行多行,里面的所有代码都应该是java代码。

D.如果一行以$include然后在行上做一些技巧(调用另一个模板)

$include anotherTemplate(id, name)

这将创建一个新的实例anotherTemplate,并调用它的render()方法

E. 并且除了 之外会有更多的“命令” $include,例如$def, $val

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

对于 1. 和 2.,我得到了这样的结果:

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

whereupto是从 Scalate Scamel 解析器中借来的(我刚刚开始阅读并且不太明白)

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

那么我该怎么做呢?

4

1 回答 1

6

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

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

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

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

现在,如果上面的解析器看到 a'$'后跟 a'{'然后没有看到javaStuff,它将回溯并按'$'顺序考虑剩余的四个 -alternatives 中的每一个(includedefval和 finally javaDirective),然后回溯到之前'$'尝试htmlDirective,在以令人费解的错误消息失败之前。但是如果我们看到 a '{',我们就知道其他替代方案都不可能成功,那么我们为什么要检查它们呢?同样,以 开头的行'$'永远不能是htmlDirective.

我们希望事情'{'成为不可回溯的点;如果后'{'解析器失败并想要回溯,我们应该停止它,并将导致回溯的失败作为错误直接传播给用户。

做到这一点的方法是使用commit. 此函数/组合器在应用于解析器时pParseResult会查看输出p并将其更改为Error(完全放弃信号),如果它最初是Failure(回溯信号),否则保持不变。通过适当使用commitdirective解析器变为:

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

当我第一次学习使用解析库时,我发现查看源代码Parsers非常有帮助;它使其中一些内容更加清晰。

(其他一些提示:appendand的目的ParseResult#append是决定应该将解析替代序列中的哪个失败传播给用户。暂时忽略这些。另外,我不会太担心>>/ flatMap/into直到你已经得到了更多的练习;到了时候,阅读Daniel Sobral 的解释。最后,我从来没有使用过|||,你可能也不会使用。快乐的解析!)

希望这可以帮助。

于 2012-05-14T21:50:15.583 回答