Skip to content

Instantly share code, notes, and snippets.

@ecnelises
Created December 12, 2016 06:44
Show Gist options
  • Save ecnelises/e2cf70d350183195d7e8af292a550e5e to your computer and use it in GitHub Desktop.
Save ecnelises/e2cf70d350183195d7e8af292a550e5e to your computer and use it in GitHub Desktop.
用Parser Combinator写成的JSON解析器
class Parser
def initialize(&p)
@proc = p
end
def call(str)
@proc.call(str)
end
def | (another)
union(self, another)
end
def << (another)
concat(self, another)
end
def <= (another)
throw_right(self, another)
end
def >= (another)
throw_left(self, another)
end
end
def regexp(re)
Parser.new do |str|
if str.nil?
nil
else
if (re =~ str) == 0
part = re.match(str).offset(0)
[str[0...part[1]], str[part[1]..-1]]
end
end
end
end
def string(s)
Parser.new do |str|
if str.nil?
nil
else
if str&.index(s) == 0
[str[0...s.size], str[s.size..-1]]
end
end
end
end
def meta_comb(p1, p2, code)
Parser.new do |str|
if str.nil?
nil
else
r1 = p1.call(str)
r2 = p2.call(r1&.[](1))
if r1.nil? or r2.nil?
nil
else
eval code
end
end
end
end
def combine(p)
Parser.new do |str|
if str.nil?
nil
else
r = p.call(str)
if r.nil?
nil
else
[[r[0]], r[1]]
end
end
end
end
def concat(p1, p2); meta_comb(p1, p2, '[r1[0] + r2[0], r2[1]]'); end
def throw_left(p1, p2); meta_comb(p1, p2, '[r2[0], r2[1]]'); end
def throw_right(p1, p2); meta_comb(p1, p2, '[r1[0], r2[1]]'); end
def union(p1, p2)
Parser.new do |str|
r1 = p1.call(str)
if r1.nil?
p2.call(str)
else
r1
end
end
end
def pmap(parser, &pr)
Parser.new do |str|
r = parser.call(str)
if r.nil?
nil
else
[r[0].map(&pr), r[1]]
end
end
end
def papply(parser, &pr)
Parser.new do |str|
r = parser.call(str)
if r.nil?
nil
else
[pr.call(r[0]), r[1]]
end
end
end
def many(p)
Parser.new do |str|
c = p.call(str)
if c.nil?
[[], str]
else
ano = many(p).call(c[1])
#TODO: try to find infinite recursion here
[[c[0]] + ano[0], ano[1]]
end
end
end
def many1(p)
Parser.new do |str|
c = p.call(str)
if c.nil?
nil
else
ano = many(p).call(c[1])
[[c[0]] + ano[0], ano[1]]
end
end
end
def opt(p)
Parser.new do |str|
c = p.call(str)
if c.nil?
['', str]
else
[c[0], c[1]]
end
end
end
def sep_by(p, sep)
Parser.new do |str|
c = p.call(str)
if c.nil?
[[], str]
else
s = sep.call(c[1])
if s.nil?
[[c[0]], c[1]]
else
k = sep_by(p, sep).call(s[1])
[[c[0]] + k[0], k[1]]
end
end
end
end
def sep_by1(p, sep)
Parser.new do |str|
c = p.call(str)
if c.nil?
nil
else
s = sep.call(c[1])
if s.nil?
[[c[0]], c[1]]
else
k = sep_by(p, sep).call(s[1])
[[c[0]] + k[0], k[1]]
end
end
end
end
def lazy(&p)
Parser.new do |str|
some = \
begin
p.call
rescue NameError => ex
p.binding.eval ex.name.to_s
end
some.call(str)
end
end
number = papply (regexp /-?([1-9][0-9]*|[0-9])(\.[0-9]+)?([eE][+-]?[0-9]+)?/), &:to_f
str = (string "\"") >= (regexp /(\\"|[^"])*/) <= (string "\"")
truep = papply (string 'true') { true }
falsep = papply (string 'false') { false }
nullp = papply (string 'null') { nil }
value = number | str | truep | falsep | nullp | lazy{array} | lazy{object}
blank = regexp /[[:space:]]*/
valueb = blank >= value <= blank
array = (string '[') >= (sep_by(valueb, (string ','))) <= (string ']')
pair = papply (combine((blank >= str <= blank) <= (string ':')) << (combine valueb)) { |r| {r[0] => r[1]} }
object = papply ((string '{') >= blank >= ((sep_by pair, (string ',')) <= (string '}')) <= blank) { |s| s.reduce({}, &:merge) }
json_parse = define_method :json_parse do |str|
valueb.call(str)[0]
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment