1

我正在为带有插值的名称-值参数的字符串编写解析器,例如:'This sentence #{x: 2, y: (2 + 5) + 3} has stuff in it.' 参数值是代码,它有自己的一组解析规则。

这是我的解析器的一个版本,简化为仅允许将基本算术作为代码:

require 'parslet'
require 'ap'
class TestParser < Parslet::Parser
  rule :integer do match('[0-9]').repeat(1).as :integer end
  rule :space do match('[\s\\n]').repeat(1) end
  rule :parens do str('(') >> code >> str(')') end
  rule :operand do integer | parens end
  rule :addition do (operand.as(:left) >> space >> str('+') >> space >> operand.as(:right)).as :addition end
  rule :code do addition | operand end
  rule :name do match('[a-z]').repeat 1 end
  rule :argument do name.as(:name) >> str(':') >> space >> code.as(:value) end
  rule :arguments do argument >> (str(',') >> space >> argument).repeat end
  rule :interpolation do str('#{') >> arguments.as(:arguments) >> str('}') end
  rule :text do (interpolation.absent? >> any).repeat(1).as(:text) end
  rule :segments do (interpolation | text).repeat end
  root :segments
end
string = 'This sentence #{x: 2, y: (2 + 5) + 3} has stuff in it.'
ap TestParser.new.parse(string), index: false

由于代码有自己的解析规则(以确保语法有效),参数值被解析为子树(括号等替换为子树中的嵌套):

[
    {
        :text => "This sentence "@0
    },
    {
        :arguments => [
            {
                 :name => "x"@16,
                :value => {
                    :integer => "2"@19
                }
            },
            {
                 :name => "y"@22,
                :value => {
                    :addition => {
                         :left => {
                            :addition => {
                                 :left => {
                                    :integer => "2"@26
                                },
                                :right => {
                                    :integer => "5"@30
                                }
                            }
                        },
                        :right => {
                            :integer => "3"@35
                        }
                    }
                }
            }
        ]
    },
    {
        :text => " has stuff in it."@37
    }
]

但是,我想将参数值存储为字符串,所以这将是理想的结果:

[
    {
        :text => "This sentence "@0
    },
    {
        :arguments => [
            {
                 :name => "x"@16,
                :value => "2"
            },
            {
                 :name => "y"@22,
                :value => "(2 + 5) + 3"
            }
        ]
    },
    {
        :text => " has stuff in it."@37
    }
]

如何使用 Parslet 子树来重构参数值子字符串?我可以编写一个代码生成器,但这似乎有点过头了——Parslet 显然可以在某些时候访问子字符串的位置信息(尽管它可能会丢弃它)。

是否可以利用或破解 Parslet 来返回子字符串?

4

2 回答 2

1

生成的树基于as解析器中的使用。

您可以尝试从表达式中的任何内容中删除它们,以便获得表达式的单个字符串匹配。这似乎是你所追求的。

如果您也想要这些表达式的解析树,那么您需要:

  • 将表达式树转换回匹配的文本。
  • 将匹配的文本重新解析回表达式树。

这些都不是理想的,但如果速度并不重要,我会选择重新解析选项。IE。删除as原子,然后根据需要将表达式重新解析为树。

由于您正确地希望重用相同的规则,但是这次您需要as在整个规则中捕获,那么您可以通过从现有解析器派生一个解析器并在以下方面实现具有相同名称的规则来实现这一点rule :x { super.x.as(:x)}

或者

您可以有一个与整个表达式匹配的表达式的一般规则,而无需知道其中的内容。

例如。"#{" >> (("}".absent >> any) | "\\}").repeat(0) >> "}"

然后,您可以根据需要将每个表达式解析为树。这样你就不会重复你的规则。它假定您无需解析整个表达式子树就可以判断表达式何时完成。

如果做不到这一点,它就会给我们留下 hacking parslet。

我这里没有解决方案,只是一些提示。

Parslet 有一个名为“CanFlatten”的模块,它实现flatten并用于as将捕获的树转换回单个字符串。你会想做这样的事情。

或者,您需要更改succ方法Atom::Base以返回“[成功/失败,结果,消耗_upto_position]”,以便每个匹配都知道它消耗到哪里。然后,您可以从开始位置和结束位置之间的源读取以获取原始文本。解析器匹配点的current position源的 应该是您想要的值。

祝你好运。

注意:我的示例表达式解析器不处理转义字符的转义..(留给读者作为练习)

于 2015-11-18T21:09:55.620 回答
1

这是我最终得到的hack。有更好的方法来实现这一点,但它们需要更广泛的改变。 Parser#parse现在返回一个Result. Result#tree给出正常的解析结果,并且Result#strings是将子树结构映射到源字符串的散列。

module Parslet

  class Parser
    class Result < Struct.new(:tree, :strings); end
    def parse(source, *args)
      source = Source.new(source) unless source.is_a? Source
      value = super source, *args 
      Result.new value, source.value_strings
    end
  end

  class Source
    prepend Module.new{
      attr_reader :value_strings
      def initialize(*args)
        super *args
        @value_strings = {}
      end
    }
  end

  class Atoms::Base
    prepend Module.new{
      def apply(source, *args)
        old_pos = source.bytepos
        super.tap do |success, value|
          next unless success
          string = source.instance_variable_get(:@str).string.slice(old_pos ... source.bytepos)
          source.value_strings[flatten(value)] = string
        end
      end    
    }
  end

end
于 2015-11-20T01:20:05.643 回答