rley 0.7.06 → 0.8.01
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.
- 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
|