neg 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.md +7 -0
- data/README.md +16 -13
- data/TODO.txt +8 -15
- data/lib/neg/errors.rb +22 -6
- data/lib/neg/parser.rb +42 -20
- data/lib/neg/translator.rb +16 -2
- data/lib/neg/version.rb +1 -1
- data/neg.gemspec +1 -1
- data/spec/greedy_spec.rb +115 -0
- data/spec/parser_repetition_spec.rb +25 -22
- data/spec/parser_spec.rb +39 -17
- data/spec/sample_arith_spec.rb +6 -6
- data/spec/sample_compact_spec.rb +5 -5
- data/spec/sample_json_parser_spec.rb +4 -10
- data/spec/sample_scheme_spec.rb +39 -0
- metadata +20 -18
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,13 @@
|
|
2
2
|
# neg - CHANGELOG.md
|
3
3
|
|
4
4
|
|
5
|
+
## neg - 1.1.0 released 2013-02-28
|
6
|
+
|
7
|
+
- giving the "right" error on unconsumed input
|
8
|
+
- get rid of UnconsumedInputError
|
9
|
+
- translator: introduce n.flattened_results
|
10
|
+
|
11
|
+
|
5
12
|
## neg - 1.0.0 released 2013-01-16
|
6
13
|
|
7
14
|
- initial release
|
data/README.md
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
# neg
|
3
2
|
|
4
3
|
A neg narser.
|
@@ -30,11 +29,11 @@ Here is the classical arithmetic example:
|
|
30
29
|
|
31
30
|
expression == operation
|
32
31
|
|
33
|
-
operator
|
34
|
-
operation
|
35
|
-
value
|
36
|
-
|
37
|
-
number
|
32
|
+
operator == `+` | `-` | `*` | `/`
|
33
|
+
operation == value + (operator + value) * 0
|
34
|
+
value == parentheses | number
|
35
|
+
parentheses == `(` + expression + `)`
|
36
|
+
number == `-` * -1 + _('0-9') * 1
|
38
37
|
end
|
39
38
|
|
40
39
|
tree = ArithParser.parse("1+(2*12)")
|
@@ -116,7 +115,7 @@ It's a nested assemblage of result nodes.
|
|
116
115
|
[ :bfalse, [ 0, 1, 1 ], true, 'false', [] ]
|
117
116
|
```
|
118
117
|
|
119
|
-
In case of successful parsing, the
|
118
|
+
In case of successful parsing, the success? == false also get all pruned. In case of failed parsing, they are left in the output parse tree.
|
120
119
|
|
121
120
|
A translator turns a raw parse tree into some final result. Look below and at the JSON parser sample in the specs for more information. If the parse failed and a translator is present, a ParseError is raised.
|
122
121
|
|
@@ -132,11 +131,11 @@ class CompactArithParser < Neg::Parser
|
|
132
131
|
|
133
132
|
expression == operation
|
134
133
|
|
135
|
-
operator
|
136
|
-
operation
|
137
|
-
value
|
138
|
-
|
139
|
-
number
|
134
|
+
operator == `+` | `-` | `*` | `/`
|
135
|
+
operation == value + (operator + value) * 0
|
136
|
+
value == parentheses | number
|
137
|
+
parentheses == `(` + expression + `)`
|
138
|
+
number == `-` * -1 + _('0-9') * 1
|
140
139
|
end
|
141
140
|
|
142
141
|
translator do
|
@@ -146,7 +145,7 @@ class CompactArithParser < Neg::Parser
|
|
146
145
|
on(:value) { |n| n.results.first }
|
147
146
|
|
148
147
|
on(:expression) { |n|
|
149
|
-
results = n.
|
148
|
+
results = n.flattened_results
|
150
149
|
results.size == 1 ? results.first : results
|
151
150
|
}
|
152
151
|
end
|
@@ -156,8 +155,12 @@ CompactArithParser.parse("1+2+3")
|
|
156
155
|
# => [ 1, '+', 2, '+', 3 ]
|
157
156
|
```
|
158
157
|
|
158
|
+
The original of this parser lies in [spec/sample_arith_spec.rb](spec/sample_arith_spec.rb). Please note that it's very dumb (like everything in neg) and, for example, avoids carefully dealing with operator precedence for its target language.
|
159
|
+
|
159
160
|
As said above, when a translator is present and the parsing fails (before the translator kicks in), a ParseError is raised, with fancy methods to navigate the failed parse tree.
|
160
161
|
|
162
|
+
See also the [sample JSON parser](spec/sample_json_parser_spec.rb) and a [tiny toy scheme parser](spec/sample_scheme_spec.rb).
|
163
|
+
|
161
164
|
|
162
165
|
## presentations
|
163
166
|
|
data/TODO.txt
CHANGED
@@ -1,17 +1,10 @@
|
|
1
1
|
|
2
|
-
[o]
|
3
|
-
|
4
|
-
[o]
|
5
|
-
[o]
|
6
|
-
[o]
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
[ ] blankslate
|
12
|
-
[ ] drop UnconsumedInput, replace with regular [ false, ... ] output
|
13
|
-
[ ] x * '?' / x * '+' / x * '*' as shortcuts
|
14
|
-
[ ] memoization (only at non-terminal level?)
|
15
|
-
|
16
|
-
[ ] "xxx" instead of `xxx` (trick on the right side)
|
2
|
+
[o] investigate translator rule returning [] (json empty array for instance)
|
3
|
+
does it get discarded like the others?
|
4
|
+
[o] include the on(nil) rule by default?
|
5
|
+
[o] raise error (with #error_tree) when translator hits unparsable input
|
6
|
+
[o] document ...['name'] in the README
|
7
|
+
|
8
|
+
[o] verify that the Parslet UnconsumedInput problem doesn't appear in neg
|
9
|
+
[o] second look at the blankslate
|
17
10
|
|
data/lib/neg/errors.rb
CHANGED
@@ -27,19 +27,20 @@ module Neg
|
|
27
27
|
|
28
28
|
class NegError < StandardError; end
|
29
29
|
|
30
|
-
class UnconsumedInputError < NegError; end
|
31
30
|
class ParserError < NegError; end
|
32
31
|
|
33
32
|
class ParseError < NegError
|
34
33
|
|
35
|
-
attr_reader :tree
|
34
|
+
attr_reader :tree, :position
|
36
35
|
|
37
36
|
def initialize(tree)
|
38
37
|
|
39
38
|
@tree = tree
|
40
39
|
@nodes = list_nodes(tree)
|
41
40
|
|
42
|
-
|
41
|
+
d = deepest_error
|
42
|
+
@position = d[1]
|
43
|
+
super(d[3])
|
43
44
|
end
|
44
45
|
|
45
46
|
def errors
|
@@ -49,15 +50,30 @@ module Neg
|
|
49
50
|
|
50
51
|
def deepest_error
|
51
52
|
|
52
|
-
|
53
|
+
# let's keep the tree depth (e[5]) for later
|
54
|
+
|
55
|
+
errors.inject do |eold, enew|
|
56
|
+
if eold[1][0] <= enew[1][0]
|
57
|
+
enew
|
58
|
+
else
|
59
|
+
eold
|
60
|
+
end
|
61
|
+
end
|
53
62
|
end
|
54
63
|
|
64
|
+
def offset; @position[0]; end
|
65
|
+
def line; @position[1]; end
|
66
|
+
def column; @position[2]; end
|
67
|
+
|
55
68
|
protected
|
56
69
|
|
57
|
-
def list_nodes(start, accumulator=[])
|
70
|
+
def list_nodes(start, depth=0, accumulator=[])
|
71
|
+
|
72
|
+
start = start.dup
|
73
|
+
start << depth
|
58
74
|
|
59
75
|
accumulator << start
|
60
|
-
start[4].each { |n| list_nodes(n, accumulator) }
|
76
|
+
start[4].each { |n| list_nodes(n, depth + 1, accumulator) }
|
61
77
|
|
62
78
|
accumulator
|
63
79
|
end
|
data/lib/neg/parser.rb
CHANGED
@@ -58,15 +58,26 @@ module Neg
|
|
58
58
|
pa
|
59
59
|
end
|
60
60
|
|
61
|
+
def self.reduce(result)
|
62
|
+
|
63
|
+
if result[0] && result[2] && result[3]
|
64
|
+
result[4] =
|
65
|
+
[]
|
66
|
+
else
|
67
|
+
result[4] =
|
68
|
+
result[4].each_with_object([]) { |cr, a| a << reduce(cr) if cr[2] }
|
69
|
+
end
|
70
|
+
|
71
|
+
result
|
72
|
+
end
|
73
|
+
|
61
74
|
def self.parse(s, opts={})
|
62
75
|
|
63
76
|
i = Neg::Input(s)
|
64
77
|
|
65
78
|
result = __send__(@root).parse(i, opts)
|
66
79
|
|
67
|
-
|
68
|
-
"remaining: #{i.remains.inspect}"
|
69
|
-
) if result[2] && ( ! i.eoi?)
|
80
|
+
result[2] = false if result[2] && ( ! i.eoi?)
|
70
81
|
|
71
82
|
if @translator && opts[:translate] != false
|
72
83
|
if result[2]
|
@@ -74,8 +85,10 @@ module Neg
|
|
74
85
|
else
|
75
86
|
raise ParseError.new(result)
|
76
87
|
end
|
77
|
-
|
88
|
+
elsif result[2] == false || opts[:noreduce]
|
78
89
|
result
|
90
|
+
else
|
91
|
+
reduce(result)
|
79
92
|
end
|
80
93
|
end
|
81
94
|
|
@@ -123,10 +136,6 @@ module Neg
|
|
123
136
|
|
124
137
|
input.rewind(start) unless success
|
125
138
|
|
126
|
-
#if success && children.size == 1 && children.first[1] == start
|
127
|
-
# return children.first
|
128
|
-
#end
|
129
|
-
|
130
139
|
[ nil, start, success, result, children ]
|
131
140
|
end
|
132
141
|
end
|
@@ -144,13 +153,13 @@ module Neg
|
|
144
153
|
@child = pa
|
145
154
|
end
|
146
155
|
|
147
|
-
def
|
156
|
+
def refine(children_results)
|
148
157
|
|
149
158
|
children_results.collect { |cr|
|
150
159
|
if cr[0] && cr[0].to_s.match(/^_/).nil?
|
151
160
|
false
|
152
161
|
elsif cr[2]
|
153
|
-
cr[3] ? cr[3] :
|
162
|
+
cr[3] ? cr[3] : refine(cr[4])
|
154
163
|
else
|
155
164
|
nil
|
156
165
|
end
|
@@ -166,11 +175,10 @@ module Neg
|
|
166
175
|
return r if r[0] == false
|
167
176
|
return r if r[1].is_a?(String)
|
168
177
|
|
169
|
-
report =
|
178
|
+
report = refine(r[2])
|
179
|
+
report = report.include?(false) ? nil : report.join
|
170
180
|
|
171
|
-
|
172
|
-
|
173
|
-
[ true, report.join, opts[:noreduce] ? r[2] : [] ]
|
181
|
+
[ true, report, r[2] ]
|
174
182
|
end
|
175
183
|
|
176
184
|
def parse(input_or_string, opts)
|
@@ -214,14 +222,28 @@ module Neg
|
|
214
222
|
rs = []
|
215
223
|
|
216
224
|
loop do
|
217
|
-
|
218
|
-
break if
|
219
|
-
rs << r
|
220
|
-
break if ! r[2]
|
221
|
-
break if rs.size == @max
|
225
|
+
rs << @child.parse(i, opts)
|
226
|
+
break if rs.last[2] == false
|
222
227
|
end
|
223
228
|
|
224
|
-
|
229
|
+
rs.size.downto(1) do |i|
|
230
|
+
|
231
|
+
r = rs[i - 1]
|
232
|
+
|
233
|
+
next if r[2] == false
|
234
|
+
|
235
|
+
if @max && i > @max
|
236
|
+
r[2] = false
|
237
|
+
r[3] = "did not expect #{r[3].inspect} (min #{@min} max #{@max})"
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
trs = rs.take_while { |r| r[2] == true }
|
242
|
+
rs = trs + [ rs.find { |r| r[2] == false } ]
|
243
|
+
|
244
|
+
success = trs.size >= @min && (@max.nil? || trs.size <= @max)
|
245
|
+
|
246
|
+
i.rewind(rs.last[1]) if success && rs.last[2] == false
|
225
247
|
|
226
248
|
[ success, nil, rs ]
|
227
249
|
end
|
data/lib/neg/translator.rb
CHANGED
@@ -40,8 +40,8 @@ module Neg
|
|
40
40
|
def self.translate(parse_tree)
|
41
41
|
|
42
42
|
results =
|
43
|
-
parse_tree[4].each_with_object([]) { |
|
44
|
-
catch(nil) { a << translate(
|
43
|
+
parse_tree[4].each_with_object([]) { |tree, a|
|
44
|
+
catch(nil) { a << translate(tree) } if tree[2]
|
45
45
|
}
|
46
46
|
|
47
47
|
apply(parse_tree, results)
|
@@ -70,6 +70,20 @@ module Neg
|
|
70
70
|
def column; @parse_tree[1][2]; end
|
71
71
|
|
72
72
|
def result; @parse_tree[3]; end
|
73
|
+
|
74
|
+
# Useful when the grammar has something of the form:
|
75
|
+
#
|
76
|
+
# array == `[` + (value + (`,` + value) * 0) * 0 + `]`
|
77
|
+
#
|
78
|
+
# It flattens the value array.
|
79
|
+
#
|
80
|
+
# Look at the spec/sample_* files to see it in action.
|
81
|
+
#
|
82
|
+
def flattened_results
|
83
|
+
|
84
|
+
f2 = results.flatten(2)
|
85
|
+
f2.any? ? [ f2.shift ] + f2.flatten(2) : []
|
86
|
+
end
|
73
87
|
end
|
74
88
|
end
|
75
89
|
end
|
data/lib/neg/version.rb
CHANGED
data/neg.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.platform = Gem::Platform::RUBY
|
11
11
|
s.authors = [ 'John Mettraux' ]
|
12
12
|
s.email = [ 'jmettraux@gmail.com' ]
|
13
|
-
s.homepage = 'https://github.com/jmettraux/
|
13
|
+
s.homepage = 'https://github.com/jmettraux/neg'
|
14
14
|
s.rubyforge_project = 'ruote'
|
15
15
|
s.summary = 'a neg narser'
|
16
16
|
|
data/spec/greedy_spec.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
|
5
|
+
describe 'neg and errors' do
|
6
|
+
|
7
|
+
class BlockParser < Neg::Parser
|
8
|
+
|
9
|
+
parser do
|
10
|
+
|
11
|
+
blocks == nl? + block + (nl + block) * 0 + nl?
|
12
|
+
|
13
|
+
block == sp? + `begin` + sp + _('a-z') + nl + body + sp? + `end`
|
14
|
+
body == ((line | block) + nl) * 0
|
15
|
+
line == sp + `line`
|
16
|
+
|
17
|
+
nl == _("\s\n") * 1
|
18
|
+
nl? == _("\s\n") * 0
|
19
|
+
sp == _(" ") * 1
|
20
|
+
sp? == _(" ") * 0
|
21
|
+
end
|
22
|
+
|
23
|
+
translator do
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'parses a single empty block' do
|
28
|
+
|
29
|
+
BlockParser.parse(%{
|
30
|
+
begin a
|
31
|
+
end
|
32
|
+
})
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'parses nested blocks' do
|
36
|
+
|
37
|
+
BlockParser.parse(%{
|
38
|
+
begin a
|
39
|
+
begin b
|
40
|
+
end
|
41
|
+
end
|
42
|
+
})
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'parses successive blocks' do
|
46
|
+
|
47
|
+
BlockParser.parse(%{
|
48
|
+
begin a
|
49
|
+
end
|
50
|
+
begin b
|
51
|
+
end
|
52
|
+
})
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'fails gracefully on a missing end (0)' do
|
56
|
+
|
57
|
+
err = nil
|
58
|
+
|
59
|
+
begin
|
60
|
+
BlockParser.parse(%{
|
61
|
+
begin a
|
62
|
+
begin b
|
63
|
+
end
|
64
|
+
})
|
65
|
+
rescue => err
|
66
|
+
end
|
67
|
+
|
68
|
+
err.class.should == Neg::ParseError
|
69
|
+
err.position.should == [ 53, 5, 6 ]
|
70
|
+
err.message.should == 'expected "end", got ""'
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'fails gracefully on a missing end (1)' do
|
74
|
+
|
75
|
+
err = nil
|
76
|
+
|
77
|
+
begin
|
78
|
+
BlockParser.parse(%{
|
79
|
+
begin a
|
80
|
+
end
|
81
|
+
begin b
|
82
|
+
begin c
|
83
|
+
end
|
84
|
+
})
|
85
|
+
rescue => err
|
86
|
+
end
|
87
|
+
|
88
|
+
err.class.should == Neg::ParseError
|
89
|
+
err.position.should == [ 81, 7, 6 ]
|
90
|
+
err.message.should == 'expected "end", got ""'
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'fails gracefully on a li too much (2)' do
|
94
|
+
|
95
|
+
err = nil
|
96
|
+
|
97
|
+
begin
|
98
|
+
BlockParser.parse(%{
|
99
|
+
begin a
|
100
|
+
end
|
101
|
+
begin b
|
102
|
+
begin c
|
103
|
+
li
|
104
|
+
end
|
105
|
+
end
|
106
|
+
})
|
107
|
+
rescue => err
|
108
|
+
end
|
109
|
+
|
110
|
+
err.class.should == Neg::ParseError
|
111
|
+
err.position.should == [ 75, 6, 12 ]
|
112
|
+
err.message.should == 'expected "end", got "li\n"'
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
@@ -18,14 +18,15 @@ describe Neg::Parser::RepetitionParser do
|
|
18
18
|
[ :text, [ 0, 1, 1 ], true, '', [] ]
|
19
19
|
end
|
20
20
|
|
21
|
-
it 'fails gracefully' do
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
21
|
+
#it 'fails gracefully' do
|
22
|
+
# lambda {
|
23
|
+
# parser.parse('xx')
|
24
|
+
# }.should raise_error(
|
25
|
+
# Neg::UnconsumedInputError,
|
26
|
+
# 'remaining: "x"')
|
27
|
+
#end
|
28
|
+
#
|
29
|
+
# UnconsumedInputError is gone.
|
29
30
|
|
30
31
|
it 'is rendered correctly via #to_s' do
|
31
32
|
|
@@ -57,14 +58,15 @@ describe Neg::Parser::RepetitionParser do
|
|
57
58
|
[ :text, [ 0, 1, 1 ], true, 'xxx', [] ]
|
58
59
|
end
|
59
60
|
|
60
|
-
it 'fails gracefully' do
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
61
|
+
#it 'fails gracefully' do
|
62
|
+
# lambda {
|
63
|
+
# parser.parse('a')
|
64
|
+
# }.should raise_error(
|
65
|
+
# Neg::UnconsumedInputError,
|
66
|
+
# 'remaining: "a"')
|
67
|
+
#end
|
68
|
+
#
|
69
|
+
# UnconsumedInputError is gone.
|
68
70
|
end
|
69
71
|
|
70
72
|
context '`x` * 2 (at least 2)' do
|
@@ -113,12 +115,13 @@ describe Neg::Parser::RepetitionParser do
|
|
113
115
|
[ nil, [ 2, 1, 3 ], false, 'expected "x", got ""', [] ] ] ]
|
114
116
|
end
|
115
117
|
|
116
|
-
it 'fails gracefully (unconsumed input)' do
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
118
|
+
#it 'fails gracefully (unconsumed input)' do
|
119
|
+
# lambda {
|
120
|
+
# parser.parse('xxxx')
|
121
|
+
# }.should raise_error(Neg::UnconsumedInputError, 'remaining: "x"')
|
122
|
+
#end
|
123
|
+
#
|
124
|
+
# UnconsumedInputError is gone.
|
122
125
|
|
123
126
|
it 'is rendered correctly via #to_s' do
|
124
127
|
|
data/spec/parser_spec.rb
CHANGED
@@ -33,30 +33,52 @@ describe 'Neg::Parser' do
|
|
33
33
|
#
|
34
34
|
# not worth the pain for now
|
35
35
|
|
36
|
-
describe '.parse' do
|
36
|
+
# describe '.parse' do
|
37
|
+
#
|
38
|
+
# let(:parser) {
|
39
|
+
# Class.new(Neg::Parser) do
|
40
|
+
# text == `x`
|
41
|
+
# end
|
42
|
+
# }
|
43
|
+
#
|
44
|
+
# it 'raises on unconsumed input' do
|
45
|
+
#
|
46
|
+
# lambda {
|
47
|
+
# parser.parse('xy')
|
48
|
+
# }.should raise_error(
|
49
|
+
# Neg::UnconsumedInputError,
|
50
|
+
# 'remaining: "y"')
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# it 'raises on unconsumed input (...)' do
|
54
|
+
#
|
55
|
+
# lambda {
|
56
|
+
# parser.parse('xyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy')
|
57
|
+
# }.should raise_error(
|
58
|
+
# Neg::UnconsumedInputError,
|
59
|
+
# 'remaining: "yyyyyyy..."')
|
60
|
+
# end
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# UnconsumedInputError is gone.
|
64
|
+
|
65
|
+
context 'unconsumed input' do
|
37
66
|
|
38
67
|
let(:parser) {
|
39
68
|
Class.new(Neg::Parser) do
|
40
|
-
text == `x`
|
69
|
+
text == `x` * -1
|
41
70
|
end
|
42
71
|
}
|
43
72
|
|
44
|
-
it '
|
45
|
-
|
46
|
-
lambda {
|
47
|
-
parser.parse('xy')
|
48
|
-
}.should raise_error(
|
49
|
-
Neg::UnconsumedInputError,
|
50
|
-
'remaining: "y"')
|
51
|
-
end
|
52
|
-
|
53
|
-
it 'raises on unconsumed input (...)' do
|
73
|
+
it 'fails gracefully' do
|
54
74
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
75
|
+
parser.parse('xx').should ==
|
76
|
+
[ :text,
|
77
|
+
[ 0, 1, 1 ],
|
78
|
+
false,
|
79
|
+
'x',
|
80
|
+
[ [ nil, [0, 1, 1], true, 'x', [] ],
|
81
|
+
[ nil, [1, 1, 2], false, 'did not expect "x" (min 0 max 1)', [] ] ] ]
|
60
82
|
end
|
61
83
|
end
|
62
84
|
end
|
data/spec/sample_arith_spec.rb
CHANGED
@@ -8,11 +8,11 @@ describe 'sample math parser' do
|
|
8
8
|
|
9
9
|
expression == operation
|
10
10
|
|
11
|
-
operator
|
12
|
-
operation
|
13
|
-
value
|
14
|
-
|
15
|
-
number
|
11
|
+
operator == `+` | `-` | `*` | `/`
|
12
|
+
operation == value + (operator + value) * 0
|
13
|
+
value == parentheses | number
|
14
|
+
parentheses == `(` + expression + `)`
|
15
|
+
number == `-` * -1 + _('0-9') * 1
|
16
16
|
end
|
17
17
|
|
18
18
|
class ArithTranslator < Neg::Translator
|
@@ -22,7 +22,7 @@ describe 'sample math parser' do
|
|
22
22
|
on(:value) { |n| n.results.first }
|
23
23
|
|
24
24
|
on(:expression) { |n|
|
25
|
-
results = n.
|
25
|
+
results = n.flattened_results
|
26
26
|
results.size == 1 ? results.first : results
|
27
27
|
}
|
28
28
|
end
|
data/spec/sample_compact_spec.rb
CHANGED
@@ -10,11 +10,11 @@ describe 'sample compact arith parser' do
|
|
10
10
|
|
11
11
|
expression == operation
|
12
12
|
|
13
|
-
operator
|
14
|
-
operation
|
15
|
-
value
|
16
|
-
|
17
|
-
number
|
13
|
+
operator == `+` | `-` | `*` | `/`
|
14
|
+
operation == value + (operator + value) * 0
|
15
|
+
value == parentheses | number
|
16
|
+
parentheses == `(` + expression + `)`
|
17
|
+
number == `-` * -1 + _('0-9') * 1
|
18
18
|
end
|
19
19
|
|
20
20
|
translator do
|
@@ -40,14 +40,8 @@ describe 'sample JSON parser' do
|
|
40
40
|
on(:value) { |n| n.results.first.first }
|
41
41
|
on(:spaces?) { throw nil }
|
42
42
|
|
43
|
-
on(:object) { |n|
|
44
|
-
|
45
|
-
Hash[f2.any? ? [ f2.shift ] + f2.flatten(2) : []]
|
46
|
-
}
|
47
|
-
on(:array) { |n|
|
48
|
-
f2 = n.results.flatten(2)
|
49
|
-
f2.any? ? [ f2.shift ] + f2.flatten(2) : []
|
50
|
-
}
|
43
|
+
on(:object) { |n| Hash[n.flattened_results] }
|
44
|
+
on(:array) { |n| n.flattened_results }
|
51
45
|
|
52
46
|
on(:string) { |n| eval(n.result) }
|
53
47
|
|
@@ -198,8 +192,8 @@ describe 'sample JSON parser' do
|
|
198
192
|
it 'raises a ParseError on incorrect input' do
|
199
193
|
|
200
194
|
lambda do
|
201
|
-
JsonParser.parse(
|
202
|
-
end.should raise_error(Neg::ParseError, 'expected "
|
195
|
+
JsonParser.parse('x')
|
196
|
+
end.should raise_error(Neg::ParseError, 'expected "null", got "x"')
|
203
197
|
end
|
204
198
|
end
|
205
199
|
|
@@ -0,0 +1,39 @@
|
|
1
|
+
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
|
5
|
+
describe 'sample scheme parser' do
|
6
|
+
|
7
|
+
class SchemeParser < Neg::Parser
|
8
|
+
|
9
|
+
parser do
|
10
|
+
|
11
|
+
expression == list | atom
|
12
|
+
|
13
|
+
list == `(` + (expression + (` ` + expression) * 0) * 0 + `)`
|
14
|
+
atom == _("^() ") * 1
|
15
|
+
end
|
16
|
+
|
17
|
+
translator do
|
18
|
+
|
19
|
+
on(:expression) { |n| n.results.first }
|
20
|
+
on(:atom) { |n| n.result }
|
21
|
+
|
22
|
+
on(:list) { |n| n.flattened_results }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'parses numbers' do
|
27
|
+
|
28
|
+
SchemeParser.parse("13").should == "13"
|
29
|
+
SchemeParser.parse("-13").should == "-13"
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'parses lists' do
|
33
|
+
|
34
|
+
SchemeParser.parse("()").should == []
|
35
|
+
SchemeParser.parse("(a b c)").should == [ 'a', 'b', 'c' ]
|
36
|
+
SchemeParser.parse("(a (b c))").should == [ 'a', [ 'b', 'c' ] ]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: neg
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-02-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -51,31 +51,33 @@ extensions: []
|
|
51
51
|
extra_rdoc_files: []
|
52
52
|
files:
|
53
53
|
- Rakefile
|
54
|
-
- lib/neg/errors.rb
|
55
54
|
- lib/neg/input.rb
|
55
|
+
- lib/neg/errors.rb
|
56
|
+
- lib/neg/version.rb
|
56
57
|
- lib/neg/parser.rb
|
57
58
|
- lib/neg/translator.rb
|
58
|
-
- lib/neg/version.rb
|
59
59
|
- lib/neg.rb
|
60
|
-
- spec/
|
61
|
-
- spec/
|
62
|
-
- spec/parser_character_spec.rb
|
63
|
-
- spec/parser_lookahead_parser_spec.rb
|
60
|
+
- spec/sample_arith_spec.rb
|
61
|
+
- spec/spec_helper.rb
|
64
62
|
- spec/parser_non_terminal_spec.rb
|
65
|
-
- spec/
|
63
|
+
- spec/greedy_spec.rb
|
64
|
+
- spec/parser_character_spec.rb
|
65
|
+
- spec/parser_string_spec.rb
|
66
|
+
- spec/sample_scheme_spec.rb
|
66
67
|
- spec/parser_sequence_spec.rb
|
68
|
+
- spec/sample_json_parser_spec.rb
|
69
|
+
- spec/parser_alternative_spec.rb
|
67
70
|
- spec/parser_spec.rb
|
68
|
-
- spec/parser_string_spec.rb
|
69
|
-
- spec/sample_arith_spec.rb
|
70
71
|
- spec/sample_compact_spec.rb
|
71
|
-
- spec/
|
72
|
-
- spec/
|
72
|
+
- spec/input_spec.rb
|
73
|
+
- spec/parser_lookahead_parser_spec.rb
|
74
|
+
- spec/parser_repetition_spec.rb
|
73
75
|
- neg.gemspec
|
74
|
-
- LICENSE.txt
|
75
76
|
- TODO.txt
|
76
|
-
-
|
77
|
+
- LICENSE.txt
|
77
78
|
- README.md
|
78
|
-
|
79
|
+
- CHANGELOG.md
|
80
|
+
homepage: https://github.com/jmettraux/neg
|
79
81
|
licenses: []
|
80
82
|
post_install_message:
|
81
83
|
rdoc_options: []
|
@@ -89,7 +91,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
89
91
|
version: '0'
|
90
92
|
segments:
|
91
93
|
- 0
|
92
|
-
hash:
|
94
|
+
hash: 4523696272151903388
|
93
95
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
96
|
none: false
|
95
97
|
requirements:
|
@@ -98,7 +100,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
98
100
|
version: '0'
|
99
101
|
segments:
|
100
102
|
- 0
|
101
|
-
hash:
|
103
|
+
hash: 4523696272151903388
|
102
104
|
requirements: []
|
103
105
|
rubyforge_project: ruote
|
104
106
|
rubygems_version: 1.8.24
|