neg 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +12 -0
- data/Rakefile +53 -0
- data/TODO.txt +17 -0
- data/lib/neg.rb +3 -0
- data/lib/neg/input.rb +89 -0
- data/lib/neg/parser.rb +318 -0
- data/lib/neg/version.rb +30 -0
- data/neg.gemspec +33 -0
- data/spec/input_spec.rb +75 -0
- data/spec/parser_alternative_spec.rb +48 -0
- data/spec/parser_character_spec.rb +72 -0
- data/spec/parser_non_terminal_spec.rb +85 -0
- data/spec/parser_repetition_spec.rb +142 -0
- data/spec/parser_sequence_spec.rb +65 -0
- data/spec/parser_spec.rb +48 -0
- data/spec/parser_string_spec.rb +41 -0
- data/spec/sample_json_parser_spec.rb +83 -0
- data/spec/spec_helper.rb +20 -0
- metadata +97 -0
data/lib/neg/version.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2012-2012, 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
|
+
VERSION = '0.2.0'
|
29
|
+
end
|
30
|
+
|
data/neg.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
Gem::Specification.new do |s|
|
3
|
+
|
4
|
+
s.name = 'neg'
|
5
|
+
|
6
|
+
s.version = File.read(
|
7
|
+
File.expand_path('../lib/neg/version.rb', __FILE__)
|
8
|
+
).match(/ VERSION *= *['"]([^'"]+)/)[1]
|
9
|
+
|
10
|
+
s.platform = Gem::Platform::RUBY
|
11
|
+
s.authors = [ 'John Mettraux' ]
|
12
|
+
s.email = [ 'jmettraux@gmail.com' ]
|
13
|
+
s.homepage = 'https://github.com/jmettraux/leg'
|
14
|
+
s.rubyforge_project = 'ruote'
|
15
|
+
s.summary = 'a neg narser'
|
16
|
+
|
17
|
+
s.description = %{
|
18
|
+
not a peg parser, just a neg narser
|
19
|
+
}.strip
|
20
|
+
|
21
|
+
#s.files = `git ls-files`.split("\n")
|
22
|
+
s.files = Dir[
|
23
|
+
'Rakefile',
|
24
|
+
'lib/**/*.rb', 'spec/**/*.rb', 'test/**/*.rb',
|
25
|
+
'*.gemspec', '*.txt', '*.rdoc', '*.md'
|
26
|
+
]
|
27
|
+
|
28
|
+
s.add_development_dependency 'rake'
|
29
|
+
s.add_development_dependency 'rspec', '>= 2.9.0'
|
30
|
+
|
31
|
+
s.require_path = 'lib'
|
32
|
+
end
|
33
|
+
|
data/spec/input_spec.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
|
5
|
+
describe Neg::Input do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
|
9
|
+
@input = Neg::Input.new("the quick blue fox\n jumped the shark\n")
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'starts at zero' do
|
13
|
+
|
14
|
+
@input.position.should == [ 0, 1, 1 ]
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#read' do
|
18
|
+
|
19
|
+
it "reads and moves" do
|
20
|
+
|
21
|
+
@input.read(5).should == 'the q'
|
22
|
+
|
23
|
+
@input.position.should == [ 5, 1, 6 ]
|
24
|
+
end
|
25
|
+
|
26
|
+
it "reads and moves (same line)" do
|
27
|
+
|
28
|
+
@input.read(9).should == 'the quick'
|
29
|
+
|
30
|
+
@input.position.should == [ 9, 1, 10 ]
|
31
|
+
end
|
32
|
+
|
33
|
+
it "reads and moves (new line)" do
|
34
|
+
|
35
|
+
@input.read(21).should == "the quick blue fox\n j"
|
36
|
+
|
37
|
+
@input.position.should == [ 21, 2, 2 ]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#rewind' do
|
42
|
+
|
43
|
+
it 'rewinds' do
|
44
|
+
|
45
|
+
@input.read(21)
|
46
|
+
@input.rewind
|
47
|
+
|
48
|
+
@input.position.should == [ 0, 1, 1 ]
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'rewinds with a [ off, line, col ]' do
|
52
|
+
|
53
|
+
@input.read(21)
|
54
|
+
@input.rewind([ 5, 1, 6 ])
|
55
|
+
|
56
|
+
@input.read(4).should == 'uick'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#eoi?' do
|
61
|
+
|
62
|
+
it 'returns false if the end of input has not yet been reached' do
|
63
|
+
|
64
|
+
@input.eoi?.should == false
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'returns true if the end of input has been reached' do
|
68
|
+
|
69
|
+
@input.read(37)
|
70
|
+
|
71
|
+
@input.eoi?.should == true
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
|
5
|
+
describe Neg::Parser::AlternativeParser do
|
6
|
+
|
7
|
+
class AltParser < Neg::Parser
|
8
|
+
text == `x` | `y`
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'parses' do
|
12
|
+
|
13
|
+
AltParser.parse('x').should ==
|
14
|
+
[ :text, true, [ 0, 1, 1 ], [
|
15
|
+
[ nil, true, [ 0, 1, 1 ], 'x' ] ] ]
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'parses (2nd alternative succeeds)' do
|
19
|
+
|
20
|
+
AltParser.parse('y').should ==
|
21
|
+
[ :text, true, [ 0, 1, 1 ], [
|
22
|
+
[ nil, false, [ 0, 1, 1 ], 'expected "x", got "y"' ],
|
23
|
+
[ nil, true, [ 0, 1, 1 ], 'y' ] ] ]
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'fails gracefully' do
|
27
|
+
|
28
|
+
AltParser.parse('z').should ==
|
29
|
+
[ :text, false, [ 0, 1, 1 ], [
|
30
|
+
[ nil, false, [ 0, 1, 1 ], 'expected "x", got "z"' ],
|
31
|
+
[ nil, false, [ 0, 1, 1 ], 'expected "y", got "z"' ] ] ]
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'goes beyond two elements' do
|
35
|
+
|
36
|
+
parser = Class.new(Neg::Parser) do
|
37
|
+
text == `x` | `y` | `z`
|
38
|
+
end
|
39
|
+
|
40
|
+
text = parser.text
|
41
|
+
|
42
|
+
text.class.should ==
|
43
|
+
Neg::Parser::NonTerminalParser
|
44
|
+
text.child.children.collect(&:class).should ==
|
45
|
+
[ Neg::Parser::StringParser ] * 3
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
@@ -0,0 +1,72 @@
|
|
1
|
+
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
|
5
|
+
describe Neg::Parser::CharacterParser do
|
6
|
+
|
7
|
+
context '_ (any)' do
|
8
|
+
|
9
|
+
let(:parser) {
|
10
|
+
Class.new(Neg::Parser) do
|
11
|
+
text == `x` + _
|
12
|
+
end
|
13
|
+
}
|
14
|
+
|
15
|
+
it 'parses "xy"' do
|
16
|
+
|
17
|
+
parser.parse('xy').should ==
|
18
|
+
[ :text,
|
19
|
+
true,
|
20
|
+
[ 0, 1, 1 ],
|
21
|
+
[ [ nil, true, [ 0, 1, 1 ], "x" ],
|
22
|
+
[ nil, true, [ 1, 1, 2 ], "y" ] ] ]
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'fails gracefully' do
|
26
|
+
|
27
|
+
parser.parse('x').should ==
|
28
|
+
[ :text,
|
29
|
+
false,
|
30
|
+
[ 0, 1, 1],
|
31
|
+
[ [ nil, true, [ 0, 1, 1 ], "x" ],
|
32
|
+
[ nil, false, [ 1, 1, 2 ], "\"\" doesn't match nil" ] ] ]
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'is rendered correctly via #to_s' do
|
36
|
+
|
37
|
+
parser.to_s.strip.should == %q{
|
38
|
+
:
|
39
|
+
text == (`x` + _)
|
40
|
+
root: text
|
41
|
+
}.strip
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "_('0-9-') (ranges)" do
|
46
|
+
|
47
|
+
let(:parser) {
|
48
|
+
Class.new(Neg::Parser) do
|
49
|
+
text == `tel:` + _('0-9-') * 1
|
50
|
+
end
|
51
|
+
}
|
52
|
+
|
53
|
+
it 'parses "tel:0-99"' do
|
54
|
+
|
55
|
+
parser.parse('tel:0-99').should ==
|
56
|
+
[ :text,
|
57
|
+
true,
|
58
|
+
[ 0, 1, 1 ],
|
59
|
+
[ [ nil, true, [ 0, 1, 1 ], "tel:" ],
|
60
|
+
[ nil,
|
61
|
+
true,
|
62
|
+
[ 4, 1, 5 ],
|
63
|
+
[ [ nil, true, [ 4, 1, 5 ], "0" ],
|
64
|
+
[ nil, true, [ 5, 1, 6 ], "-" ],
|
65
|
+
[ nil, true, [ 6, 1, 7 ], "9" ],
|
66
|
+
[ nil, true, [ 7, 1, 8 ], "9" ] ] ] ] ]
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'fails gracefully'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
@@ -0,0 +1,85 @@
|
|
1
|
+
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
|
5
|
+
describe Neg::Parser::NonTerminalParser do
|
6
|
+
|
7
|
+
context 'name == ...' do
|
8
|
+
|
9
|
+
it 'parses' do
|
10
|
+
|
11
|
+
parser = Class.new(Neg::Parser) do
|
12
|
+
text == x | z
|
13
|
+
x == `x`
|
14
|
+
z == `zz` | `z`
|
15
|
+
end
|
16
|
+
|
17
|
+
parser.parse('x')[1].should == true
|
18
|
+
parser.parse('z')[1].should == true
|
19
|
+
parser.parse('zz')[1].should == true
|
20
|
+
parser.parse('y')[1].should == false
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'sets its name in the result (as a Symbol)' do
|
24
|
+
|
25
|
+
parser = Class.new(Neg::Parser) do
|
26
|
+
x == `x` | `X` | `xx`
|
27
|
+
end
|
28
|
+
|
29
|
+
parser.parse('X').should ==
|
30
|
+
[ :x, true, [ 0, 1, 1 ], [
|
31
|
+
[ nil, false, [ 0, 1, 1 ], "expected \"x\", got \"X\"" ],
|
32
|
+
[ nil, true, [ 0, 1, 1 ], "X" ] ] ]
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'is rendered as x when on the right side' do
|
36
|
+
|
37
|
+
parser = Class.new(Neg::Parser) do
|
38
|
+
word == car | bus
|
39
|
+
car == `car`
|
40
|
+
bus == `bus`
|
41
|
+
end
|
42
|
+
|
43
|
+
parser.to_s.strip.should == %q{
|
44
|
+
:
|
45
|
+
bus == `bus`
|
46
|
+
car == `car`
|
47
|
+
word == (car | bus)
|
48
|
+
root: word
|
49
|
+
}.strip
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context '...["name"]' do
|
54
|
+
|
55
|
+
let(:parser) {
|
56
|
+
Class.new(Neg::Parser) do
|
57
|
+
transportation ==
|
58
|
+
(`car` | `bus`)['vehicle'] +
|
59
|
+
`_` +
|
60
|
+
(`cluj` | `split`)['city']
|
61
|
+
end
|
62
|
+
}
|
63
|
+
|
64
|
+
it 'is rendered as []' do
|
65
|
+
|
66
|
+
parser.to_s.strip.should == %q{
|
67
|
+
:
|
68
|
+
transportation == ((`car` | `bus`)["vehicle"] + `_` + (`cluj` | `split`)["city"])
|
69
|
+
root: transportation
|
70
|
+
}.strip
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'sets the name (as a string) in the result' do
|
74
|
+
|
75
|
+
parser.parse('car_cluj').should ==
|
76
|
+
[ :transportation,
|
77
|
+
true,
|
78
|
+
[ 0, 1, 1],
|
79
|
+
[ [ 'vehicle', true, [ 0, 1, 1 ], [ [ nil, true, [ 0, 1, 1 ], 'car' ] ] ],
|
80
|
+
[ nil, true, [ 3, 1, 4 ], '_'],
|
81
|
+
[ 'city', true, [ 4, 1, 5 ], [ [ nil, true, [ 4, 1, 5 ], 'cluj' ] ] ] ] ]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
@@ -0,0 +1,142 @@
|
|
1
|
+
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
|
5
|
+
describe Neg::Parser::RepetitionParser do
|
6
|
+
|
7
|
+
context '`x` * -1 (maybe)' do
|
8
|
+
|
9
|
+
let(:parser) {
|
10
|
+
Class.new(Neg::Parser) do
|
11
|
+
text == `x` * -1
|
12
|
+
end
|
13
|
+
}
|
14
|
+
|
15
|
+
it 'parses the empty string' do
|
16
|
+
|
17
|
+
parser.parse('').should ==
|
18
|
+
[ :text, true, [ 0, 1, 1 ], [] ]
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'fails gracefully' do
|
22
|
+
|
23
|
+
lambda {
|
24
|
+
parser.parse('xx')
|
25
|
+
}.should raise_error(
|
26
|
+
Neg::UnconsumedInputError,
|
27
|
+
'remaining: "x"')
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'is rendered correctly via #to_s' do
|
31
|
+
|
32
|
+
parser.to_s.strip.should == %q{
|
33
|
+
:
|
34
|
+
text == `x` * -1
|
35
|
+
root: text
|
36
|
+
}.strip
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context '`x` * 0 (0 or more)' do
|
41
|
+
|
42
|
+
let(:parser) {
|
43
|
+
Class.new(Neg::Parser) do
|
44
|
+
text == `x` * 0
|
45
|
+
end
|
46
|
+
}
|
47
|
+
|
48
|
+
it 'parses the empty string' do
|
49
|
+
|
50
|
+
parser.parse('').should ==
|
51
|
+
[ :text, true, [ 0, 1, 1 ], [] ]
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'parses' do
|
55
|
+
|
56
|
+
parser.parse('xxx').should ==
|
57
|
+
[ :text, true, [ 0, 1, 1 ], [
|
58
|
+
[ nil, true, [ 0, 1, 1 ], 'x' ],
|
59
|
+
[ nil, true, [ 1, 1, 2 ], 'x' ],
|
60
|
+
[ nil, true, [ 2, 1, 3 ], 'x' ] ] ]
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'fails gracefully' do
|
64
|
+
|
65
|
+
lambda {
|
66
|
+
parser.parse('a')
|
67
|
+
}.should raise_error(
|
68
|
+
Neg::UnconsumedInputError,
|
69
|
+
'remaining: "a"')
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context '`x` * 2 (at least 2)' do
|
74
|
+
|
75
|
+
let(:parser) {
|
76
|
+
Class.new(Neg::Parser) do
|
77
|
+
text == `x` * 2
|
78
|
+
end
|
79
|
+
}
|
80
|
+
|
81
|
+
it 'parses' do
|
82
|
+
|
83
|
+
parser.parse('xxx').should ==
|
84
|
+
[ :text, true, [ 0, 1, 1 ], [
|
85
|
+
[ nil, true, [ 0, 1, 1 ], 'x' ],
|
86
|
+
[ nil, true, [ 1, 1, 2 ], 'x' ],
|
87
|
+
[ nil, true, [ 2, 1, 3 ], 'x' ] ] ]
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'fails gracefully' do
|
91
|
+
|
92
|
+
parser.parse('x').should ==
|
93
|
+
[ :text, false, [ 0, 1, 1 ], [
|
94
|
+
[ nil, true, [ 0, 1, 1 ], 'x' ],
|
95
|
+
[ nil, false, [ 1, 1, 2 ], 'expected "x", got ""' ] ] ]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context '`x` * [ 3, 3 ] (at least 3, max 3)' do
|
100
|
+
|
101
|
+
let(:parser) {
|
102
|
+
Class.new(Neg::Parser) do
|
103
|
+
text == `x` * [ 3, 3 ]
|
104
|
+
end
|
105
|
+
}
|
106
|
+
|
107
|
+
it 'parses' do
|
108
|
+
|
109
|
+
parser.parse('xxx').should ==
|
110
|
+
[ :text, true, [ 0, 1, 1 ], [
|
111
|
+
[ nil, true, [ 0, 1, 1 ], 'x' ],
|
112
|
+
[ nil, true, [ 1, 1, 2 ], 'x' ],
|
113
|
+
[ nil, true, [ 2, 1, 3 ], 'x' ] ] ]
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'fails gracefully' do
|
117
|
+
|
118
|
+
parser.parse('xx').should ==
|
119
|
+
[ :text, false, [ 0, 1, 1 ], [
|
120
|
+
[ nil, true, [ 0, 1, 1 ], 'x' ],
|
121
|
+
[ nil, true, [ 1, 1, 2 ], 'x' ],
|
122
|
+
[ nil, false, [ 2, 1, 3 ], 'expected "x", got ""' ] ] ]
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'fails gracefully (unconsumed input)' do
|
126
|
+
|
127
|
+
lambda {
|
128
|
+
parser.parse('xxxx')
|
129
|
+
}.should raise_error(Neg::UnconsumedInputError, 'remaining: "x"')
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'is rendered correctly via #to_s' do
|
133
|
+
|
134
|
+
parser.to_s.strip.should == %q{
|
135
|
+
:
|
136
|
+
text == `x` * [3, 3]
|
137
|
+
root: text
|
138
|
+
}.strip
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|