Created
December 12, 2016 06:44
-
-
Save ecnelises/e2cf70d350183195d7e8af292a550e5e to your computer and use it in GitHub Desktop.
用Parser Combinator写成的JSON解析器
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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