neg 0.3.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +1 -1
- data/LICENSE.txt +1 -1
- data/README.md +168 -1
- data/TODO.txt +4 -5
- data/lib/neg.rb +1 -0
- data/lib/neg/errors.rb +66 -0
- data/lib/neg/input.rb +1 -1
- data/lib/neg/parser.rb +71 -49
- data/lib/neg/translator.rb +76 -0
- data/lib/neg/version.rb +2 -2
- data/spec/parser_alternative_spec.rb +8 -3
- data/spec/parser_character_spec.rb +2 -19
- data/spec/parser_lookahead_parser_spec.rb +18 -16
- data/spec/parser_non_terminal_spec.rb +9 -12
- data/spec/parser_repetition_spec.rb +5 -14
- data/spec/parser_sequence_spec.rb +2 -15
- data/spec/parser_spec.rb +15 -0
- data/spec/parser_string_spec.rb +3 -3
- data/spec/sample_arith_spec.rb +90 -0
- data/spec/sample_compact_spec.rb +50 -0
- data/spec/sample_json_parser_spec.rb +169 -50
- metadata +12 -2
@@ -0,0 +1,76 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2012-2013, John Mettraux, jmettraux@gmail.com
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
#
|
22
|
+
# Made in Japan.
|
23
|
+
#++
|
24
|
+
|
25
|
+
|
26
|
+
module Neg
|
27
|
+
|
28
|
+
class Translator
|
29
|
+
|
30
|
+
def self.on(node_name, &block)
|
31
|
+
|
32
|
+
@rules ||=
|
33
|
+
{ nil => lambda { |n| n.results.empty? ? throw(nil) : n.results } }
|
34
|
+
#
|
35
|
+
# the default rule for anonymous nodes...
|
36
|
+
|
37
|
+
@rules[node_name] = block
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.translate(parse_tree)
|
41
|
+
|
42
|
+
results =
|
43
|
+
parse_tree[4].each_with_object([]) { |node, a|
|
44
|
+
catch(nil) { a << translate(node) }
|
45
|
+
}
|
46
|
+
|
47
|
+
apply(parse_tree, results)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.apply(parse_tree, results)
|
51
|
+
|
52
|
+
rule = (@rules || {})[parse_tree[0]]
|
53
|
+
|
54
|
+
rule ? rule.call(Node.new(parse_tree, results)) : results
|
55
|
+
end
|
56
|
+
|
57
|
+
class Node
|
58
|
+
|
59
|
+
attr_reader :parse_tree, :results
|
60
|
+
|
61
|
+
def initialize(parse_tree, results)
|
62
|
+
|
63
|
+
@parse_tree = parse_tree
|
64
|
+
@results = results
|
65
|
+
end
|
66
|
+
|
67
|
+
def position; @parse_tree[1]; end
|
68
|
+
def offset; @parse_tree[1][0]; end
|
69
|
+
def line; @parse_tree[1][1]; end
|
70
|
+
def column; @parse_tree[1][2]; end
|
71
|
+
|
72
|
+
def result; @parse_tree[3]; end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
data/lib/neg/version.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c) 2012-
|
2
|
+
# Copyright (c) 2012-2013, John Mettraux, jmettraux@gmail.com
|
3
3
|
#
|
4
4
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
5
|
# of this software and associated documentation files (the "Software"), to deal
|
@@ -25,6 +25,6 @@
|
|
25
25
|
|
26
26
|
module Neg
|
27
27
|
|
28
|
-
VERSION = '0.
|
28
|
+
VERSION = '1.0.0'
|
29
29
|
end
|
30
30
|
|
@@ -11,14 +11,19 @@ describe Neg::Parser::AlternativeParser do
|
|
11
11
|
it 'parses' do
|
12
12
|
|
13
13
|
AltParser.parse('x').should ==
|
14
|
-
[ :text, [ 0, 1, 1 ], true,
|
15
|
-
[ nil, [ 0, 1, 1 ], true, 'x', [] ] ] ]
|
14
|
+
[ :text, [ 0, 1, 1 ], true, 'x', [] ]
|
16
15
|
end
|
17
16
|
|
18
17
|
it 'parses (2nd alternative succeeds)' do
|
19
18
|
|
20
19
|
AltParser.parse('y').should ==
|
21
|
-
[ :text, [ 0, 1, 1 ], true,
|
20
|
+
[ :text, [ 0, 1, 1 ], true, 'y', [] ]
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'parses (2nd alternative succeeds) :noreduce => true' do
|
24
|
+
|
25
|
+
AltParser.parse('y', :noreduce => true).should ==
|
26
|
+
[ :text, [ 0, 1, 1 ], true, 'y', [
|
22
27
|
[ nil, [ 0, 1, 1 ], false, 'expected "x", got "y"', [] ],
|
23
28
|
[ nil, [ 0, 1, 1 ], true, 'y', [] ] ] ]
|
24
29
|
end
|
@@ -15,12 +15,7 @@ describe Neg::Parser::CharacterParser do
|
|
15
15
|
it 'parses "xy"' do
|
16
16
|
|
17
17
|
parser.parse('xy').should ==
|
18
|
-
[ :text,
|
19
|
-
[ 0, 1, 1 ],
|
20
|
-
true,
|
21
|
-
nil,
|
22
|
-
[ [ nil, [ 0, 1, 1 ], true, "x", [] ],
|
23
|
-
[ nil, [ 1, 1, 2 ], true, "y", [] ] ] ]
|
18
|
+
[ :text, [ 0, 1, 1 ], true, 'xy', [] ]
|
24
19
|
end
|
25
20
|
|
26
21
|
it 'fails gracefully' do
|
@@ -55,19 +50,7 @@ describe Neg::Parser::CharacterParser do
|
|
55
50
|
it 'parses "tel:0-99"' do
|
56
51
|
|
57
52
|
parser.parse('tel:0-99').should ==
|
58
|
-
[ :text,
|
59
|
-
[ 0, 1, 1 ],
|
60
|
-
true,
|
61
|
-
nil,
|
62
|
-
[ [ nil, [ 0, 1, 1 ], true, "tel:", [] ],
|
63
|
-
[ nil,
|
64
|
-
[ 4, 1, 5 ],
|
65
|
-
true,
|
66
|
-
nil,
|
67
|
-
[ [ nil, [ 4, 1, 5 ], true, "0", [] ],
|
68
|
-
[ nil, [ 5, 1, 6 ], true, "-", [] ],
|
69
|
-
[ nil, [ 6, 1, 7 ], true, "9", [] ],
|
70
|
-
[ nil, [ 7, 1, 8 ], true, "9", [] ] ] ] ] ]
|
53
|
+
[ :text, [ 0, 1, 1 ], true, 'tel:0-99', [] ]
|
71
54
|
end
|
72
55
|
|
73
56
|
it 'fails gracefully' do
|
@@ -17,6 +17,17 @@ describe Neg::Parser::LookaheadParser do
|
|
17
17
|
it 'parses' do
|
18
18
|
|
19
19
|
parser.parse('xz').should ==
|
20
|
+
[ :root,
|
21
|
+
[ 0, 1, 1 ],
|
22
|
+
true,
|
23
|
+
nil,
|
24
|
+
[ [ :x, [ 0, 1, 1 ], true, 'x', [] ],
|
25
|
+
[ :z, [ 1, 1, 2 ], true, 'z', [] ] ] ]
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'parses (:noreduce => true)' do
|
29
|
+
|
30
|
+
parser.parse('xz', :noreduce => true).should ==
|
20
31
|
[ :root,
|
21
32
|
[ 0, 1, 1 ],
|
22
33
|
true,
|
@@ -24,11 +35,11 @@ describe Neg::Parser::LookaheadParser do
|
|
24
35
|
[ [ :x,
|
25
36
|
[ 0, 1, 1 ],
|
26
37
|
true,
|
27
|
-
|
28
|
-
[ [ nil, [ 0, 1, 1 ], true,
|
29
|
-
[ nil, [ 1, 1, 2 ], true,
|
30
|
-
[ nil, [ 1, 1, 2 ], true,
|
31
|
-
[ :z, [ 1, 1, 2 ], true,
|
38
|
+
'x',
|
39
|
+
[ [ nil, [ 0, 1, 1 ], true, 'x', [] ],
|
40
|
+
[ nil, [ 1, 1, 2 ], true, '', [
|
41
|
+
[ nil, [ 1, 1, 2 ], true, 'z', [] ] ] ] ] ],
|
42
|
+
[ :z, [ 1, 1, 2 ], true, 'z', [] ] ] ]
|
32
43
|
end
|
33
44
|
|
34
45
|
it 'fails gracefully' do
|
@@ -79,17 +90,8 @@ describe Neg::Parser::LookaheadParser do
|
|
79
90
|
[ 0, 1, 1 ],
|
80
91
|
true,
|
81
92
|
nil,
|
82
|
-
[ [ :x,
|
83
|
-
|
84
|
-
true,
|
85
|
-
nil,
|
86
|
-
[ [ nil, [ 0, 1, 1 ], true, "x", [] ],
|
87
|
-
[ nil,
|
88
|
-
[ 1, 1, 2 ],
|
89
|
-
true,
|
90
|
-
nil,
|
91
|
-
[ [ nil, [ 1, 1, 2 ], false, "expected \"y\", got \"z\"", [] ] ] ] ] ],
|
92
|
-
[ :z, [ 1, 1, 2 ], true, "z", [] ] ] ]
|
93
|
+
[ [ :x, [ 0, 1, 1 ], true, 'x', [] ],
|
94
|
+
[ :z, [ 1, 1, 2 ], true, 'z', [] ] ] ]
|
93
95
|
end
|
94
96
|
|
95
97
|
it 'fails gracefully' do
|
@@ -27,9 +27,7 @@ describe Neg::Parser::NonTerminalParser do
|
|
27
27
|
end
|
28
28
|
|
29
29
|
parser.parse('X').should ==
|
30
|
-
[ :x, [ 0, 1, 1 ], true,
|
31
|
-
[ nil, [ 0, 1, 1 ], false, "expected \"x\", got \"X\"", [] ],
|
32
|
-
[ nil, [ 0, 1, 1 ], true, "X", [] ] ] ]
|
30
|
+
[ :x, [ 0, 1, 1 ], true, 'X', [] ]
|
33
31
|
end
|
34
32
|
|
35
33
|
it 'is rendered as x when on the right side' do
|
@@ -49,14 +47,15 @@ describe Neg::Parser::NonTerminalParser do
|
|
49
47
|
}.strip
|
50
48
|
end
|
51
49
|
|
52
|
-
it '
|
50
|
+
it 'does not bind if the name starts with _' do
|
53
51
|
|
54
52
|
parser = Class.new(Neg::Parser) do
|
55
|
-
sentence == (
|
56
|
-
|
53
|
+
sentence == (_word + ` `) * 1
|
54
|
+
_word == _("a-z") * 1
|
57
55
|
end
|
58
56
|
|
59
|
-
|
57
|
+
parser.parse("ab cd ").should ==
|
58
|
+
[ :sentence, [ 0, 1, 1 ], true, 'ab cd ', [] ]
|
60
59
|
end
|
61
60
|
end
|
62
61
|
|
@@ -87,11 +86,9 @@ describe Neg::Parser::NonTerminalParser do
|
|
87
86
|
[ 0, 1, 1 ],
|
88
87
|
true,
|
89
88
|
nil,
|
90
|
-
[ [ 'vehicle', [ 0, 1, 1 ], true,
|
91
|
-
[ nil, [
|
92
|
-
|
93
|
-
[ 'city', [ 4, 1, 5 ], true, nil, [
|
94
|
-
[ nil, [ 4, 1, 5 ], true, 'cluj', [] ] ] ] ] ]
|
89
|
+
[ [ 'vehicle', [ 0, 1, 1 ], true, 'car', [] ],
|
90
|
+
[ nil, [ 3, 1, 4 ], true, '_', [] ],
|
91
|
+
[ 'city', [ 4, 1, 5 ], true, 'cluj', [] ] ] ]
|
95
92
|
end
|
96
93
|
end
|
97
94
|
end
|
@@ -15,7 +15,7 @@ describe Neg::Parser::RepetitionParser do
|
|
15
15
|
it 'parses the empty string' do
|
16
16
|
|
17
17
|
parser.parse('').should ==
|
18
|
-
[ :text, [ 0, 1, 1 ], true,
|
18
|
+
[ :text, [ 0, 1, 1 ], true, '', [] ]
|
19
19
|
end
|
20
20
|
|
21
21
|
it 'fails gracefully' do
|
@@ -48,16 +48,13 @@ describe Neg::Parser::RepetitionParser do
|
|
48
48
|
it 'parses the empty string' do
|
49
49
|
|
50
50
|
parser.parse('').should ==
|
51
|
-
[ :text, [ 0, 1, 1 ], true,
|
51
|
+
[ :text, [ 0, 1, 1 ], true, '', [] ]
|
52
52
|
end
|
53
53
|
|
54
54
|
it 'parses' do
|
55
55
|
|
56
56
|
parser.parse('xxx').should ==
|
57
|
-
[ :text, [ 0, 1, 1 ], true,
|
58
|
-
[ nil, [ 0, 1, 1 ], true, 'x', [] ],
|
59
|
-
[ nil, [ 1, 1, 2 ], true, 'x', [] ],
|
60
|
-
[ nil, [ 2, 1, 3 ], true, 'x', [] ] ] ]
|
57
|
+
[ :text, [ 0, 1, 1 ], true, 'xxx', [] ]
|
61
58
|
end
|
62
59
|
|
63
60
|
it 'fails gracefully' do
|
@@ -81,10 +78,7 @@ describe Neg::Parser::RepetitionParser do
|
|
81
78
|
it 'parses' do
|
82
79
|
|
83
80
|
parser.parse('xxx').should ==
|
84
|
-
[ :text, [ 0, 1, 1 ], true,
|
85
|
-
[ nil, [ 0, 1, 1 ], true, 'x', [] ],
|
86
|
-
[ nil, [ 1, 1, 2 ], true, 'x', [] ],
|
87
|
-
[ nil, [ 2, 1, 3 ], true, 'x', [] ] ] ]
|
81
|
+
[ :text, [ 0, 1, 1 ], true, 'xxx', [] ]
|
88
82
|
end
|
89
83
|
|
90
84
|
it 'fails gracefully' do
|
@@ -107,10 +101,7 @@ describe Neg::Parser::RepetitionParser do
|
|
107
101
|
it 'parses' do
|
108
102
|
|
109
103
|
parser.parse('xxx').should ==
|
110
|
-
[ :text, [ 0, 1, 1 ], true,
|
111
|
-
[ nil, [ 0, 1, 1 ], true, 'x', [] ],
|
112
|
-
[ nil, [ 1, 1, 2 ], true, 'x', [] ],
|
113
|
-
[ nil, [ 2, 1, 3 ], true, 'x', [] ] ] ]
|
104
|
+
[ :text, [ 0, 1, 1 ], true, 'xxx', [] ]
|
114
105
|
end
|
115
106
|
|
116
107
|
it 'fails gracefully' do
|
@@ -11,9 +11,7 @@ describe Neg::Parser::SequenceParser do
|
|
11
11
|
it 'parses' do
|
12
12
|
|
13
13
|
SeqParser.parse('xy').should ==
|
14
|
-
[ :text, [ 0, 1, 1 ], true,
|
15
|
-
[ nil, [ 0, 1, 1 ], true, 'x', [] ],
|
16
|
-
[ nil, [ 1, 1, 2 ], true, 'y', [] ] ] ]
|
14
|
+
[ :text, [ 0, 1, 1 ], true, 'xy', [] ]
|
17
15
|
end
|
18
16
|
|
19
17
|
it 'fails gracefully' do
|
@@ -51,18 +49,7 @@ describe Neg::Parser::SequenceParser do
|
|
51
49
|
[ 0, 1, 1 ],
|
52
50
|
true,
|
53
51
|
nil,
|
54
|
-
[ [ :
|
55
|
-
[ 0, 1, 1 ],
|
56
|
-
false,
|
57
|
-
nil,
|
58
|
-
[ [ nil, [ 0, 1, 1 ], true, "poo", [] ],
|
59
|
-
[ nil, [ 3, 1, 4 ], false, "expected \"dle\", got \"l\"", [] ] ] ],
|
60
|
-
[ :pool,
|
61
|
-
[ 0, 1, 1 ],
|
62
|
-
true,
|
63
|
-
nil,
|
64
|
-
[ [ nil, [ 0, 1, 1 ], true, "poo", [] ],
|
65
|
-
[ nil, [ 3, 1, 4 ], true, "l", [] ] ] ] ] ]
|
52
|
+
[ [ :pool, [ 0, 1, 1 ], true, "pool", [] ] ] ]
|
66
53
|
end
|
67
54
|
end
|
68
55
|
|
data/spec/parser_spec.rb
CHANGED
@@ -18,6 +18,21 @@ describe 'Neg::Parser' do
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
+
# describe 'the blankslate' do
|
22
|
+
#
|
23
|
+
# it 'lets through node names like "send"' do
|
24
|
+
#
|
25
|
+
# parser =
|
26
|
+
# Class.new(Neg::Parser) do
|
27
|
+
# send == `x`
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# parser.parse("x").should == [ :send, [ 0, 1, 1 ], true, 'x', [] ]
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# not worth the pain for now
|
35
|
+
|
21
36
|
describe '.parse' do
|
22
37
|
|
23
38
|
let(:parser) {
|
data/spec/parser_string_spec.rb
CHANGED
@@ -10,7 +10,7 @@ describe Neg::Parser::StringParser do
|
|
10
10
|
|
11
11
|
parser = Neg::Parser::StringParser.new('xxx')
|
12
12
|
|
13
|
-
parser.parse('xxx').should ==
|
13
|
+
parser.parse('xxx', {}).should ==
|
14
14
|
[ nil, [ 0, 1, 1 ], true, 'xxx', [] ]
|
15
15
|
end
|
16
16
|
|
@@ -18,7 +18,7 @@ describe Neg::Parser::StringParser do
|
|
18
18
|
|
19
19
|
parser = Neg::Parser::StringParser.new('xxx')
|
20
20
|
|
21
|
-
parser.parse('yyy').should ==
|
21
|
+
parser.parse('yyy', {}).should ==
|
22
22
|
[ nil, [ 0, 1, 1 ], false, 'expected "xxx", got "yyy"', [] ]
|
23
23
|
end
|
24
24
|
end
|
@@ -33,7 +33,7 @@ describe Neg::Parser::StringParser do
|
|
33
33
|
|
34
34
|
it 'parses an exact string' do
|
35
35
|
|
36
|
-
parser.parse('x').should ==
|
36
|
+
parser.parse('x', {}).should ==
|
37
37
|
[ :root, [ 0, 1, 1 ], true, 'x', [] ]
|
38
38
|
end
|
39
39
|
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
|
5
|
+
describe 'sample math parser' do
|
6
|
+
|
7
|
+
class ArithParser < Neg::Parser
|
8
|
+
|
9
|
+
expression == operation
|
10
|
+
|
11
|
+
operator == `+` | `-` | `*` | `/`
|
12
|
+
operation == value + (operator + value) * 0
|
13
|
+
value == parenthese | number
|
14
|
+
parenthese == `(` + expression + `)`
|
15
|
+
number == `-` * -1 + _('0-9') * 1
|
16
|
+
end
|
17
|
+
|
18
|
+
class ArithTranslator < Neg::Translator
|
19
|
+
|
20
|
+
on(:number) { |n| n.result.to_i }
|
21
|
+
on(:operator) { |n| n.result }
|
22
|
+
on(:value) { |n| n.results.first }
|
23
|
+
|
24
|
+
on(:expression) { |n|
|
25
|
+
results = n.results.flatten(2)
|
26
|
+
results.size == 1 ? results.first : results
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def parse(s, opts={})
|
31
|
+
|
32
|
+
r = ArithParser.parse(s, opts)
|
33
|
+
|
34
|
+
if ENV['DEBUG'] == 'true' # /!\ not $DEBUG
|
35
|
+
puts "--#{s.inspect}-->"
|
36
|
+
pp r
|
37
|
+
end
|
38
|
+
|
39
|
+
r[2]
|
40
|
+
end
|
41
|
+
|
42
|
+
def translate(s)
|
43
|
+
|
44
|
+
ArithTranslator.translate(ArithParser.parse(s))
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'parses numbers' do
|
48
|
+
|
49
|
+
parse("0").should == true
|
50
|
+
parse("-0").should == true
|
51
|
+
parse("12").should == true
|
52
|
+
parse("-3").should == true
|
53
|
+
parse("-12").should == true
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'parses parentheses' do
|
57
|
+
|
58
|
+
parse("(1)").should == true
|
59
|
+
parse("((1))").should == true
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'parses operations' do
|
63
|
+
|
64
|
+
parse("1+1").should == true
|
65
|
+
parse("1+-1").should == true
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'parses at large' do
|
69
|
+
|
70
|
+
parse("1+(1+1)").should == true
|
71
|
+
parse("12+(34-(56/78))").should == true
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'translates numbers' do
|
75
|
+
|
76
|
+
translate("0").should == 0
|
77
|
+
translate("101").should == 101
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'translates parentheses' do
|
81
|
+
|
82
|
+
translate("(12)").should == 12
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'translates operations' do
|
86
|
+
|
87
|
+
translate("1+2+3").should == [ 1, '+', 2, '+', 3 ]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|