也许我的部分解决方案将有助于澄清我在问题中的意思。
假设您有一个不重要的解析器:
class MyParser < Parslet::Parser
rule(:dollars) {
match('[0-9]').repeat(1).as(:dollars)
}
rule(:comma_separated_dollars) {
match('[0-9]').repeat(1, 3).as(:dollars) >> ( match(',') >> match('[0-9]').repeat(3, 3).as(:dollars) ).repeat(1)
}
rule(:cents) {
match('[0-9]').repeat(2, 2).as(:cents)
}
rule(:currency) {
(str('$') >> (comma_separated_dollars | dollars) >> str('.') >> cents).as(:currency)
# order is important in (comma_separated_dollars | dollars)
}
end
现在,如果我们要解析一个固定宽度的货币字符串;这不是最简单的事情。当然,您可以准确地计算出如何根据最终宽度来表达重复表达式,但这会变得非常棘手,尤其是在逗号分隔的情况下。此外,在我的用例中,货币实际上只是一个例子。我希望能够有一种简单的方法来为地址、邮政编码等提供固定宽度的定义......
这似乎应该由 PEG 处理。我设法编写了一个原型版本,使用Lookahead作为模板:
class FixedWidth < Parslet::Atoms::Base
attr_reader :bound_parslet
attr_reader :width
def initialize(width, bound_parslet) # :nodoc:
super()
@width = width
@bound_parslet = bound_parslet
@error_msgs = {
:premature => "Premature end of input (expected #{width} characters)",
:failed => "Failed fixed width",
}
end
def try(source, context) # :nodoc:
pos = source.pos
teststring = source.read(width).to_s
if (not teststring) || teststring.size != width
return error(source, @error_msgs[:premature]) #if not teststring && teststring.size == width
end
fakesource = Parslet::Source.new(teststring)
value = bound_parslet.apply(fakesource, context)
return value if not value.error?
source.pos = pos
return error(source, @error_msgs[:failed])
end
def to_s_inner(prec) # :nodoc:
"FIXED-WIDTH(#{width}, #{bound_parslet.to_s(prec)})"
end
def error_tree # :nodoc:
Parslet::ErrorTree.new(self, bound_parslet.error_tree)
end
end
# now we can easily define a fixed-width currency rule:
class SHPParser
rule(:currency15) {
FixedWidth.new(15, currency >> str(' ').repeat)
}
end
当然,这是一个相当黑客的解决方案。除其他外,行号和错误消息在固定宽度约束内不好。我很想看到这个想法以更好的方式实施。