rley 0.7.06 → 0.8.01

Sign up to get free protection for your applications and to get access to all the features.
Files changed (167) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +362 -62
  3. data/.travis.yml +6 -6
  4. data/CHANGELOG.md +20 -4
  5. data/LICENSE.txt +1 -1
  6. data/README.md +7 -7
  7. data/examples/NLP/engtagger.rb +193 -190
  8. data/examples/NLP/nano_eng/nano_en_demo.rb +7 -11
  9. data/examples/NLP/nano_eng/nano_grammar.rb +21 -21
  10. data/examples/NLP/pico_en_demo.rb +2 -2
  11. data/examples/data_formats/JSON/cli_options.rb +1 -1
  12. data/examples/data_formats/JSON/json_ast_builder.rb +21 -27
  13. data/examples/data_formats/JSON/json_ast_nodes.rb +12 -21
  14. data/examples/data_formats/JSON/json_demo.rb +1 -2
  15. data/examples/data_formats/JSON/json_grammar.rb +13 -13
  16. data/examples/data_formats/JSON/json_lexer.rb +8 -8
  17. data/examples/data_formats/JSON/json_minifier.rb +1 -1
  18. data/examples/general/calc_iter1/calc_ast_builder.rb +13 -10
  19. data/examples/general/calc_iter1/calc_ast_nodes.rb +23 -37
  20. data/examples/general/calc_iter1/calc_grammar.rb +7 -6
  21. data/examples/general/calc_iter1/calc_lexer.rb +6 -4
  22. data/examples/general/calc_iter1/spec/calculator_spec.rb +5 -5
  23. data/examples/general/calc_iter2/calc_ast_builder.rb +5 -3
  24. data/examples/general/calc_iter2/calc_ast_nodes.rb +27 -43
  25. data/examples/general/calc_iter2/calc_grammar.rb +12 -12
  26. data/examples/general/calc_iter2/calc_lexer.rb +11 -10
  27. data/examples/general/calc_iter2/spec/calculator_spec.rb +26 -26
  28. data/examples/general/left.rb +2 -2
  29. data/examples/general/right.rb +2 -2
  30. data/lib/rley.rb +1 -1
  31. data/lib/rley/base/dotted_item.rb +28 -31
  32. data/lib/rley/base/grm_items_builder.rb +6 -0
  33. data/lib/rley/constants.rb +2 -2
  34. data/lib/rley/engine.rb +22 -25
  35. data/lib/rley/formatter/asciitree.rb +3 -3
  36. data/lib/rley/formatter/bracket_notation.rb +1 -8
  37. data/lib/rley/formatter/debug.rb +6 -6
  38. data/lib/rley/formatter/json.rb +2 -2
  39. data/lib/rley/gfg/call_edge.rb +1 -1
  40. data/lib/rley/gfg/edge.rb +5 -5
  41. data/lib/rley/gfg/end_vertex.rb +2 -6
  42. data/lib/rley/gfg/epsilon_edge.rb +1 -5
  43. data/lib/rley/gfg/grm_flow_graph.rb +27 -23
  44. data/lib/rley/gfg/item_vertex.rb +10 -10
  45. data/lib/rley/gfg/non_terminal_vertex.rb +4 -4
  46. data/lib/rley/gfg/scan_edge.rb +1 -1
  47. data/lib/rley/gfg/shortcut_edge.rb +2 -2
  48. data/lib/rley/gfg/start_vertex.rb +4 -8
  49. data/lib/rley/gfg/vertex.rb +43 -39
  50. data/lib/rley/interface.rb +16 -0
  51. data/lib/rley/lexical/token_range.rb +6 -6
  52. data/lib/rley/notation/all_notation_nodes.rb +2 -0
  53. data/lib/rley/notation/ast_builder.rb +191 -0
  54. data/lib/rley/notation/ast_node.rb +44 -0
  55. data/lib/rley/notation/ast_visitor.rb +113 -0
  56. data/lib/rley/notation/grammar.rb +49 -0
  57. data/lib/rley/notation/grammar_builder.rb +504 -0
  58. data/lib/rley/notation/grouping_node.rb +23 -0
  59. data/lib/rley/notation/parser.rb +56 -0
  60. data/lib/rley/notation/sequence_node.rb +35 -0
  61. data/lib/rley/notation/symbol_node.rb +29 -0
  62. data/lib/rley/notation/tokenizer.rb +192 -0
  63. data/lib/rley/parse_forest_visitor.rb +5 -5
  64. data/lib/rley/parse_rep/ast_base_builder.rb +48 -11
  65. data/lib/rley/parse_rep/cst_builder.rb +5 -6
  66. data/lib/rley/parse_rep/parse_forest_builder.rb +22 -18
  67. data/lib/rley/parse_rep/parse_forest_factory.rb +3 -3
  68. data/lib/rley/parse_rep/parse_rep_creator.rb +14 -16
  69. data/lib/rley/parse_rep/parse_tree_builder.rb +4 -4
  70. data/lib/rley/parse_rep/parse_tree_factory.rb +27 -27
  71. data/lib/rley/parse_tree_visitor.rb +1 -1
  72. data/lib/rley/parser/error_reason.rb +4 -5
  73. data/lib/rley/parser/gfg_chart.rb +118 -26
  74. data/lib/rley/parser/gfg_parsing.rb +22 -33
  75. data/lib/rley/parser/parse_entry.rb +25 -31
  76. data/lib/rley/parser/parse_entry_set.rb +19 -16
  77. data/lib/rley/parser/parse_entry_tracker.rb +4 -4
  78. data/lib/rley/parser/parse_tracer.rb +13 -13
  79. data/lib/rley/parser/parse_walker_factory.rb +23 -28
  80. data/lib/rley/ptree/non_terminal_node.rb +7 -5
  81. data/lib/rley/ptree/parse_tree.rb +3 -3
  82. data/lib/rley/ptree/parse_tree_node.rb +5 -5
  83. data/lib/rley/ptree/terminal_node.rb +7 -7
  84. data/lib/rley/rley_error.rb +12 -12
  85. data/lib/rley/sppf/alternative_node.rb +6 -6
  86. data/lib/rley/sppf/composite_node.rb +7 -7
  87. data/lib/rley/sppf/epsilon_node.rb +3 -3
  88. data/lib/rley/sppf/leaf_node.rb +3 -3
  89. data/lib/rley/sppf/parse_forest.rb +16 -16
  90. data/lib/rley/sppf/sppf_node.rb +7 -8
  91. data/lib/rley/sppf/token_node.rb +3 -3
  92. data/lib/rley/syntax/{grammar_builder.rb → base_grammar_builder.rb} +61 -23
  93. data/lib/rley/syntax/grammar.rb +5 -5
  94. data/lib/rley/syntax/grm_symbol.rb +7 -7
  95. data/lib/rley/syntax/match_closest.rb +43 -0
  96. data/lib/rley/syntax/non_terminal.rb +9 -15
  97. data/lib/rley/syntax/production.rb +16 -10
  98. data/lib/rley/syntax/symbol_seq.rb +7 -9
  99. data/lib/rley/syntax/terminal.rb +4 -5
  100. data/lib/rley/syntax/verbatim_symbol.rb +3 -3
  101. data/lib/support/base_tokenizer.rb +19 -18
  102. data/spec/rley/base/dotted_item_spec.rb +2 -2
  103. data/spec/rley/engine_spec.rb +23 -21
  104. data/spec/rley/formatter/asciitree_spec.rb +7 -7
  105. data/spec/rley/formatter/bracket_notation_spec.rb +13 -13
  106. data/spec/rley/formatter/json_spec.rb +1 -1
  107. data/spec/rley/gfg/end_vertex_spec.rb +5 -5
  108. data/spec/rley/gfg/grm_flow_graph_spec.rb +2 -2
  109. data/spec/rley/gfg/item_vertex_spec.rb +10 -10
  110. data/spec/rley/gfg/non_terminal_vertex_spec.rb +3 -3
  111. data/spec/rley/gfg/shortcut_edge_spec.rb +1 -1
  112. data/spec/rley/gfg/start_vertex_spec.rb +5 -5
  113. data/spec/rley/gfg/vertex_spec.rb +3 -3
  114. data/spec/rley/lexical/token_range_spec.rb +16 -16
  115. data/spec/rley/lexical/token_spec.rb +2 -2
  116. data/spec/rley/notation/grammar_builder_spec.rb +302 -0
  117. data/spec/rley/notation/parser_spec.rb +184 -0
  118. data/spec/rley/notation/tokenizer_spec.rb +370 -0
  119. data/spec/rley/parse_forest_visitor_spec.rb +165 -163
  120. data/spec/rley/parse_rep/ambiguous_parse_spec.rb +44 -44
  121. data/spec/rley/parse_rep/ast_builder_spec.rb +6 -7
  122. data/spec/rley/parse_rep/cst_builder_spec.rb +5 -5
  123. data/spec/rley/parse_rep/groucho_spec.rb +24 -26
  124. data/spec/rley/parse_rep/parse_forest_builder_spec.rb +27 -27
  125. data/spec/rley/parse_rep/parse_forest_factory_spec.rb +8 -8
  126. data/spec/rley/parse_rep/parse_tree_factory_spec.rb +3 -3
  127. data/spec/rley/parse_tree_visitor_spec.rb +10 -8
  128. data/spec/rley/parser/dangling_else_spec.rb +445 -0
  129. data/spec/rley/parser/error_reason_spec.rb +6 -6
  130. data/spec/rley/parser/gfg_earley_parser_spec.rb +120 -12
  131. data/spec/rley/parser/gfg_parsing_spec.rb +6 -13
  132. data/spec/rley/parser/parse_entry_spec.rb +19 -19
  133. data/spec/rley/parser/parse_walker_factory_spec.rb +10 -10
  134. data/spec/rley/ptree/non_terminal_node_spec.rb +5 -3
  135. data/spec/rley/ptree/parse_tree_node_spec.rb +4 -4
  136. data/spec/rley/ptree/terminal_node_spec.rb +6 -6
  137. data/spec/rley/sppf/alternative_node_spec.rb +6 -6
  138. data/spec/rley/sppf/non_terminal_node_spec.rb +3 -3
  139. data/spec/rley/sppf/token_node_spec.rb +4 -4
  140. data/spec/rley/support/ambiguous_grammar_helper.rb +4 -5
  141. data/spec/rley/support/grammar_abc_helper.rb +3 -5
  142. data/spec/rley/support/grammar_ambig01_helper.rb +5 -6
  143. data/spec/rley/support/grammar_arr_int_helper.rb +5 -6
  144. data/spec/rley/support/grammar_b_expr_helper.rb +5 -6
  145. data/spec/rley/support/grammar_int_seq_helper.rb +51 -0
  146. data/spec/rley/support/grammar_l0_helper.rb +14 -17
  147. data/spec/rley/support/grammar_pb_helper.rb +8 -7
  148. data/spec/rley/support/grammar_sppf_helper.rb +3 -3
  149. data/spec/rley/syntax/{grammar_builder_spec.rb → base_grammar_builder_spec.rb} +35 -16
  150. data/spec/rley/syntax/grammar_spec.rb +6 -6
  151. data/spec/rley/syntax/grm_symbol_spec.rb +1 -1
  152. data/spec/rley/syntax/match_closest_spec.rb +46 -0
  153. data/spec/rley/syntax/non_terminal_spec.rb +8 -8
  154. data/spec/rley/syntax/production_spec.rb +17 -13
  155. data/spec/rley/syntax/symbol_seq_spec.rb +2 -2
  156. data/spec/rley/syntax/terminal_spec.rb +5 -5
  157. data/spec/rley/syntax/verbatim_symbol_spec.rb +1 -1
  158. data/spec/spec_helper.rb +0 -12
  159. data/spec/support/base_tokenizer_spec.rb +7 -2
  160. metadata +48 -74
  161. data/.simplecov +0 -7
  162. data/lib/rley/parser/parse_state.rb +0 -83
  163. data/lib/rley/parser/parse_state_tracker.rb +0 -59
  164. data/lib/rley/parser/state_set.rb +0 -101
  165. data/spec/rley/parser/parse_state_spec.rb +0 -125
  166. data/spec/rley/parser/parse_tracer_spec.rb +0 -200
  167. 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::GrammarBuilder.new
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::GrammarBuilder.new
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