rley 0.7.06 → 0.8.01
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +362 -62
- data/.travis.yml +6 -6
- data/CHANGELOG.md +20 -4
- data/LICENSE.txt +1 -1
- data/README.md +7 -7
- data/examples/NLP/engtagger.rb +193 -190
- data/examples/NLP/nano_eng/nano_en_demo.rb +7 -11
- data/examples/NLP/nano_eng/nano_grammar.rb +21 -21
- data/examples/NLP/pico_en_demo.rb +2 -2
- data/examples/data_formats/JSON/cli_options.rb +1 -1
- data/examples/data_formats/JSON/json_ast_builder.rb +21 -27
- data/examples/data_formats/JSON/json_ast_nodes.rb +12 -21
- data/examples/data_formats/JSON/json_demo.rb +1 -2
- data/examples/data_formats/JSON/json_grammar.rb +13 -13
- data/examples/data_formats/JSON/json_lexer.rb +8 -8
- data/examples/data_formats/JSON/json_minifier.rb +1 -1
- data/examples/general/calc_iter1/calc_ast_builder.rb +13 -10
- data/examples/general/calc_iter1/calc_ast_nodes.rb +23 -37
- data/examples/general/calc_iter1/calc_grammar.rb +7 -6
- data/examples/general/calc_iter1/calc_lexer.rb +6 -4
- data/examples/general/calc_iter1/spec/calculator_spec.rb +5 -5
- data/examples/general/calc_iter2/calc_ast_builder.rb +5 -3
- data/examples/general/calc_iter2/calc_ast_nodes.rb +27 -43
- data/examples/general/calc_iter2/calc_grammar.rb +12 -12
- data/examples/general/calc_iter2/calc_lexer.rb +11 -10
- data/examples/general/calc_iter2/spec/calculator_spec.rb +26 -26
- data/examples/general/left.rb +2 -2
- data/examples/general/right.rb +2 -2
- data/lib/rley.rb +1 -1
- data/lib/rley/base/dotted_item.rb +28 -31
- data/lib/rley/base/grm_items_builder.rb +6 -0
- data/lib/rley/constants.rb +2 -2
- data/lib/rley/engine.rb +22 -25
- data/lib/rley/formatter/asciitree.rb +3 -3
- data/lib/rley/formatter/bracket_notation.rb +1 -8
- data/lib/rley/formatter/debug.rb +6 -6
- data/lib/rley/formatter/json.rb +2 -2
- data/lib/rley/gfg/call_edge.rb +1 -1
- data/lib/rley/gfg/edge.rb +5 -5
- data/lib/rley/gfg/end_vertex.rb +2 -6
- data/lib/rley/gfg/epsilon_edge.rb +1 -5
- data/lib/rley/gfg/grm_flow_graph.rb +27 -23
- data/lib/rley/gfg/item_vertex.rb +10 -10
- data/lib/rley/gfg/non_terminal_vertex.rb +4 -4
- data/lib/rley/gfg/scan_edge.rb +1 -1
- data/lib/rley/gfg/shortcut_edge.rb +2 -2
- data/lib/rley/gfg/start_vertex.rb +4 -8
- data/lib/rley/gfg/vertex.rb +43 -39
- data/lib/rley/interface.rb +16 -0
- data/lib/rley/lexical/token_range.rb +6 -6
- data/lib/rley/notation/all_notation_nodes.rb +2 -0
- data/lib/rley/notation/ast_builder.rb +191 -0
- data/lib/rley/notation/ast_node.rb +44 -0
- data/lib/rley/notation/ast_visitor.rb +113 -0
- data/lib/rley/notation/grammar.rb +49 -0
- data/lib/rley/notation/grammar_builder.rb +504 -0
- data/lib/rley/notation/grouping_node.rb +23 -0
- data/lib/rley/notation/parser.rb +56 -0
- data/lib/rley/notation/sequence_node.rb +35 -0
- data/lib/rley/notation/symbol_node.rb +29 -0
- data/lib/rley/notation/tokenizer.rb +192 -0
- data/lib/rley/parse_forest_visitor.rb +5 -5
- data/lib/rley/parse_rep/ast_base_builder.rb +48 -11
- data/lib/rley/parse_rep/cst_builder.rb +5 -6
- data/lib/rley/parse_rep/parse_forest_builder.rb +22 -18
- data/lib/rley/parse_rep/parse_forest_factory.rb +3 -3
- data/lib/rley/parse_rep/parse_rep_creator.rb +14 -16
- data/lib/rley/parse_rep/parse_tree_builder.rb +4 -4
- data/lib/rley/parse_rep/parse_tree_factory.rb +27 -27
- data/lib/rley/parse_tree_visitor.rb +1 -1
- data/lib/rley/parser/error_reason.rb +4 -5
- data/lib/rley/parser/gfg_chart.rb +118 -26
- data/lib/rley/parser/gfg_parsing.rb +22 -33
- data/lib/rley/parser/parse_entry.rb +25 -31
- data/lib/rley/parser/parse_entry_set.rb +19 -16
- data/lib/rley/parser/parse_entry_tracker.rb +4 -4
- data/lib/rley/parser/parse_tracer.rb +13 -13
- data/lib/rley/parser/parse_walker_factory.rb +23 -28
- data/lib/rley/ptree/non_terminal_node.rb +7 -5
- data/lib/rley/ptree/parse_tree.rb +3 -3
- data/lib/rley/ptree/parse_tree_node.rb +5 -5
- data/lib/rley/ptree/terminal_node.rb +7 -7
- data/lib/rley/rley_error.rb +12 -12
- data/lib/rley/sppf/alternative_node.rb +6 -6
- data/lib/rley/sppf/composite_node.rb +7 -7
- data/lib/rley/sppf/epsilon_node.rb +3 -3
- data/lib/rley/sppf/leaf_node.rb +3 -3
- data/lib/rley/sppf/parse_forest.rb +16 -16
- data/lib/rley/sppf/sppf_node.rb +7 -8
- data/lib/rley/sppf/token_node.rb +3 -3
- data/lib/rley/syntax/{grammar_builder.rb → base_grammar_builder.rb} +61 -23
- data/lib/rley/syntax/grammar.rb +5 -5
- data/lib/rley/syntax/grm_symbol.rb +7 -7
- data/lib/rley/syntax/match_closest.rb +43 -0
- data/lib/rley/syntax/non_terminal.rb +9 -15
- data/lib/rley/syntax/production.rb +16 -10
- data/lib/rley/syntax/symbol_seq.rb +7 -9
- data/lib/rley/syntax/terminal.rb +4 -5
- data/lib/rley/syntax/verbatim_symbol.rb +3 -3
- data/lib/support/base_tokenizer.rb +19 -18
- data/spec/rley/base/dotted_item_spec.rb +2 -2
- data/spec/rley/engine_spec.rb +23 -21
- data/spec/rley/formatter/asciitree_spec.rb +7 -7
- data/spec/rley/formatter/bracket_notation_spec.rb +13 -13
- data/spec/rley/formatter/json_spec.rb +1 -1
- data/spec/rley/gfg/end_vertex_spec.rb +5 -5
- data/spec/rley/gfg/grm_flow_graph_spec.rb +2 -2
- data/spec/rley/gfg/item_vertex_spec.rb +10 -10
- data/spec/rley/gfg/non_terminal_vertex_spec.rb +3 -3
- data/spec/rley/gfg/shortcut_edge_spec.rb +1 -1
- data/spec/rley/gfg/start_vertex_spec.rb +5 -5
- data/spec/rley/gfg/vertex_spec.rb +3 -3
- data/spec/rley/lexical/token_range_spec.rb +16 -16
- data/spec/rley/lexical/token_spec.rb +2 -2
- data/spec/rley/notation/grammar_builder_spec.rb +302 -0
- data/spec/rley/notation/parser_spec.rb +184 -0
- data/spec/rley/notation/tokenizer_spec.rb +370 -0
- data/spec/rley/parse_forest_visitor_spec.rb +165 -163
- data/spec/rley/parse_rep/ambiguous_parse_spec.rb +44 -44
- data/spec/rley/parse_rep/ast_builder_spec.rb +6 -7
- data/spec/rley/parse_rep/cst_builder_spec.rb +5 -5
- data/spec/rley/parse_rep/groucho_spec.rb +24 -26
- data/spec/rley/parse_rep/parse_forest_builder_spec.rb +27 -27
- data/spec/rley/parse_rep/parse_forest_factory_spec.rb +8 -8
- data/spec/rley/parse_rep/parse_tree_factory_spec.rb +3 -3
- data/spec/rley/parse_tree_visitor_spec.rb +10 -8
- data/spec/rley/parser/dangling_else_spec.rb +445 -0
- data/spec/rley/parser/error_reason_spec.rb +6 -6
- data/spec/rley/parser/gfg_earley_parser_spec.rb +120 -12
- data/spec/rley/parser/gfg_parsing_spec.rb +6 -13
- data/spec/rley/parser/parse_entry_spec.rb +19 -19
- data/spec/rley/parser/parse_walker_factory_spec.rb +10 -10
- data/spec/rley/ptree/non_terminal_node_spec.rb +5 -3
- data/spec/rley/ptree/parse_tree_node_spec.rb +4 -4
- data/spec/rley/ptree/terminal_node_spec.rb +6 -6
- data/spec/rley/sppf/alternative_node_spec.rb +6 -6
- data/spec/rley/sppf/non_terminal_node_spec.rb +3 -3
- data/spec/rley/sppf/token_node_spec.rb +4 -4
- data/spec/rley/support/ambiguous_grammar_helper.rb +4 -5
- data/spec/rley/support/grammar_abc_helper.rb +3 -5
- data/spec/rley/support/grammar_ambig01_helper.rb +5 -6
- data/spec/rley/support/grammar_arr_int_helper.rb +5 -6
- data/spec/rley/support/grammar_b_expr_helper.rb +5 -6
- data/spec/rley/support/grammar_int_seq_helper.rb +51 -0
- data/spec/rley/support/grammar_l0_helper.rb +14 -17
- data/spec/rley/support/grammar_pb_helper.rb +8 -7
- data/spec/rley/support/grammar_sppf_helper.rb +3 -3
- data/spec/rley/syntax/{grammar_builder_spec.rb → base_grammar_builder_spec.rb} +35 -16
- data/spec/rley/syntax/grammar_spec.rb +6 -6
- data/spec/rley/syntax/grm_symbol_spec.rb +1 -1
- data/spec/rley/syntax/match_closest_spec.rb +46 -0
- data/spec/rley/syntax/non_terminal_spec.rb +8 -8
- data/spec/rley/syntax/production_spec.rb +17 -13
- data/spec/rley/syntax/symbol_seq_spec.rb +2 -2
- data/spec/rley/syntax/terminal_spec.rb +5 -5
- data/spec/rley/syntax/verbatim_symbol_spec.rb +1 -1
- data/spec/spec_helper.rb +0 -12
- data/spec/support/base_tokenizer_spec.rb +7 -2
- metadata +48 -74
- data/.simplecov +0 -7
- data/lib/rley/parser/parse_state.rb +0 -83
- data/lib/rley/parser/parse_state_tracker.rb +0 -59
- data/lib/rley/parser/state_set.rb +0 -101
- data/spec/rley/parser/parse_state_spec.rb +0 -125
- data/spec/rley/parser/parse_tracer_spec.rb +0 -200
- data/spec/rley/parser/state_set_spec.rb +0 -130
@@ -23,12 +23,12 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
23
23
|
end
|
24
24
|
end # context
|
25
25
|
|
26
|
-
context 'Provided services:' do
|
26
|
+
context 'Provided services:' do
|
27
27
|
it 'should provide human-readable representation of itself' do
|
28
|
-
pattern = /^#<Rley::GFG::EndVertex:\d+ label="NT\."/
|
29
|
-
expect(subject.inspect).to match(pattern)
|
30
|
-
end
|
31
|
-
end # context
|
28
|
+
pattern = /^#<Rley::GFG::EndVertex:\d+ label="NT\."/
|
29
|
+
expect(subject.inspect).to match(pattern)
|
30
|
+
end
|
31
|
+
end # context
|
32
32
|
end # describe
|
33
33
|
end # module
|
34
34
|
end # module
|
@@ -112,7 +112,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
112
112
|
end
|
113
113
|
|
114
114
|
it 'should handle empty productions' do
|
115
|
-
builder = Rley::Syntax::
|
115
|
+
builder = Rley::Syntax::BaseGrammarBuilder.new
|
116
116
|
builder.add_terminals('a')
|
117
117
|
builder.add_production('S' => 'A')
|
118
118
|
builder.add_production('A' => 'a')
|
@@ -162,7 +162,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
162
162
|
let(:problematic_grammar) do
|
163
163
|
# Based on grammar example in book
|
164
164
|
# C. Fisher, R. LeBlanc, "Crafting a Compiler"; page 98
|
165
|
-
builder = Rley::Syntax::
|
165
|
+
builder = Rley::Syntax::BaseGrammarBuilder.new
|
166
166
|
builder.add_terminals('a', 'b', 'c')
|
167
167
|
builder.add_production('S' => 'A')
|
168
168
|
builder.add_production('S' => 'B')
|
@@ -73,10 +73,10 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
73
73
|
# Case: dot is after first symbol
|
74
74
|
instance1 = ItemVertex.new(sample_item)
|
75
75
|
expect(instance1.prev_symbol).to eq(t_a)
|
76
|
-
|
76
|
+
|
77
77
|
# Case: dot is after second or later symbol
|
78
78
|
instance2 = ItemVertex.new(next_item)
|
79
|
-
expect(instance2.prev_symbol).to eq(nt_b_sequence)
|
79
|
+
expect(instance2.prev_symbol).to eq(nt_b_sequence)
|
80
80
|
|
81
81
|
# Case: dot is at begin
|
82
82
|
instance3 = ItemVertex.new(Base::DottedItem.new(sample_prod, 0))
|
@@ -86,8 +86,8 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
86
86
|
instance4 = ItemVertex.new(Base::DottedItem.new(empty_prod, 0))
|
87
87
|
expect(instance4.prev_symbol).to be_nil
|
88
88
|
end
|
89
|
-
|
90
|
-
|
89
|
+
|
90
|
+
|
91
91
|
it 'should know the next symbol (if any) in the rhs' do
|
92
92
|
# Case: dot is not penultimate
|
93
93
|
expect(subject.next_symbol).to eq(nt_b_sequence)
|
@@ -112,21 +112,21 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
112
112
|
shortcut = ShortcutEdge.new(subject, next_vertex)
|
113
113
|
expect(subject.shortcut).to eq(shortcut)
|
114
114
|
end
|
115
|
-
|
115
|
+
|
116
116
|
it 'should reject an invalid shortcut edge' do
|
117
117
|
err = StandardError
|
118
118
|
err_msg = 'Invalid shortcut argument'
|
119
119
|
expect { subject.shortcut = 'invalid' }.to raise_error(err, err_msg)
|
120
|
-
end
|
120
|
+
end
|
121
121
|
end # context
|
122
122
|
|
123
123
|
context 'Provided services:' do
|
124
124
|
it 'should provide human-readable representation of itself' do
|
125
|
-
prefix = /^#<Rley::GFG::ItemVertex:\d+/
|
125
|
+
prefix = /^#<Rley::GFG::ItemVertex:\d+/
|
126
126
|
expect(subject.inspect).to match(prefix)
|
127
|
-
suffix = /label="sentence => a \. b_sequence c">$/
|
128
|
-
expect(subject.inspect).to match(suffix)
|
129
|
-
end
|
127
|
+
suffix = /label="sentence => a \. b_sequence c">$/
|
128
|
+
expect(subject.inspect).to match(suffix)
|
129
|
+
end
|
130
130
|
end # context
|
131
131
|
end # describe
|
132
132
|
end # module
|
@@ -19,7 +19,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
19
19
|
it 'should know its non-terminal' do
|
20
20
|
expect(subject.non_terminal).to eq(sample_nt)
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
|
24
24
|
it 'should accept at more than one outgoing edge' do
|
25
25
|
edge1 = double('fake-edge1')
|
@@ -28,11 +28,11 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
28
28
|
expect { subject.add_edge(edge1) }.not_to raise_error
|
29
29
|
expect(subject.edges.size).to eq(1)
|
30
30
|
expect(subject.edges.last).to eq(edge1)
|
31
|
-
|
31
|
+
|
32
32
|
expect { subject.add_edge(edge2) }.not_to raise_error
|
33
33
|
expect(subject.edges.size).to eq(2)
|
34
34
|
expect(subject.edges.last).to eq(edge2)
|
35
|
-
end
|
35
|
+
end
|
36
36
|
end # context
|
37
37
|
end # describe
|
38
38
|
end # module
|
@@ -27,7 +27,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
27
27
|
it 'should know the successor vertex' do
|
28
28
|
expect(vertex1).to receive(:shortcut=)
|
29
29
|
expect(vertex1).to receive(:next_symbol).and_return(nt_b_sequence)
|
30
|
-
|
30
|
+
|
31
31
|
expect(subject.successor).to eq(vertex2)
|
32
32
|
end
|
33
33
|
|
@@ -22,12 +22,12 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
22
22
|
expect(subject.label).to eq('.NT')
|
23
23
|
end
|
24
24
|
end # context
|
25
|
-
|
26
|
-
context 'Provided services:' do
|
25
|
+
|
26
|
+
context 'Provided services:' do
|
27
27
|
it 'should provide human-readable representation of itself' do
|
28
|
-
pattern = /^#<Rley::GFG::StartVertex:\d+ label="\.NT"/
|
29
|
-
expect(subject.inspect).to match(pattern)
|
30
|
-
end
|
28
|
+
pattern = /^#<Rley::GFG::StartVertex:\d+ label="\.NT"/
|
29
|
+
expect(subject.inspect).to match(pattern)
|
30
|
+
end
|
31
31
|
end # context
|
32
32
|
end # describe
|
33
33
|
end # module
|
@@ -24,10 +24,10 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
24
24
|
it 'should know whether it has a dot at the end of the rhs' do
|
25
25
|
expect(subject).not_to be_complete
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
it 'should know the previous symbol (if any) in the rhs' do
|
29
29
|
expect(subject.prev_symbol).to be_nil
|
30
|
-
end
|
30
|
+
end
|
31
31
|
|
32
32
|
it 'should know the next symbol (if any) in the rhs' do
|
33
33
|
expect(subject.next_symbol).to be_nil
|
@@ -40,7 +40,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
40
40
|
expect { subject.add_edge(edge1) }.not_to raise_error
|
41
41
|
expect(subject.edges.size).to eq(1)
|
42
42
|
expect(subject.edges.last).to eq(edge1)
|
43
|
-
|
43
|
+
|
44
44
|
err = StandardError
|
45
45
|
msg = 'At most one edge accepted'
|
46
46
|
expect { subject.add_edge(edge2) }.to raise_error err, msg
|
@@ -27,7 +27,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
27
27
|
# Both bounds provided
|
28
28
|
expect { TokenRange.new(low: 0, high: 5) }.not_to raise_error
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
it 'could be created with another TokenRange' do
|
32
32
|
# Low bound provided
|
33
33
|
instance = TokenRange.new(low: 0)
|
@@ -61,7 +61,7 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
61
61
|
expect(subject == [0, 5]).to eq(true)
|
62
62
|
end
|
63
63
|
|
64
|
-
|
64
|
+
|
65
65
|
it 'should know whether it is bounded or not' do
|
66
66
|
expect(subject).to be_bounded
|
67
67
|
|
@@ -119,50 +119,50 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
119
119
|
expect(instance.low).to eq(1)
|
120
120
|
expect(instance.high).to eq(4)
|
121
121
|
end
|
122
|
-
|
122
|
+
|
123
123
|
it 'should tell whether an index value lies outside the range' do
|
124
124
|
# Out of range...
|
125
125
|
expect(subject.out_of_range?(-1)).to eq(true)
|
126
126
|
expect(subject.out_of_range?(6)).to eq(true)
|
127
|
-
|
127
|
+
|
128
128
|
# On boundaries...
|
129
129
|
expect(subject.out_of_range?(0)).to eq(false)
|
130
130
|
expect(subject.out_of_range?(5)).to eq(false)
|
131
|
-
|
131
|
+
|
132
132
|
# Inside boundaries
|
133
133
|
expect(subject.out_of_range?(2)).to eq(false)
|
134
134
|
|
135
135
|
instance = TokenRange.new(low: nil, high: 5)
|
136
|
-
|
136
|
+
|
137
137
|
# Lower bound is nil
|
138
138
|
expect(instance.out_of_range?(-1)).to eq(false)
|
139
139
|
expect(instance.out_of_range?(5)).to eq(false)
|
140
140
|
expect(instance.out_of_range?(6)).to eq(true)
|
141
|
-
|
141
|
+
|
142
142
|
instance = TokenRange.new(low: 0, high: nil)
|
143
|
-
|
143
|
+
|
144
144
|
# Upper bound is nil
|
145
145
|
expect(instance.out_of_range?(-1)).to eq(true)
|
146
146
|
expect(instance.out_of_range?(0)).to eq(false)
|
147
147
|
expect(instance.out_of_range?(6)).to eq(false)
|
148
148
|
end
|
149
|
-
|
149
|
+
|
150
150
|
it 'should provide a text representation of itself' do
|
151
151
|
# Case 1: not bound is set
|
152
152
|
instance = TokenRange.new({})
|
153
|
-
expect(instance.to_string(0)).to eq('[?, ?]')
|
154
|
-
|
153
|
+
expect(instance.to_string(0)).to eq('[?, ?]')
|
154
|
+
|
155
155
|
# Case: only low bound is set
|
156
156
|
instance = TokenRange.new(low: 0)
|
157
|
-
expect(instance.to_string(0)).to eq('[0, ?]')
|
157
|
+
expect(instance.to_string(0)).to eq('[0, ?]')
|
158
158
|
|
159
159
|
# Case: only upper bound is set
|
160
|
-
instance = TokenRange.new(high: 5)
|
161
|
-
expect(instance.to_string(0)).to eq('[?, 5]')
|
160
|
+
instance = TokenRange.new(high: 5)
|
161
|
+
expect(instance.to_string(0)).to eq('[?, 5]')
|
162
162
|
|
163
163
|
# Case: both bounds are set
|
164
|
-
instance = TokenRange.new(low: 0, high: 5)
|
165
|
-
expect(instance.to_string(0)).to eq('[0, 5]')
|
164
|
+
instance = TokenRange.new(low: 0, high: 5)
|
165
|
+
expect(instance.to_string(0)).to eq('[0, 5]')
|
166
166
|
end
|
167
167
|
end
|
168
168
|
end # describe
|
@@ -29,10 +29,10 @@ module Rley # Open this namespace to avoid module qualifier prefixes
|
|
29
29
|
it 'should know its terminal' do
|
30
30
|
expect(subject.terminal).to eq(a_terminal)
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
it 'should know its terminal' do
|
34
34
|
expect(subject.position).to eq(a_pos)
|
35
|
-
end
|
35
|
+
end
|
36
36
|
end # context
|
37
37
|
end # describe
|
38
38
|
end # module
|
@@ -0,0 +1,302 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../spec_helper'
|
4
|
+
|
5
|
+
# Load the class under test
|
6
|
+
require_relative '../../../lib/rley/notation/grammar_builder'
|
7
|
+
|
8
|
+
module Rley # Open this namespace to avoid module qualifier prefixes
|
9
|
+
module Notation # Open this namespace to avoid module qualifier prefixes
|
10
|
+
describe GrammarBuilder do
|
11
|
+
context 'Initialization without argument:' do
|
12
|
+
it 'could be created without argument' do
|
13
|
+
expect { GrammarBuilder.new }.not_to raise_error
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should have no grammar symbols at start' do
|
17
|
+
expect(subject.symbols).to be_empty
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should have no productions at start' do
|
21
|
+
expect(subject.productions).to be_empty
|
22
|
+
end
|
23
|
+
end # context
|
24
|
+
|
25
|
+
context 'Initialization with argument:' do
|
26
|
+
it 'could be created with a block argument' do
|
27
|
+
expect do
|
28
|
+
GrammarBuilder.new { nil }
|
29
|
+
end.not_to raise_error
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'could have grammar symbols from block argument' do
|
33
|
+
instance = GrammarBuilder.new do
|
34
|
+
add_terminals('a', 'b', 'c')
|
35
|
+
end
|
36
|
+
expect(instance.symbols.size).to eq(3)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should have no productions at start' do
|
40
|
+
expect(subject.productions).to be_empty
|
41
|
+
end
|
42
|
+
end # context
|
43
|
+
|
44
|
+
context 'Adding symbols:' do
|
45
|
+
it 'should build terminals from their names' do
|
46
|
+
subject.add_terminals('a', 'b', 'c')
|
47
|
+
expect(subject.symbols.size).to eq(3)
|
48
|
+
expect(subject.symbols['a']).to be_kind_of(Syntax::Terminal)
|
49
|
+
expect(subject.symbols['a'].name).to eq('a')
|
50
|
+
expect(subject.symbols['b']).to be_kind_of(Syntax::Terminal)
|
51
|
+
expect(subject.symbols['b'].name).to eq('b')
|
52
|
+
expect(subject.symbols['c']).to be_kind_of(Syntax::Terminal)
|
53
|
+
expect(subject.symbols['c'].name).to eq('c')
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should accept already built terminals' do
|
57
|
+
a = Syntax::Terminal.new('a')
|
58
|
+
b = Syntax::VerbatimSymbol.new('b')
|
59
|
+
c = Syntax::Literal.new('c', /c/)
|
60
|
+
|
61
|
+
subject.add_terminals(a, b, c)
|
62
|
+
expect(subject.symbols.size).to eq(3)
|
63
|
+
expect(subject.symbols['a']).to eq(a)
|
64
|
+
expect(subject.symbols['b']).to eq(b)
|
65
|
+
expect(subject.symbols['c']).to eq(c)
|
66
|
+
end
|
67
|
+
end # context
|
68
|
+
|
69
|
+
context 'Adding productions:' do
|
70
|
+
subject do
|
71
|
+
instance = GrammarBuilder.new
|
72
|
+
instance.add_terminals('a', 'b', 'c')
|
73
|
+
instance
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'should add a valid production' do
|
77
|
+
# Case of a rhs representation that consists of one name only
|
78
|
+
expect { subject.add_production('S' => 'A') }.not_to raise_error
|
79
|
+
expect(subject.productions.size).to eq(1)
|
80
|
+
new_prod = subject.productions[0]
|
81
|
+
expect(new_prod.lhs).to eq(subject['S'])
|
82
|
+
expect(new_prod.rhs[0]).not_to be_nil
|
83
|
+
expect(new_prod.rhs[0]).to eq(subject['A'])
|
84
|
+
|
85
|
+
# Case of rhs with multiple symbols
|
86
|
+
subject.add_production('A' => 'a A c')
|
87
|
+
expect(subject.productions.size).to eq(2)
|
88
|
+
new_prod = subject.productions.last
|
89
|
+
expect(new_prod.lhs).to eq(subject['A'])
|
90
|
+
expect_rhs = [subject['a'], subject['A'], subject['c']]
|
91
|
+
expect(new_prod.rhs.members).to eq(expect_rhs)
|
92
|
+
|
93
|
+
# GrammarBuilder#rule is an alias of add_production
|
94
|
+
subject.rule('A' => 'b')
|
95
|
+
expect(subject.productions.size).to eq(3)
|
96
|
+
new_prod = subject.productions.last
|
97
|
+
expect(new_prod.lhs).to eq(subject['A'])
|
98
|
+
expect(new_prod.rhs[0]).to eq(subject['b'])
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'should accept annotated terminals' do
|
102
|
+
subject.rule('A' => "a b {match_closest: 'IF' } c")
|
103
|
+
expect(subject.productions.size).to eq(1)
|
104
|
+
new_prod = subject.productions.last
|
105
|
+
expect(new_prod.lhs).to eq(subject['A'])
|
106
|
+
expect(new_prod.rhs[0].name).to eq('a')
|
107
|
+
expect(new_prod.rhs[0]).to eq(subject['a'])
|
108
|
+
expect(new_prod.rhs[1].name).to eq('b')
|
109
|
+
expect(new_prod.rhs[2].name).to eq('c')
|
110
|
+
expect(new_prod.constraints.size).to eq(1)
|
111
|
+
expect(new_prod.constraints[0]).to be_kind_of(Syntax::MatchClosest)
|
112
|
+
expect(new_prod.constraints[0].idx_symbol).to eq(1) # b is on position 1
|
113
|
+
expect(new_prod.constraints[0].closest_symb).to eq('IF')
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should support optional symbol' do
|
117
|
+
instance = GrammarBuilder.new
|
118
|
+
instance.add_terminals('LPAREN', 'RPAREN')
|
119
|
+
|
120
|
+
instance.rule 'argument_list' => 'LPAREN arguments? RPAREN'
|
121
|
+
instance.grammar_complete!
|
122
|
+
|
123
|
+
# implicitly called: rule('arguments_qmark' => 'arguments').tag suffix_qmark_one
|
124
|
+
# implicitly called: rule('arguments_qmark' => '').tag suffix_qmark_none
|
125
|
+
expect(instance.productions.size).to eq(3)
|
126
|
+
prod_star = instance.productions.select { |prod| prod.lhs.name == 'arguments_qmark' }
|
127
|
+
expect(prod_star.size).to eq(2)
|
128
|
+
first_prod = instance.productions.first
|
129
|
+
expect(first_prod.lhs.name).to eq('argument_list')
|
130
|
+
expect(first_prod.rhs.members[1].name).to eq('arguments_qmark')
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should support Kleene's star" do
|
134
|
+
instance = GrammarBuilder.new
|
135
|
+
instance.add_terminals('EOF')
|
136
|
+
|
137
|
+
instance.rule 'program' => 'declaration* EOF'
|
138
|
+
instance.grammar_complete!
|
139
|
+
|
140
|
+
# implicitly called: rule('declaration_star' => 'declaration_star declaration').tag suffix_star_more
|
141
|
+
# implicitly called: rule('declaration_star' => '').tag suffix_star_last
|
142
|
+
expect(instance.productions.size).to eq(3)
|
143
|
+
prod_star = instance.productions.select { |prod| prod.lhs.name == 'declaration_star' }
|
144
|
+
expect(prod_star.size).to eq(2)
|
145
|
+
first_prod = instance.productions.first
|
146
|
+
expect(first_prod.lhs.name).to eq('program')
|
147
|
+
expect(first_prod.rhs.members[0].name).to eq('declaration_star')
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should support symbols decorated with Kleene's plus" do
|
151
|
+
instance = GrammarBuilder.new
|
152
|
+
instance.add_terminals('plus', 'minus', 'digit')
|
153
|
+
|
154
|
+
instance.rule 'integer' => 'value'
|
155
|
+
instance.rule 'integer' => 'sign value'
|
156
|
+
instance.rule 'sign' => 'plus'
|
157
|
+
instance.rule 'sign' => 'minus'
|
158
|
+
instance.rule 'value' => 'digit+'
|
159
|
+
expect(instance.productions.size).to eq(5)
|
160
|
+
instance.grammar_complete!
|
161
|
+
expect(instance.productions.size).to eq(7)
|
162
|
+
|
163
|
+
# implicitly called: rule('digit_plus' => 'digit_plus digit').tag suffix_plus_more
|
164
|
+
# implicitly called: rule('digit_plus' => 'digit').tag suffix_plus_last
|
165
|
+
expect(instance.productions.size).to eq(7) # Two additional rules generated
|
166
|
+
prod_plus = instance.productions.select { |prod| prod.lhs.name == 'digit_plus' }
|
167
|
+
expect(prod_plus.size).to eq(2)
|
168
|
+
val_prod = instance.productions[4]
|
169
|
+
expect(val_prod.lhs.name).to eq('value')
|
170
|
+
expect(val_prod.rhs.members[0].name).to eq('digit_plus')
|
171
|
+
end
|
172
|
+
|
173
|
+
it "should support optional grouping" do
|
174
|
+
instance = GrammarBuilder.new
|
175
|
+
instance.add_terminals('EQUAL', 'IDENTIFIER', 'VAR')
|
176
|
+
|
177
|
+
instance.rule 'var_decl' => 'VAR IDENTIFIER (EQUAL expression)?'
|
178
|
+
instance.grammar_complete!
|
179
|
+
|
180
|
+
# implicitly called: rule('seq_EQUAL_expression' => 'EQUAL expression').tag 'return_children'
|
181
|
+
# implicitly called: rule('seq_EQUAL_expression_qmark' => 'seq_EQUAL_expression').tag suffix_qmark_one
|
182
|
+
# implicitly called: rule('seq_EQUAL_expression_qmark' => '').tag suffix_qmark_none
|
183
|
+
expect(instance.productions.size).to eq(4)
|
184
|
+
first_prod = instance.productions.first
|
185
|
+
expect(first_prod.lhs.name).to eq('var_decl')
|
186
|
+
expect(first_prod.rhs.members[2].name).to eq('seq_EQUAL_expression_qmark')
|
187
|
+
(p0, p1, p2) = instance.productions[1..3]
|
188
|
+
expect(p0.lhs.name).to eq('seq_EQUAL_expression')
|
189
|
+
expect(p0.rhs[0].name).to eq('EQUAL')
|
190
|
+
expect(p0.rhs[1].name).to eq('expression')
|
191
|
+
expect(p0.name).to eq('return_children')
|
192
|
+
|
193
|
+
expect(p1.lhs.name).to eq('seq_EQUAL_expression_qmark')
|
194
|
+
expect(p1.rhs[0].name).to eq('seq_EQUAL_expression')
|
195
|
+
expect(p1.name).to eq('_qmark_one')
|
196
|
+
|
197
|
+
expect(p2.lhs.name).to eq('seq_EQUAL_expression_qmark')
|
198
|
+
expect(p2.rhs).to be_empty
|
199
|
+
expect(p2.name).to eq('_qmark_none')
|
200
|
+
end
|
201
|
+
|
202
|
+
it "should support grouping with star modifier" do
|
203
|
+
instance = GrammarBuilder.new
|
204
|
+
instance.add_terminals('OR')
|
205
|
+
|
206
|
+
instance.rule 'logic_or' => 'logic_and (OR logic_and)*'
|
207
|
+
instance.grammar_complete!
|
208
|
+
|
209
|
+
# implicitly called: rule('seq_OR_logic_and' => 'OR logic_and').tag 'return_children'
|
210
|
+
# implicitly called: rule('seq_EQUAL_expression_star' => 'seq_EQUAL_expression_star seq_EQUAL_expression').tag suffix_star_more
|
211
|
+
# implicitly called: rule('seq_EQUAL_expression_star' => '').tag suffix_star_none
|
212
|
+
expect(instance.productions.size).to eq(4)
|
213
|
+
first_prod = instance.productions.first
|
214
|
+
expect(first_prod.lhs.name).to eq('logic_or')
|
215
|
+
expect(first_prod.rhs.members[1].name).to eq('seq_OR_logic_and_star')
|
216
|
+
|
217
|
+
(p0, p1, p2) = instance.productions[1..3]
|
218
|
+
expect(p0.lhs.name).to eq('seq_OR_logic_and')
|
219
|
+
expect(p0.rhs[0].name).to eq('OR')
|
220
|
+
expect(p0.rhs[1].name).to eq('logic_and')
|
221
|
+
expect(p0.name).to eq('return_children')
|
222
|
+
|
223
|
+
expect(p1.lhs.name).to eq('seq_OR_logic_and_star')
|
224
|
+
expect(p1.rhs[0].name).to eq('seq_OR_logic_and_star')
|
225
|
+
expect(p1.rhs[1].name).to eq('seq_OR_logic_and')
|
226
|
+
expect(p1.name).to eq('_star_more')
|
227
|
+
|
228
|
+
expect(p2.lhs.name).to eq('seq_OR_logic_and_star')
|
229
|
+
expect(p2.rhs).to be_empty
|
230
|
+
expect(p2.name).to eq('_star_none')
|
231
|
+
end
|
232
|
+
|
233
|
+
it "should support grouping with plus modifier" do
|
234
|
+
instance = GrammarBuilder.new
|
235
|
+
instance.add_terminals('POINT TO SEMI_COLON')
|
236
|
+
|
237
|
+
instance.rule 'path' => 'POINT (TO POINT)+ SEMI_COLON'
|
238
|
+
instance.grammar_complete!
|
239
|
+
|
240
|
+
# implicitly called: rule('seq_TO_POINT' => 'TO POINT').tag 'return_children'
|
241
|
+
# implicitly called: rule('seq_TO_POINT_plus' => 'seq_TO_POINT_plus seq_TO_POINT').tag suffix_plus_more
|
242
|
+
# implicitly called: rule('seq_TO_POINT_plus' => 'seq_TO_POINT').tag suffix_plus_one
|
243
|
+
expect(instance.productions.size).to eq(4)
|
244
|
+
first_prod = instance.productions.first
|
245
|
+
expect(first_prod.lhs.name).to eq('path')
|
246
|
+
expect(first_prod.rhs.members[1].name).to eq('seq_TO_POINT_plus')
|
247
|
+
|
248
|
+
(p0, p1, p2) = instance.productions[1..3]
|
249
|
+
expect(p0.lhs.name).to eq('seq_TO_POINT')
|
250
|
+
expect(p0.rhs[0].name).to eq('TO')
|
251
|
+
expect(p0.rhs[1].name).to eq('POINT')
|
252
|
+
expect(p0.name).to eq('return_children')
|
253
|
+
|
254
|
+
expect(p1.lhs.name).to eq('seq_TO_POINT_plus')
|
255
|
+
expect(p1.rhs[0].name).to eq('seq_TO_POINT_plus')
|
256
|
+
expect(p1.rhs[1].name).to eq('seq_TO_POINT')
|
257
|
+
expect(p1.name).to eq('_plus_more')
|
258
|
+
|
259
|
+
expect(p2.lhs.name).to eq('seq_TO_POINT_plus')
|
260
|
+
expect(p2.rhs[0].name).to eq('seq_TO_POINT')
|
261
|
+
expect(p2.name).to eq('_plus_one')
|
262
|
+
end
|
263
|
+
|
264
|
+
it "should support grouping with nested annotation" do
|
265
|
+
instance = GrammarBuilder.new
|
266
|
+
instance.add_terminals('IF ELSE LPAREN RPAREN')
|
267
|
+
st = "IF LPAREN expr RPAREN stmt (ELSE { match_closest: 'IF' } stmt)?"
|
268
|
+
instance.rule('if_stmt' => st)
|
269
|
+
instance.grammar_complete!
|
270
|
+
|
271
|
+
# implicitly called: rule('seq_ELSE_stmt' => 'ELSE stmt').tag 'return_children'
|
272
|
+
# implicitly called: rule('seq_ELSE_stmt_qmark' => 'seq_ELSE_stmt ').tag suffix_plus_more
|
273
|
+
# implicitly called: rule('seq_ELSE_stmt_qmark' => '').tag suffix_plus_one
|
274
|
+
expect(instance.productions.size).to eq(4)
|
275
|
+
first_prod = instance.productions.first
|
276
|
+
expect(first_prod.lhs.name).to eq('if_stmt')
|
277
|
+
expect(first_prod.rhs.members[5].name).to eq('seq_ELSE_stmt_qmark')
|
278
|
+
|
279
|
+
(p0, p1, p2) = instance.productions[1..3]
|
280
|
+
expect(p0.lhs.name).to eq('seq_ELSE_stmt')
|
281
|
+
expect(p0.rhs[0].name).to eq('ELSE')
|
282
|
+
expect(p0.rhs[1].name).to eq('stmt')
|
283
|
+
expect(p0.name).to eq('return_children')
|
284
|
+
expect(p0.constraints.size). to eq(1)
|
285
|
+
expect(p0.constraints[0]).to be_kind_of(Syntax::MatchClosest)
|
286
|
+
expect(p0.constraints[0].idx_symbol).to eq(0) # ELSE is on position 0
|
287
|
+
expect(p0.constraints[0].closest_symb).to eq('IF')
|
288
|
+
|
289
|
+
expect(p1.lhs.name).to eq('seq_ELSE_stmt_qmark')
|
290
|
+
expect(p1.rhs[0].name).to eq('seq_ELSE_stmt')
|
291
|
+
expect(p1.name).to eq('_qmark_one')
|
292
|
+
|
293
|
+
expect(p2.lhs.name).to eq('seq_ELSE_stmt_qmark')
|
294
|
+
expect(p2.rhs).to be_empty
|
295
|
+
expect(p2.name).to eq('_qmark_none')
|
296
|
+
end
|
297
|
+
end # context
|
298
|
+
end # describe
|
299
|
+
end # module
|
300
|
+
end # module
|
301
|
+
|
302
|
+
# End of file
|