rley 0.7.08 → 0.8.03

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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +29 -5
  3. data/CHANGELOG.md +28 -4
  4. data/README.md +4 -5
  5. data/examples/NLP/nano_eng/nano_en_demo.rb +7 -11
  6. data/examples/NLP/nano_eng/nano_grammar.rb +18 -18
  7. data/examples/data_formats/JSON/json_ast_builder.rb +9 -18
  8. data/examples/data_formats/JSON/json_demo.rb +1 -2
  9. data/examples/data_formats/JSON/json_grammar.rb +11 -11
  10. data/examples/general/calc_iter1/calc_grammar.rb +5 -4
  11. data/examples/general/calc_iter2/calc_grammar.rb +9 -9
  12. data/examples/general/left.rb +1 -1
  13. data/examples/general/right.rb +1 -1
  14. data/lib/rley/base/dotted_item.rb +5 -0
  15. data/lib/rley/base/grm_items_builder.rb +6 -0
  16. data/lib/rley/constants.rb +1 -1
  17. data/lib/rley/engine.rb +2 -2
  18. data/lib/rley/interface.rb +16 -0
  19. data/lib/rley/notation/all_notation_nodes.rb +4 -0
  20. data/lib/rley/notation/ast_builder.rb +185 -0
  21. data/lib/rley/notation/ast_node.rb +44 -0
  22. data/lib/rley/notation/ast_visitor.rb +115 -0
  23. data/lib/rley/notation/grammar.rb +49 -0
  24. data/lib/rley/notation/grammar_builder.rb +505 -0
  25. data/lib/rley/notation/grouping_node.rb +23 -0
  26. data/lib/rley/notation/parser.rb +56 -0
  27. data/lib/rley/notation/sequence_node.rb +35 -0
  28. data/lib/rley/notation/symbol_node.rb +29 -0
  29. data/lib/rley/notation/tokenizer.rb +180 -0
  30. data/lib/rley/parse_rep/ast_base_builder.rb +44 -0
  31. data/lib/rley/parser/gfg_chart.rb +101 -6
  32. data/lib/rley/parser/gfg_earley_parser.rb +1 -1
  33. data/lib/rley/parser/gfg_parsing.rb +5 -3
  34. data/lib/rley/parser/parse_entry_set.rb +1 -1
  35. data/lib/rley/syntax/{grammar_builder.rb → base_grammar_builder.rb} +53 -15
  36. data/lib/rley/syntax/grm_symbol.rb +1 -1
  37. data/lib/rley/syntax/match_closest.rb +43 -0
  38. data/lib/rley/syntax/production.rb +6 -0
  39. data/lib/rley.rb +1 -1
  40. data/spec/rley/engine_spec.rb +6 -6
  41. data/spec/rley/gfg/grm_flow_graph_spec.rb +2 -2
  42. data/spec/rley/notation/grammar_builder_spec.rb +302 -0
  43. data/spec/rley/notation/parser_spec.rb +183 -0
  44. data/spec/rley/notation/tokenizer_spec.rb +364 -0
  45. data/spec/rley/parse_rep/ast_builder_spec.rb +0 -1
  46. data/spec/rley/parse_rep/groucho_spec.rb +1 -1
  47. data/spec/rley/parse_rep/parse_forest_builder_spec.rb +1 -1
  48. data/spec/rley/parse_rep/parse_forest_factory_spec.rb +2 -2
  49. data/spec/rley/parse_rep/parse_tree_factory_spec.rb +1 -1
  50. data/spec/rley/parser/dangling_else_spec.rb +447 -0
  51. data/spec/rley/parser/gfg_earley_parser_spec.rb +118 -10
  52. data/spec/rley/parser/gfg_parsing_spec.rb +2 -1
  53. data/spec/rley/parser/parse_walker_factory_spec.rb +2 -2
  54. data/spec/rley/support/ambiguous_grammar_helper.rb +2 -2
  55. data/spec/rley/support/grammar_abc_helper.rb +2 -2
  56. data/spec/rley/support/grammar_ambig01_helper.rb +2 -2
  57. data/spec/rley/support/grammar_arr_int_helper.rb +2 -2
  58. data/spec/rley/support/grammar_b_expr_helper.rb +2 -2
  59. data/spec/rley/support/grammar_int_seq_helper.rb +51 -0
  60. data/spec/rley/support/grammar_l0_helper.rb +2 -2
  61. data/spec/rley/support/grammar_pb_helper.rb +2 -2
  62. data/spec/rley/support/grammar_sppf_helper.rb +2 -2
  63. data/spec/rley/syntax/{grammar_builder_spec.rb → base_grammar_builder_spec.rb} +29 -11
  64. data/spec/rley/syntax/match_closest_spec.rb +46 -0
  65. data/spec/rley/syntax/production_spec.rb +4 -0
  66. metadata +29 -14
  67. data/lib/rley/parser/parse_state.rb +0 -78
  68. data/lib/rley/parser/parse_state_tracker.rb +0 -59
  69. data/lib/rley/parser/state_set.rb +0 -100
  70. data/spec/rley/parser/parse_state_spec.rb +0 -125
  71. data/spec/rley/parser/parse_tracer_spec.rb +0 -200
  72. data/spec/rley/parser/state_set_spec.rb +0 -130
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../spec_helper' # Use the RSpec framework
4
+
5
+ require_relative '../../../lib/rley/notation/ast_builder'
6
+ # Load the class under test
7
+ require_relative '../../../lib/rley/notation/parser'
8
+
9
+ module Rley
10
+ module Notation
11
+ describe Parser do
12
+ subject { Parser.new }
13
+
14
+ # Utility method to walk towards deeply nested node
15
+ # @param aNTNode [Rley::PTree::NonTerminalNode]
16
+ # @param subnodePath[Array<Integer>] An Array of subnode indices
17
+ def walk_subnodes(aNTNode, subnodePath)
18
+ curr_node = aNTNode
19
+ subnodePath.each do |index|
20
+ curr_node = curr_node.subnodes[index]
21
+ end
22
+
23
+ curr_node
24
+ end
25
+
26
+ context 'Initialization:' do
27
+ it 'should be initialized without argument' do
28
+ expect { Parser.new }.not_to raise_error
29
+ end
30
+
31
+ it 'should have its parse engine initialized' do
32
+ expect(subject.engine).to be_kind_of(Rley::Engine)
33
+ end
34
+ end # context
35
+
36
+ context 'Parsing into CST:' do
37
+ subject do
38
+ instance = Parser.new
39
+ instance.engine.configuration.repr_builder = Rley::ParseRep::CSTBuilder
40
+
41
+ instance
42
+ end
43
+
44
+ it 'should parse single symbol names' do
45
+ samples = %w[IF ifCondition statement]
46
+
47
+ # One drawback od CSTs: they have a deeply nested structure
48
+ samples.each do |source|
49
+ ptree = subject.parse(source)
50
+ expect(ptree.root).to be_kind_of(Rley::PTree::NonTerminalNode)
51
+ expect(ptree.root.symbol.name).to eq('notation')
52
+ expect(ptree.root.subnodes[0]).to be_kind_of(Rley::PTree::NonTerminalNode)
53
+ expect(ptree.root.subnodes[0].symbol.name).to eq('rhs')
54
+ expect(ptree.root.subnodes[0].subnodes[0]).to be_kind_of(Rley::PTree::NonTerminalNode)
55
+ member_seq = ptree.root.subnodes[0].subnodes[0]
56
+ expect(member_seq.symbol.name).to eq('member_seq')
57
+ expect(member_seq.subnodes[0]).to be_kind_of(Rley::PTree::NonTerminalNode)
58
+ expect(member_seq.subnodes[0].symbol.name).to eq('member')
59
+ expect(member_seq.subnodes[0].subnodes[0]).to be_kind_of(Rley::PTree::NonTerminalNode)
60
+ expect(member_seq.subnodes[0].subnodes[0].symbol.name).to eq('strait_member')
61
+ strait_member = member_seq.subnodes[0].subnodes[0]
62
+ expect(strait_member.subnodes[0]).to be_kind_of(Rley::PTree::NonTerminalNode)
63
+ expect(strait_member.subnodes[0].symbol.name).to eq('base_member')
64
+ expect(strait_member.subnodes[0].subnodes[0]).to be_kind_of(Rley::PTree::TerminalNode)
65
+ expect(strait_member.subnodes[0].subnodes[0].token.lexeme).to eq(source)
66
+ end
67
+ end
68
+ end # context
69
+
70
+ context 'Parsing into AST:' do
71
+ subject do
72
+ instance = Parser.new
73
+ instance.engine.configuration.repr_builder = ASTBuilder
74
+
75
+ instance
76
+ end
77
+
78
+ it 'should parse single symbol names' do
79
+ samples = %w[IF ifCondition statement]
80
+
81
+ samples.each do |source|
82
+ ptree = subject.parse(source)
83
+ expect(ptree.root).to be_kind_of(SymbolNode)
84
+ expect(ptree.root.name).to eq(source)
85
+ expect(ptree.root.repetition).to eq(:exactly_one)
86
+ end
87
+ end
88
+
89
+ it 'should parse a sequence of symbols' do
90
+ sequence = 'INT_LIT ELLIPSIS INT_LIT'
91
+
92
+ ptree = subject.parse(sequence)
93
+ expect(ptree.root).to be_kind_of(SequenceNode)
94
+ expect(ptree.root.subnodes[0]).to be_kind_of(SymbolNode)
95
+ expect(ptree.root.subnodes[0].name).to eq('INT_LIT')
96
+ expect(ptree.root.subnodes[1]).to be_kind_of(SymbolNode)
97
+ expect(ptree.root.subnodes[1].name).to eq('ELLIPSIS')
98
+ expect(ptree.root.subnodes[2]).to be_kind_of(SymbolNode)
99
+ expect(ptree.root.subnodes[2].name).to eq('INT_LIT')
100
+ end
101
+
102
+ it 'should parse an optional symbol' do
103
+ optional = 'member_seq?'
104
+
105
+ ptree = subject.parse(optional)
106
+ expect(ptree.root).to be_kind_of(SymbolNode)
107
+ expect(ptree.root.name).to eq('member_seq')
108
+ expect(ptree.root.repetition).to eq(:zero_or_one)
109
+ end
110
+
111
+ it 'should parse a symbol with a + modifier' do
112
+ one_or_more = 'member+'
113
+
114
+ ptree = subject.parse(one_or_more)
115
+ expect(ptree.root).to be_kind_of(SymbolNode)
116
+ expect(ptree.root.name).to eq('member')
117
+ expect(ptree.root.repetition).to eq(:one_or_more)
118
+ end
119
+
120
+ it 'should parse a symbol with a * modifier' do
121
+ zero_or_more = 'declaration* EOF'
122
+
123
+ ptree = subject.parse(zero_or_more)
124
+ expect(ptree.root).to be_kind_of(SequenceNode)
125
+ expect(ptree.root.subnodes[0]).to be_kind_of(SymbolNode)
126
+ expect(ptree.root.subnodes[0].name).to eq('declaration')
127
+ expect(ptree.root.subnodes[0].repetition).to eq(:zero_or_more)
128
+ expect(ptree.root.subnodes[1]).to be_kind_of(SymbolNode)
129
+ expect(ptree.root.subnodes[1].name).to eq('EOF')
130
+ expect(ptree.root.subnodes[1].repetition).to eq(:exactly_one)
131
+ end
132
+
133
+ it 'should parse a grouping with a modifier' do
134
+ input = 'IF ifCondition statement (ELSE statement)?'
135
+
136
+ ptree = subject.parse(input)
137
+ expect(ptree.root).to be_kind_of(SequenceNode)
138
+ expect(ptree.root.subnodes[0]).to be_kind_of(SymbolNode)
139
+ expect(ptree.root.subnodes[0].name).to eq('IF')
140
+ expect(ptree.root.subnodes[1]).to be_kind_of(SymbolNode)
141
+ expect(ptree.root.subnodes[1].name).to eq('ifCondition')
142
+ expect(ptree.root.subnodes[2]).to be_kind_of(SymbolNode)
143
+ expect(ptree.root.subnodes[2].name).to eq('statement')
144
+ expect(ptree.root.subnodes[3]).to be_kind_of(SequenceNode)
145
+ expect(ptree.root.subnodes[3].repetition).to eq(:zero_or_one)
146
+ expect(ptree.root.subnodes[3].subnodes[0]).to be_kind_of(SymbolNode)
147
+ expect(ptree.root.subnodes[3].subnodes[0].name).to eq('ELSE')
148
+ expect(ptree.root.subnodes[3].subnodes[1]).to be_kind_of(SymbolNode)
149
+ expect(ptree.root.subnodes[3].subnodes[1].name).to eq('statement')
150
+ end
151
+
152
+ it 'should parse an annotated symbol' do
153
+ optional = 'member_seq{repeat: 0..1}'
154
+
155
+ ptree = subject.parse(optional)
156
+ expect(ptree.root).to be_kind_of(SymbolNode)
157
+ expect(ptree.root.name).to eq('member_seq')
158
+ expect(ptree.root.repetition).to eq(:zero_or_one)
159
+ end
160
+
161
+ it 'should parse a grouping with embedded annotation' do
162
+ if_stmt = "IF ifCondition statement ( ELSE { match_closest: 'IF' } statement )?"
163
+
164
+ ptree = subject.parse(if_stmt)
165
+ expect(ptree.root).to be_kind_of(SequenceNode)
166
+ expect(ptree.root.subnodes[0]).to be_kind_of(SymbolNode)
167
+ expect(ptree.root.subnodes[0].name).to eq('IF')
168
+ expect(ptree.root.subnodes[1]).to be_kind_of(SymbolNode)
169
+ expect(ptree.root.subnodes[1].name).to eq('ifCondition')
170
+ expect(ptree.root.subnodes[2]).to be_kind_of(SymbolNode)
171
+ expect(ptree.root.subnodes[2].name).to eq('statement')
172
+ optional = ptree.root.subnodes[3]
173
+ expect(optional).to be_kind_of(SequenceNode)
174
+ expect(optional.repetition).to eq(:zero_or_one)
175
+ expect(optional.subnodes[0]).to be_kind_of(SymbolNode)
176
+ expect(optional.subnodes[0].name).to eq('ELSE')
177
+ expect(optional.subnodes[0].annotation).to eq({ 'match_closest' => 'IF' })
178
+ expect(optional.subnodes[1].name).to eq('statement')
179
+ end
180
+ end # context
181
+ end # describe
182
+ end # module
183
+ end # module
@@ -0,0 +1,364 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../spec_helper'
4
+
5
+ # Load the class under test
6
+ require_relative '../../../lib/rley/notation/tokenizer'
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 Tokenizer do
11
+ # Utility method for comparing actual and expected token
12
+ # sequence. The final EOF is removed from the input sequence.
13
+ def match_expectations(aScanner, theExpectations)
14
+ tokens = aScanner.tokens
15
+
16
+ tokens.each_with_index do |token, i|
17
+ terminal, lexeme = theExpectations[i]
18
+ expect(token.terminal).to eq(terminal)
19
+ expect(token.lexeme).to eq(lexeme)
20
+ end
21
+ end
22
+
23
+ context 'Initialization:' do
24
+ let(:sample_text) { 'begin-object member-list end-object' }
25
+ subject { Tokenizer.new }
26
+
27
+ it 'could be initialized with a text to tokenize or...' do
28
+ expect { Tokenizer.new(sample_text) }.not_to raise_error
29
+ end
30
+
31
+ it 'could be initialized without argument...' do
32
+ expect { Tokenizer.new }.not_to raise_error
33
+ end
34
+
35
+ it 'should have its scanner initialized' do
36
+ expect(subject.scanner).to be_kind_of(StringScanner)
37
+ end
38
+ end # context
39
+
40
+ context 'Input tokenization:' do
41
+ it 'should recognize single special character token' do
42
+ input = '(){}?*+,'
43
+ subject.start_with(input)
44
+ expectations = [
45
+ # [token lexeme]
46
+ %w[LEFT_PAREN (],
47
+ %w[RIGHT_PAREN )],
48
+ %w[LEFT_BRACE {],
49
+ %w[RIGHT_BRACE }],
50
+ %w[QUESTION_MARK ?],
51
+ %w[STAR *],
52
+ %w[PLUS +],
53
+ %w[COMMA ,]
54
+ ]
55
+ match_expectations(subject, expectations)
56
+ end
57
+
58
+ it 'should recognize one or two special character tokens' do
59
+ input = '..'
60
+ subject.start_with(input)
61
+ expectations = [
62
+ # [token lexeme]
63
+ %w[ELLIPSIS ..]
64
+ ]
65
+ match_expectations(subject, expectations)
66
+ end
67
+
68
+ it 'should treat ? * + as symbols if they occur as suffix' do
69
+ input = 'a+ + b* * 3 ?'
70
+ subject.start_with(input)
71
+ expectations = [
72
+ # [token lexeme]
73
+ %w[SYMBOL a],
74
+ %w[PLUS +],
75
+ %w[SYMBOL +],
76
+ %w[SYMBOL b],
77
+ %w[STAR *],
78
+ %w[SYMBOL *],
79
+ %w[INT_LIT 3],
80
+ %w[SYMBOL ?]
81
+ ]
82
+ match_expectations(subject, expectations)
83
+ end
84
+
85
+ it 'should recognize annotation keywords' do
86
+ keywords = 'match_closest: repeat:'
87
+ subject.start_with(keywords)
88
+ expectations = [
89
+ # [token lexeme]
90
+ %w[KEY match_closest],
91
+ %w[KEY repeat]
92
+ ]
93
+ match_expectations(subject, expectations)
94
+ end
95
+
96
+ it 'should recognize ordinal integer values' do
97
+ input = <<-RLEY_END
98
+ 3 123
99
+ 987654 0
100
+ RLEY_END
101
+
102
+ expectations = [
103
+ ['3', 3],
104
+ ['123', 123],
105
+ ['987654', 987654],
106
+ ['0', 0]
107
+ ]
108
+
109
+ subject.start_with(input)
110
+ subject.tokens[0..-2].each_with_index do |tok, i|
111
+ expect(tok).to be_kind_of(Rley::Lexical::Token)
112
+ expect(tok.terminal).to eq('INT_LIT')
113
+ (lexeme,) = expectations[i]
114
+ expect(tok.lexeme).to eq(lexeme)
115
+ end
116
+ end
117
+
118
+ it 'should recognize string literals' do
119
+ input = <<-RLEY_END
120
+ ""
121
+ "string"
122
+ "123"
123
+ ''
124
+ 'string'
125
+ '123'
126
+ RLEY_END
127
+
128
+ expectations = [
129
+ '',
130
+ 'string',
131
+ '123'
132
+ ] * 2
133
+
134
+ subject.start_with(input)
135
+ subject.tokens.each_with_index do |str, i|
136
+ expect(str).to be_kind_of(Rley::Lexical::Token)
137
+ expect(str.terminal).to eq('STR_LIT')
138
+ (lexeme,) = expectations[i]
139
+ expect(str.lexeme).to eq(lexeme)
140
+ end
141
+ end
142
+
143
+ it 'should recognize a sequence of symbols' do
144
+ input = 'IF ifCondition statement ELSE statement'
145
+ expectations = %w[IF ifCondition statement ELSE statement]
146
+
147
+ subject.start_with(input)
148
+ subject.tokens.each_with_index do |str, i|
149
+ expect(str).to be_kind_of(Rley::Lexical::Token)
150
+ expect(str.terminal).to eq('SYMBOL')
151
+ (lexeme,) = expectations[i]
152
+ expect(str.lexeme).to eq(lexeme)
153
+ end
154
+ end
155
+
156
+ it 'should recognize an optional symbol' do
157
+ input = 'RETURN expression? SEMICOLON'
158
+ expectations = [
159
+ %w[RETURN SYMBOL],
160
+ %w[expression SYMBOL],
161
+ %w[? QUESTION_MARK],
162
+ %w[SEMICOLON SYMBOL]
163
+ ]
164
+
165
+ subject.start_with(input)
166
+ subject.tokens.each_with_index do |str, i|
167
+ expect(str).to be_kind_of(Rley::Lexical::Token)
168
+ (lexeme, token) = expectations[i]
169
+ expect(str.lexeme).to eq(lexeme)
170
+ expect(str.terminal).to eq(token)
171
+ end
172
+ end
173
+
174
+ it 'should recognize a symbol with a star quantifier' do
175
+ input = 'declaration* EOF'
176
+ expectations = [
177
+ %w[declaration SYMBOL],
178
+ %w[* STAR],
179
+ %w[EOF SYMBOL]
180
+ ]
181
+
182
+ subject.start_with(input)
183
+ subject.tokens.each_with_index do |str, i|
184
+ expect(str).to be_kind_of(Rley::Lexical::Token)
185
+ (lexeme, token) = expectations[i]
186
+ expect(str.lexeme).to eq(lexeme)
187
+ expect(str.terminal).to eq(token)
188
+ end
189
+ end
190
+
191
+ it 'should recognize a symbol with a plus quantifier' do
192
+ input = 'declaration+ EOF'
193
+ expectations = [
194
+ %w[declaration SYMBOL],
195
+ %w[+ PLUS],
196
+ %w[EOF SYMBOL]
197
+ ]
198
+
199
+ subject.start_with(input)
200
+ subject.tokens.each_with_index do |str, i|
201
+ expect(str).to be_kind_of(Rley::Lexical::Token)
202
+ (lexeme, token) = expectations[i]
203
+ expect(str.lexeme).to eq(lexeme)
204
+ expect(str.terminal).to eq(token)
205
+ end
206
+ end
207
+
208
+ it 'should recognize a grouping with a quantifier' do
209
+ input = 'IF ifCondition statement (ELSE statement)?'
210
+ expectations = [
211
+ %w[IF SYMBOL],
212
+ %w[ifCondition SYMBOL],
213
+ %w[statement SYMBOL],
214
+ %w[( LEFT_PAREN],
215
+ %w[ELSE SYMBOL],
216
+ %w[statement SYMBOL],
217
+ %w[) RIGHT_PAREN],
218
+ %w[? QUESTION_MARK]
219
+ ]
220
+
221
+ subject.start_with(input)
222
+ subject.tokens.each_with_index do |str, i|
223
+ expect(str).to be_kind_of(Rley::Lexical::Token)
224
+ (lexeme, token) = expectations[i]
225
+ expect(str.lexeme).to eq(lexeme)
226
+ expect(str.terminal).to eq(token)
227
+ end
228
+ end
229
+
230
+ it 'should recognize a match closest constraint' do
231
+ input = "IF ifCondition statement ELSE { match_closest: 'IF' } statement"
232
+ expectations = [
233
+ %w[IF SYMBOL],
234
+ %w[ifCondition SYMBOL],
235
+ %w[statement SYMBOL],
236
+ %w[ELSE SYMBOL],
237
+ %w[{ LEFT_BRACE],
238
+ %w[match_closest KEY],
239
+ %w[IF STR_LIT],
240
+ %w[} RIGHT_BRACE],
241
+ %w[statement SYMBOL]
242
+ ]
243
+
244
+ subject.start_with(input)
245
+ subject.tokens.each_with_index do |str, i|
246
+ expect(str).to be_kind_of(Rley::Lexical::Token)
247
+ (lexeme, token) = expectations[i]
248
+ expect(str.lexeme).to eq(lexeme)
249
+ expect(str.terminal).to eq(token)
250
+ end
251
+ end
252
+
253
+ it 'should recognize a repeat constraint' do
254
+ input = 'IF ifCondition statement { repeat: 1 } ELSE statement'
255
+ expectations = [
256
+ %w[IF SYMBOL],
257
+ %w[ifCondition SYMBOL],
258
+ %w[statement SYMBOL],
259
+ %w[{ LEFT_BRACE],
260
+ %w[repeat KEY],
261
+ %w[1 INT_LIT],
262
+ %w[} RIGHT_BRACE],
263
+ %w[ELSE SYMBOL],
264
+ %w[statement SYMBOL]
265
+ ]
266
+
267
+ subject.start_with(input)
268
+ subject.tokens.each_with_index do |str, i|
269
+ expect(str).to be_kind_of(Rley::Lexical::Token)
270
+ (lexeme, token) = expectations[i]
271
+ expect(str.lexeme).to eq(lexeme)
272
+ expect(str.terminal).to eq(token)
273
+ end
274
+ end
275
+
276
+ it 'should recognize a grouping with a repeat constraint' do
277
+ input = 'IF ifCondition statement ( ELSE statement ){ repeat: 0..1 }'
278
+ expectations = [
279
+ %w[IF SYMBOL],
280
+ %w[ifCondition SYMBOL],
281
+ %w[statement SYMBOL],
282
+ %w[( LEFT_PAREN],
283
+ %w[ELSE SYMBOL],
284
+ %w[statement SYMBOL],
285
+ %w[) RIGHT_PAREN],
286
+ %w[{ LEFT_BRACE],
287
+ %w[repeat KEY],
288
+ %w[0 INT_LIT],
289
+ %w[.. ELLIPSIS],
290
+ %w[1 INT_LIT],
291
+ %w[} RIGHT_BRACE]
292
+ ]
293
+
294
+ subject.start_with(input)
295
+ subject.tokens.each_with_index do |str, i|
296
+ expect(str).to be_kind_of(Rley::Lexical::Token)
297
+ (lexeme, token) = expectations[i]
298
+ expect(str.lexeme).to eq(lexeme)
299
+ expect(str.terminal).to eq(token)
300
+ end
301
+ end
302
+
303
+ it 'should recognize a combination of constraints' do
304
+ input = "IF ifCondition statement ELSE { repeat: 1, match_closest: 'IF' } statement"
305
+ expectations = [
306
+ %w[IF SYMBOL],
307
+ %w[ifCondition SYMBOL],
308
+ %w[statement SYMBOL],
309
+ %w[ELSE SYMBOL],
310
+ %w[{ LEFT_BRACE],
311
+ %w[repeat KEY],
312
+ %w[1 INT_LIT],
313
+ %w[, COMMA],
314
+ %w[match_closest KEY],
315
+ %w[IF STR_LIT],
316
+ %w[} RIGHT_BRACE],
317
+ %w[statement SYMBOL]
318
+ ]
319
+
320
+ subject.start_with(input)
321
+ subject.tokens.each_with_index do |str, i|
322
+ expect(str).to be_kind_of(Rley::Lexical::Token)
323
+ (lexeme, token) = expectations[i]
324
+ expect(str.lexeme).to eq(lexeme)
325
+ expect(str.terminal).to eq(token)
326
+ end
327
+ end
328
+
329
+ it 'should recognize a grouping with a nested constraint' do
330
+ input = "IF ifCondition statement ( ELSE { match_closest: 'IF' } statement ){ repeat: 0..1 }"
331
+ expectations = [
332
+ %w[IF SYMBOL],
333
+ %w[ifCondition SYMBOL],
334
+ %w[statement SYMBOL],
335
+ %w[( LEFT_PAREN],
336
+ %w[ELSE SYMBOL],
337
+ %w[{ LEFT_BRACE],
338
+ %w[match_closest KEY],
339
+ %w[IF STR_LIT],
340
+ %w[} RIGHT_BRACE],
341
+ %w[statement SYMBOL],
342
+ %w[) RIGHT_PAREN],
343
+ %w[{ LEFT_BRACE],
344
+ %w[repeat KEY],
345
+ %w[0 INT_LIT],
346
+ %w[.. ELLIPSIS],
347
+ %w[1 INT_LIT],
348
+ %w[} RIGHT_BRACE]
349
+ ]
350
+
351
+ subject.start_with(input)
352
+ subject.tokens.each_with_index do |str, i|
353
+ expect(str).to be_kind_of(Rley::Lexical::Token)
354
+ (lexeme, token) = expectations[i]
355
+ expect(str.lexeme).to eq(lexeme)
356
+ expect(str.terminal).to eq(token)
357
+ end
358
+ end
359
+ end # context
360
+ end # describe
361
+ end # module
362
+ end # module
363
+
364
+ # End of file