我正在使用Whittle gem来解析模板语言,并希望匹配规则中未包含的任何内容。我很清楚其他模板引擎,但这更像是一个学术练习而不是一个生产案例。
我遇到的问题是解析器会忽略:id
之上:raw
的优先级,并且仍然等待:raw
之后的{{
标记。
如何告诉解析器不允许在表达式中应用:raw
规则并仅在表达式中应用:spc
规则?
解析器代码
class Parser < Whittle::Parser
# Skip whitespaces (should not apply in :raw)
rule(:spc => /\s+/).skip!
# Various delimiters
rule("{{") ^ 4
rule("}}") ^ 4
rule("{%") ^ 4
rule("%}") ^ 4
rule("|") ^ 4
rule("end") ^ 4
# Defines an id (very large match)
rule(:id => /[a-zA-Z_.$<>=!:]+(\((\w+|\s+|,|")+\))?/) ^ 2
# inline tag
rule(:inline) do |r|
r["{{", :inline_head, "}}"].as { |_,id,_| Tag::Inline.new(id) }
end
# inline tag contents
# allows "|" chaining
rule(:inline_head) do |r|
r[:inline_head, "|", :id].as { |head, _, id| head << id }
r[:id].as { |id| [id] }
r[].as { [] }
end
# block tag
rule(:block) do |r|
r["{%", :block_head, "%}", :all, "{%", "end", "%}"].as { |_,head,_,tags,_,_,_|
Tag::Block.new(head, tags)
}
end
# block tag heading
# separates all the keywords
rule(:block_head) do |r|
r[:block_head, :id].as { |head, id| head << id }
#r[:id].as { |id| [id] }
r[].as { [] }
end
# one rule to match them all
rule(:all) do |r|
r[:all,:inline].as { |all, inline| all << inline }
r[:all, :block].as { |all, block| all << block }
r[:all, :raw].as { |all, raw| all << raw }
r[].as { [] }
end
# the everything but tags rule
rule(:raw => /[^\{\}%]+/).as { |text| Tag::Raw.new(text) } ^ 1
# starting rule
start(:all)
end
输入文本将是,输出是一个由对象表示的抽象语法树(它们现在只是像对象一样的哈希)。
<html>
<head>
<title>{{ title|capitalize }}</title>
</head>
<body>
<div class="news">
{% for news in articles %}
{{ news.title }}
{{ news.body | limit(100) }}
{{ tags | join(",", name) }}
{% end %}
</div>
</body>
</html>
答案 0 :(得分:1)
我不相信运营商优先支持在这里起作用。运算符优先级仅在解决foo = 6 + 7
等表达式中的歧义时发挥作用,其中表达式可以解释为(foo = 6) + 7
或foo = (6 + 7)
。赋予非运营商优先权并不能真正起到任何作用。
也许还不清楚解析器实际上做了什么。它基本上循环重复,将所有终端规则与输入字符串相匹配。对于它找到的那些,它需要最长的一个,并试图找到它将适合的当前状态的规则。所以解析器总会找到你的空格并丢弃它,因为这是你语法中的第一条规则。
我认为你实际上不想跳过空格,因为它在你的语法中很重要。您希望将其包含在允许它的规则中;这将使你的语法更加冗长,但(目前)是不可避免的。
所以:raw
变成如下所示,将所有空格和非语法标记吞入单个字符串:
rule(:raw => /[^\s\{\}%]+/)
rule(:text) do |r|
r[:text, :raw].as { |text, raw| text << raw }
r[:text, :spc].as { |text, spc| text << spc }
r[:spc]
r[:raw]
end
然后在你的:all
规则中,将该文本转换为AST的一部分(您实际上也可以在上述规则中执行此操作,但我对您的类定义一无所知)。
rule(:all) do |r|
# ... snip ...
r[:all, :text].as { |all, text| all << Tag::Raw.new(text) }
# ... snip ...
end
我一直在考虑如何提供在不同状态下匹配不同令牌的能力,但我对编写lex / flex的克隆很谨慎,我认为这会让人感到困惑,所以我试图提出一种方法,使用块来将规则嵌套在彼此内部,以传达状态如何相互关联;虽然创建一个易于理解的DSL来做这件事并不简单;)我还想提供一个可选的DSL来隐藏用于重复的算法;可能提供一种转换为LR解析器的PEG层。这仍然是一个(非常)年轻的项目;)