rley 0.7.08 → 0.8.03

Sign up to get free protection for your applications and to get access to all the features.
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