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.
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