Whittle解析器条件规则激活

时间:2011-12-13 11:00:55

标签: ruby regex parsing

我正在使用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>

1 个答案:

答案 0 :(得分:1)

我不相信运营商优先支持在这里起作用。运算符优先级仅在解决foo = 6 + 7等表达式中的歧义时发挥作用,其中表达式可以解释为(foo = 6) + 7foo = (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层。这仍然是一个(非常)年轻的项目;)