neg, a neg narser 2013-01-16 home comments

Neg is a neg narser, it could have been a peg parser, but it didn’t make it to the exam.

I worked with Parslet a lot and at some point, tried to contribute to it (and failed). Neg is born out of the residual energy of those efforts. I had fun in the process and I’m sharing the result.

Neg is naively implemented, it’s not trying to be fast, it’s not trying to be the best, it’s not awesome at all.

The classical thing to do is to write a JSON parser to test your parser tool on the road, here is one for neg:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
require 'neg'

class JsonParser < Neg::Parser

  # 1st stage: the parser
  #
  # it outputs a parse tree

  parser do

    value ==
      spaces? +
      (object | array | string | number | btrue | bfalse | null) +
      spaces?

    spaces? == _("\s\n\r") * 0

    object == `{` + (entry + (`,` + entry) * 0) * 0 + `}`
    entry == spaces? + string + spaces? + `:` + value

    array == `[` + (value + (`,` + value) * 0) * 0 + `]`

    string == `"` + ((`\\` + _) | _('^"')) * 0 + `"`

    _digit == _("0-9")

    number ==
      `-` * -1 +
      (`0` | (_("1-9") + _digit * 0)) +
      (`.` + _digit * 1) * -1 +
      (_("eE") + _("+-") * -1 + _digit * 1) * -1

    btrue == `true`
    bfalse == `false`
    null == `null`
  end

  # 2nd stage: the translator
  #
  # it turns the raw (and rather indigest) parse tree into the desired
  # output
  #
  # here for each named capture above, we rework the result/results to
  # fit our need: output a Ruby structure

  translator do

    # each rule intercepts its result nodes and returns a rehashed
    # result (for leaf nodes) / result set (for branch nodes)

    on(:value) { |n| n.results.first.first }
    on(:spaces?) { throw nil }

    on(:object) { |n|
      f2 = n.results.flatten(2)
      Hash[f2.any? ? [ f2.shift ] + f2.flatten(2) : []]
    }
    on(:array) { |n|
      f2 = n.results.flatten(2)
      f2.any? ? [ f2.shift ] + f2.flatten(2) : []
    }

    on(:string) { |n| eval(n.result) }

    on(:number) { |n|
      n.result.match(/[\.eE]/) ? n.result.to_f : n.result.to_i
    }

    on(:btrue) { true }
    on(:bfalse) { false }
    on(:null) { nil }
  end
end

JsonParser.parse("[ 1, 2, 3 ]")
  # --> [ 1, 2, 3 ]

 

Fun note: there is another JSON parsing post that was written recently: Parsing JSON the hard way.

Neg can be found at https://github.com/jmettraux/neg

© 2011-2013 John Mettraux