我正在为具有插值名称 - 值参数的字符串编写解析器,例如:'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来返回子字符串?
答案 0 :(得分:1)
生成的树基于解析器中""100"",""110"",""120"",""145""
的使用。
您可以尝试从表达式中的任何内容中删除它们,以便为表达式获得单个字符串匹配。这似乎就是你所追求的。
如果你想要解析这些表达式的树,那么你需要:
这些都不是理想的,但如果速度不重要,我会去重新解析选项。即。删除as
个原子,然后根据需要将表达式重新解析为树。
由于您正确地希望重用相同的规则,但这次您需要在整个规则中捕获as
,然后您可以通过从现有解析器派生解析器并使用相同名称实现规则来实现此目的。 as
OR
你可以有一个表达式的一般规则,它匹配整个表达式而不知道它是什么。
例如。 rule :x { super.x.as(:x)}
然后,您可以根据需要将每个表达式解析为树。这样你就不会重复你的规则了。它假定您可以在不解析整个表达式子树的情况下判断表达式何时完成。
如果做不到这一点,就会给我们带来黑客攻击。
我这里没有解决方案,只是一些提示。
Parslet有一个名为“CanFlatten”的模块,它实现"#{" >> (("}".absent >> any) | "\\}").repeat(0) >> "}"
并由flatten
用于将捕获的树转换回单个字符串。你会想要做这样的事情。
或者,您需要更改as
中的succ
方法以返回“[success / fail,result,consume_upto_position]”,以便每个匹配都知道它消耗的位置。然后,您可以从源头读取起始位置和结束位置,以获取原始文本。解析器匹配时源的Atom::Base
应该是您想要的值。
祝你好运。
注意:我的示例表达式解析器不处理转义字符的转义..(作为读者的练习)
答案 1 :(得分:1)
这是我最终的黑客攻击。有更好的方法来实现这一目标,但他们需要进行更广泛的更改。 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