modl 0.0.2 → 0.3.0

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 (60) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +15 -0
  3. data/.idea/vcs.xml +6 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +5 -0
  6. data/.travis.yml +7 -0
  7. data/CHANGELOG.md +4 -0
  8. data/CODE_OF_CONDUCT.md +74 -0
  9. data/Gemfile +9 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +52 -0
  12. data/Rakefile +6 -0
  13. data/bin/console +14 -0
  14. data/bin/setup +8 -0
  15. data/grammar_tests/1.modl +1 -0
  16. data/grammar_tests/2.modl +1 -0
  17. data/grammar_tests/3.modl +1 -0
  18. data/grammar_tests/a.modl +1 -0
  19. data/grammar_tests/b.modl +1 -0
  20. data/grammar_tests/base_tests.json +996 -0
  21. data/grammar_tests/c.modl +1 -0
  22. data/grammar_tests/demo_config.modl +9 -0
  23. data/grammar_tests/error_tests.json +70 -0
  24. data/grammar_tests/import_config.modl +9 -0
  25. data/grammar_tests/test_import_dir/nested_import1.txt +1 -0
  26. data/grammar_tests/test_import_dir/nested_import2.txt +1 -0
  27. data/grammar_tests/test_import_dir/nested_import3.txt +1 -0
  28. data/grammar_tests/test_import_dir/test_import.txt +9 -0
  29. data/lib/modl/interpreter.rb +10 -0
  30. data/lib/modl/parser/MODLLexer.interp +136 -0
  31. data/lib/modl/parser/MODLLexer.rb +324 -0
  32. data/lib/modl/parser/MODLLexer.tokens +41 -0
  33. data/lib/modl/parser/MODLParser.interp +95 -0
  34. data/lib/modl/parser/MODLParser.rb +2504 -0
  35. data/lib/modl/parser/MODLParser.tokens +41 -0
  36. data/lib/modl/parser/MODLParserBaseListener.rb +164 -0
  37. data/lib/modl/parser/MODLParserBaseVisitor.rb +107 -0
  38. data/lib/modl/parser/MODLParserListener.rb +151 -0
  39. data/lib/modl/parser/MODLParserVisitor.rb +56 -0
  40. data/lib/modl/parser/class_processor.rb +159 -0
  41. data/lib/modl/parser/evaluator.rb +164 -0
  42. data/lib/modl/parser/file_importer.rb +64 -0
  43. data/lib/modl/parser/global_parse_context.rb +249 -0
  44. data/lib/modl/parser/instruction_processor.rb +58 -0
  45. data/lib/modl/parser/interpreter.rb +38 -0
  46. data/lib/modl/parser/modl_class.rb +102 -0
  47. data/lib/modl/parser/modl_index.rb +30 -0
  48. data/lib/modl/parser/modl_keylist.rb +43 -0
  49. data/lib/modl/parser/modl_method.rb +132 -0
  50. data/lib/modl/parser/object_cache.rb +54 -0
  51. data/lib/modl/parser/parsed.rb +1410 -0
  52. data/lib/modl/parser/parser.rb +42 -0
  53. data/lib/modl/parser/ref_processor.rb +139 -0
  54. data/lib/modl/parser/substitutions.rb +67 -0
  55. data/lib/modl/parser/sutil.rb +78 -0
  56. data/lib/modl/parser/throwing_error_listener.rb +20 -0
  57. data/lib/modl/parser/version.rb +5 -0
  58. data/modl.gemspec +32 -0
  59. metadata +138 -11
  60. data/lib/modl.rb +0 -5
@@ -0,0 +1,1410 @@
1
+ require 'modl/parser/MODLParserBaseListener'
2
+ require 'modl/parser/global_parse_context'
3
+ require 'modl/parser/ref_processor'
4
+ require 'modl/parser/substitutions'
5
+ require 'modl/parser/file_importer'
6
+ require 'antlr4/runtime/parse_cancellation_exception'
7
+ require 'modl/parser/sutil'
8
+ require 'modl/parser/modl_class'
9
+ require 'modl/parser/modl_method'
10
+ require 'modl/parser/modl_index'
11
+ require 'modl/parser/modl_keylist'
12
+ require 'modl/parser/evaluator'
13
+ require 'cgi'
14
+ require 'net/http'
15
+
16
+ module Modl
17
+ module Parser
18
+ # This class represents a MODL parse tree for a given MODL object.
19
+ # It tries to process the parse tree as it is generated as much as
20
+ # possible to save revisiting nodes unnecessarily.
21
+ #
22
+ # Many of the method names are generated by ANTLR4 so are not ruby style.
23
+ class Parsed < Modl::Parser::MODLParserBaseListener
24
+ attr_accessor :structures
25
+ attr_accessor :global
26
+
27
+ def initialize(global = nil)
28
+ @global = global
29
+ @structures = []
30
+ end
31
+
32
+ def enterModl(ctx)
33
+
34
+ @global = GlobalParseContext.new if @global.nil?
35
+
36
+ ctx_modl_structure = ctx.modl_structure
37
+ ctx_modl_structure.each do |str|
38
+ structure = ParsedStructure.new @global
39
+ str.enter_rule(structure)
40
+ @structures << structure
41
+ end
42
+
43
+ @global
44
+ end
45
+
46
+ def self.additional_string_processing(text)
47
+ text = Substitutions.process text
48
+ # Special case for a possibly empty graved string ``
49
+ unless text.nil?
50
+ match_data = /^`([^`]*)`$/.match text
51
+ return match_data[1] if match_data&.length&.positive?
52
+ end
53
+ text
54
+ end
55
+
56
+ # Class to represent a parsed grammar object
57
+ class ParsedMap < Modl::Parser::MODLParserBaseListener
58
+ attr_accessor :mapItems
59
+
60
+ def initialize(global)
61
+ @global = global
62
+ @mapItems = []
63
+ end
64
+
65
+ def find_property(key)
66
+ if key.is_a? Integer
67
+ return @mapItems[key]
68
+ else
69
+ @mapItems.each do |mi|
70
+ return mi.pair if mi.pair.key == key
71
+ end
72
+ end
73
+ end
74
+
75
+ def enterModl_map(ctx)
76
+ modl_map_item = ctx.modl_map_item
77
+ return if modl_map_item.nil?
78
+
79
+ modl_map_item.each do |mi|
80
+ map_item = ParsedMapItem.new @global
81
+ mi.enter_rule(map_item)
82
+ @mapItems << map_item
83
+ end
84
+ end
85
+
86
+ def extract_hash
87
+ result = {}
88
+ @mapItems.each do |i|
89
+ i_hash = i.extract_hash
90
+ next unless i_hash.is_a? Hash
91
+
92
+ i_hash.keys.each do |k|
93
+ result[k] = i_hash[k]
94
+ end
95
+ end
96
+ result.is_a?(Array) && result.length == 1 ? result[0] : result
97
+ end
98
+
99
+ end
100
+
101
+ # Class to represent a parsed grammar object
102
+ class ParsedMapItem < Modl::Parser::MODLParserBaseListener
103
+ attr_accessor :pair
104
+ attr_accessor :mapConditional
105
+
106
+ def initialize(global)
107
+ @global = global
108
+ end
109
+
110
+ def enterModl_map_item(ctx)
111
+ modl_pair = ctx.modl_pair
112
+ unless modl_pair.nil?
113
+ @pair = ParsedPair.new @global
114
+ modl_pair.enter_rule(@pair)
115
+ end
116
+ modl_map_conditional = ctx.modl_map_conditional
117
+ return if modl_map_conditional.nil?
118
+
119
+ @mapConditional = ParsedMapConditional.new @global
120
+ modl_map_conditional.enter_rule(@mapConditional)
121
+ end
122
+
123
+ def extract_hash
124
+ return @pair.extract_hash if @pair
125
+ return @mapConditional.extract_hash if @mapConditional
126
+ end
127
+
128
+ end
129
+
130
+ # Class to represent a parsed grammar object
131
+ class ParsedStructure < Modl::Parser::MODLParserBaseListener
132
+ attr_accessor :array
133
+ attr_accessor :pair
134
+ attr_accessor :top_level_conditional
135
+ attr_accessor :map
136
+
137
+ def initialize(global)
138
+ @global = global
139
+ end
140
+
141
+ def enterModl_structure(ctx)
142
+ modl_pair = ctx.modl_pair
143
+ modl_top_level_conditional = ctx.modl_top_level_conditional
144
+ modl_map = ctx.modl_map
145
+ modl_array = ctx.modl_array
146
+
147
+ if !modl_pair.nil?
148
+ @pair = ParsedPair.new @global
149
+ modl_pair.enter_rule(@pair)
150
+ elsif !modl_top_level_conditional.nil?
151
+ @top_level_conditional = ParsedTopLevelConditional.new @global
152
+ modl_top_level_conditional.enter_rule(@top_level_conditional)
153
+ elsif !modl_map.nil?
154
+ @map = ParsedMap.new @global
155
+ modl_map.enter_rule(@map)
156
+ elsif !modl_array.nil?
157
+ @array = ParsedArray.new @global
158
+ modl_array.enter_rule(@array)
159
+ end
160
+ end
161
+
162
+ def extract_hash
163
+ return @array.extract_hash if @array
164
+ return @pair.extract_hash if @pair
165
+ return @top_level_conditional.extract_hash if @top_level_conditional
166
+ return @map.extract_hash if @map
167
+ end
168
+ end
169
+
170
+ # Class to represent a parsed grammar object
171
+ class ParsedPair < Modl::Parser::MODLParserBaseListener
172
+ attr_accessor :key
173
+ attr_accessor :map
174
+ attr_accessor :array
175
+ attr_accessor :valueItem
176
+ attr_accessor :key_lists
177
+ attr_accessor :type # A string set to the type of pair that we have found bases on its key
178
+ attr_accessor :text # The simple text value rather than the object
179
+ attr_accessor :final
180
+
181
+ def initialize(global)
182
+ @global = global
183
+ @needs_defref = true
184
+ @final = false
185
+ @file_importer = FileImporter.new
186
+ end
187
+
188
+ def find_property(key)
189
+ return self if key == @key
190
+
191
+ return @map.find_property(key) if @map
192
+ return @array.find_property(key) if @array
193
+ return @valueItem.find_property(key) if @valueItem
194
+ end
195
+
196
+ # Set the appropriate field base on the value type
197
+ def set_value(value)
198
+ if value.is_a? Array
199
+ @map = nil
200
+ @array = ParsedArray.new @global
201
+ @array.abstractArrayItems = []
202
+ value.each do |item|
203
+ array_item = ParsedArrayItem.new @global
204
+ array_item.arrayValueItem = ParsedArrayValueItem.new @global
205
+ array_item.arrayValueItem.primitive = ParsedPrimitive.new(@global)
206
+ array_item.arrayValueItem.primitive.string = ParsedString.new(item)
207
+ array_item.arrayValueItem.primitive.text = item
208
+ @array.abstractArrayItems << array_item
209
+ end
210
+ @valueItem = nil
211
+ @text = @array.extract_hash
212
+ return
213
+ elsif value.is_a?(ParsedPair)
214
+ @map = value.map ? value.map : nil
215
+ @array = value.array ? value.array : nil
216
+ @valueItem = value.valueItem ? value.valueItem : nil
217
+ return
218
+ elsif value.is_a?(TrueClass) || value.is_a?(FalseClass)
219
+ @map = nil
220
+ @array = nil
221
+ @valueItem = ParsedValueItem.new @global
222
+ @valueItem.value = ParsedValue.new @global
223
+ @valueItem.value.primitive = ParsedPrimitive.new(@global)
224
+ if value
225
+ @valueItem.value.primitive.trueVal = ParsedTrue.instance
226
+ else
227
+ @valueItem.value.primitive.falseVal = ParsedFalse.instance
228
+ end
229
+ @valueItem.value.primitive.text = value
230
+ @valueItem.value.text = value
231
+ @text = value
232
+ return
233
+ end
234
+ value = value.extract_hash unless value.is_a?(String) || value.is_a?(Integer)
235
+ @map = nil
236
+ @array = nil
237
+ @valueItem = ParsedValueItem.new @global
238
+ @valueItem.value = ParsedString.new(value)
239
+ @text = value
240
+ end
241
+
242
+ # Convert this object to a simple hash ready for JSON.generate
243
+ def extract_hash
244
+
245
+ value = @array.extract_hash if @array
246
+ value = @valueItem.extract_hash if @valueItem
247
+ value = @map.extract_hash if @map
248
+
249
+ if value.is_a?(String) && (value.start_with?('%') || value.start_with?('`'))
250
+ @text, _ignore = RefProcessor.deref(@text, @global)
251
+ else
252
+ @text = value
253
+ end
254
+
255
+ return if @type == 'index'
256
+ return if @type == 'hidden'
257
+ return if @type == 'version'
258
+ return if @type == 'class'
259
+ return if @type == 'method'
260
+ return if @type == 'import'
261
+ return if @type == 'allow'
262
+
263
+ {@key => @text}
264
+ end
265
+
266
+ def enterModl_pair(ctx)
267
+ @type = 'pair' # default the type to an ordinary pair
268
+
269
+ ctx_string = ctx.STRING
270
+ @key = ctx_string.to_s unless ctx_string.nil?
271
+ ctx_string = ctx.NUMBER
272
+ @key = ctx_string.to_s unless ctx_string.nil?
273
+ ctx_quoted = ctx.QUOTED
274
+ unless ctx_quoted.nil?
275
+ @key = ctx_quoted.to_s
276
+ @key = Sutil.toptail(@key) # remove the quotes
277
+ end
278
+
279
+ if @key.include?('%') || @key.include?('`')
280
+ @key, new_value = RefProcessor.deref @key, @global
281
+ unless @key.is_a?(String)
282
+ @key = new_value.is_a?(String) ? new_value : new_value.text
283
+ end
284
+ raise InterpreterError, "Error: '" + @key.to_s + "' should de-ref to a string." unless key.is_a?(String)
285
+ end
286
+
287
+ @final = true if @key.upcase == @key
288
+
289
+ set_pair_type
290
+
291
+ raise InterpreterError, 'Invalid keyword: ' + @key if @type == 'pair' && @key.start_with?('*')
292
+
293
+ modl_array = ctx.modl_array
294
+ modl_map = ctx.modl_map
295
+ modl_value_item = ctx.modl_value_item
296
+
297
+ if !modl_array.nil?
298
+ @array = ParsedArray.new @global
299
+ modl_array.enter_rule(@array)
300
+ elsif !modl_map.nil?
301
+ @map = ParsedMap.new @global
302
+ modl_map.enter_rule(@map)
303
+ elsif !modl_value_item.nil?
304
+ @valueItem = ParsedValueItem.new @global
305
+ modl_value_item.enter_rule(@valueItem)
306
+ end
307
+
308
+ validate_key if @type == 'pair' || @type == 'hidden'
309
+
310
+ # Type-specific processing
311
+ case @type
312
+ when 'class'
313
+ ClassExtractor.extract(self, @global)
314
+ when 'id'
315
+ extract_value
316
+ when 'name'
317
+ extract_value
318
+ when 'superclass'
319
+ extract_value
320
+ when 'allow'
321
+ extract_value
322
+ when 'keylist'
323
+ KeylistExtractor.extract(self, @valueItem) if @valueItem
324
+ KeylistExtractor.extract(self, @array) if @array
325
+ when 'version'
326
+ extract_value
327
+
328
+ raise InterpreterError, 'Invalid MODL version: nil' if @valueItem.value.primitive.number.nil?
329
+ raise InterpreterError, 'Invalid MODL version: ' + @valueItem.value.primitive.number.num.to_s if @valueItem.value.primitive.number.num.is_a? Float
330
+ raise InterpreterError, 'Invalid MODL version: ' + @valueItem.value.primitive.number.num.to_s if @valueItem.value.primitive.number.num.zero?
331
+ raise InterpreterError, 'MODL version should be on the first line if specified.' if @global.has_pairs?
332
+ @global.syntax_version = @valueItem.value.primitive.number.num
333
+
334
+ when 'method'
335
+ MethodExtractor.extract(self, @global)
336
+ when 'transform'
337
+ extract_transform @valueItem
338
+ when 'import'
339
+ files = @valueItem.extract_hash if @valueItem
340
+ files = @array.extract_hash if @array
341
+ @file_importer.import_files files, @global
342
+ when 'index'
343
+ IndexExtractor.extract(self, @global)
344
+ when 'hidden'
345
+ extract_value
346
+ invoke_deref
347
+ else
348
+ extract_value
349
+ invoke_deref
350
+ end
351
+
352
+ return if @global.in_condition? # Don't store pairs in conditionals until we evaluate the conditions
353
+
354
+ if @key.start_with? '_'
355
+ k = Sutil.tail(@key)
356
+ existing = @global.pair(k)
357
+ raise InterpreterError, 'Already defined ' + k + ' as final.' if existing&.final
358
+
359
+ @global.pair(k, self)
360
+ end
361
+ existing = @global.pair(@key)
362
+ raise InterpreterError, 'Already defined ' + @key + ' as final.' if existing&.final
363
+
364
+ @global.pair(@key, self)
365
+ end
366
+
367
+ private
368
+
369
+ def extract_value
370
+ item = @valueItem
371
+ @text = item.value.text if item.is_a?(ParsedValueItem) && item.value
372
+ @text = item.valueItem.value.text if item.is_a?(ParsedPair)
373
+ invoke_deref
374
+ end
375
+
376
+ def extract_transform item
377
+ @transform = item.value.primitive.string.string
378
+ end
379
+
380
+ def validate_key
381
+ invalid_chars = "!$@-+'*#^&"
382
+ invalid_chars.each_char do |c|
383
+ next unless @key.include?(c)
384
+
385
+ raise InterpreterError, 'Invalid key - "' + c + '" character not allowed: ' + @key
386
+ end
387
+
388
+ key = @key.start_with?('_') ? Sutil.tail(@key) : @key
389
+ raise InterpreterError, 'Invalid key - "' + key + '" - entirely numeric keys are not allowed: ' + @key if key == key.to_i.to_s
390
+ end
391
+
392
+ def invoke_deref
393
+ return unless @needs_defref && !@text.nil? && @text.is_a?(String) && @text.include?('%')
394
+
395
+ @needs_defref = false
396
+ @text, new_value = RefProcessor.deref @text, @global
397
+
398
+ if new_value.is_a? ParsedMap
399
+ @map = new_value
400
+ @valueItem = nil
401
+ @array = nil
402
+ elsif new_value.is_a? ParsedArray
403
+ @array = new_value
404
+ @valueItem = nil
405
+ @map = nil
406
+ elsif new_value.is_a? ParsedValueItem
407
+ @valueItem = new_value
408
+ elsif new_value.nil?
409
+ set_value @text
410
+ elsif new_value.is_a? ParsedPair
411
+ set_value @text if @text
412
+ set_value new_value if @text.nil?
413
+ elsif new_value.is_a? String
414
+ set_value @text
415
+ else
416
+ set_value(new_value)
417
+ end
418
+ end
419
+
420
+ # Set the pair type if its a 'special' type
421
+ def set_pair_type
422
+ @type = 'class' if @key == '*c' || @key == '*class'
423
+ if @key == '*C' || @key == '*CLASS'
424
+ @type = 'class'
425
+ @key = @key.downcase
426
+ end
427
+ @type = 'id' if @key == '*i' || @key == '*id'
428
+ @type = 'name' if @key == '*n' || @key == '*name'
429
+ @type = 'name' if @key == '*N' || @key == '*NAME'
430
+ @type = 'superclass' if @key == '*S' || @key == '*SUPERCLASS'
431
+ @type = 'superclass' if @key == '*s' || @key == '*superclass'
432
+ @type = 'keylist' if @key == '*a' || @key == '*assign'
433
+ @type = 'version' if @key == '*V' || @key == '*VERSION'
434
+ @type = 'method' if @key == '*m' || @key == '*method'
435
+ @type = 'transform' if @key == '*t' || @key == '*transform'
436
+ if @key == '*L' || @key == '*LOAD'
437
+ @key = @key.downcase
438
+ @type = 'import'
439
+ end
440
+ if @key == '*l' || @key == '*load'
441
+ @type = 'import'
442
+ end
443
+ @type = 'index' if @key == '?'
444
+ @type = 'hidden' if @key.start_with? '_'
445
+ @type = 'allow' if @key.downcase == '*allow'
446
+ end
447
+ end
448
+
449
+ # Class to represent a parsed grammar object
450
+ class ParsedArrayValueItem < Modl::Parser::MODLParserBaseListener
451
+ attr_accessor :map
452
+ attr_accessor :array
453
+ attr_accessor :pair
454
+ attr_accessor :primitive
455
+ attr_accessor :text # The simple text value rather than the object
456
+
457
+ def initialize(global)
458
+ @global = global
459
+ end
460
+
461
+ def find_property(key)
462
+ return @map.find_property(key) if @map
463
+ return @array.find_property(key) if @array
464
+ return @nbArray.find_property(key) if @nbArray
465
+ return @pair.find_property(key) if @pair
466
+ return @primitive.find_property(key) if @primitive
467
+ end
468
+
469
+ def extract_hash
470
+ return @map.extract_hash if @map
471
+ return @array.extract_hash if @array
472
+ return @nbArray.extract_hash if @nbArray
473
+ return @pair.extract_hash if @pair
474
+ return @primitive.extract_hash if @primitive
475
+
476
+ @text
477
+ end
478
+
479
+ def enterModl_array_value_item(ctx)
480
+ @text = nil
481
+ modl_map = ctx.modl_map
482
+ modl_array = ctx.modl_array
483
+ modl_pair = ctx.modl_pair
484
+ modl_primitive = ctx.modl_primitive
485
+
486
+ if !modl_map.nil?
487
+ @map = ParsedMap.new @global
488
+ modl_map.enter_rule(@map)
489
+ elsif !modl_array.nil?
490
+ @array = ParsedArray.new @global
491
+ modl_array.enter_rule(@array)
492
+ elsif !modl_pair.nil?
493
+ @pair = ParsedPair.new @global
494
+ modl_pair.enter_rule(@pair)
495
+ elsif !modl_primitive.nil?
496
+ @primitive = ParsedPrimitive.new @global
497
+ modl_primitive.enter_rule(@primitive)
498
+ @text = @primitive.text
499
+ end
500
+
501
+ # ignoring comments!
502
+ end
503
+ end
504
+
505
+ # Class to represent a parsed grammar object
506
+ class ParsedValueItem < Modl::Parser::MODLParserBaseListener
507
+ attr_accessor :value
508
+ attr_accessor :valueConditional
509
+
510
+ def initialize(global)
511
+ @global = global
512
+ end
513
+
514
+ def find_property(key)
515
+ @value.find_property(key) if @value
516
+ end
517
+
518
+ def enterModl_value_item(ctx)
519
+ modl_value_conditional = ctx.modl_value_conditional
520
+ unless modl_value_conditional.nil?
521
+ @valueConditional = ParsedValueConditional.new @global
522
+ modl_value_conditional.enter_rule(@valueConditional)
523
+ end
524
+ modl_value = ctx.modl_value
525
+ return if modl_value.nil?
526
+ @value = ParsedValue.new @global
527
+ modl_value.enter_rule(@value)
528
+ end
529
+
530
+ def extract_hash
531
+ return @value.extract_hash if @value
532
+ return @valueConditional.extract_hash if @valueConditional
533
+ end
534
+
535
+ end
536
+
537
+ # Class to represent a parsed grammar object
538
+ class ParsedValue < Modl::Parser::MODLParserBaseListener
539
+ attr_accessor :map
540
+ attr_accessor :array
541
+ attr_accessor :nbArray
542
+ attr_accessor :pair
543
+ attr_accessor :primitive
544
+ attr_accessor :text # The simple text value rather than the object
545
+
546
+ def initialize(global)
547
+ @global = global
548
+ end
549
+
550
+ def find_property(key)
551
+ return @map.find_property(key) if @map
552
+ return @array.find_property(key) if @array
553
+ return @nbArray.find_property(key) if @nbArray
554
+ return @pair.find_property(key) if @pair
555
+ return @primitive.find_property(key) if @primitive
556
+ end
557
+
558
+ def extract_hash
559
+ return @map.extract_hash if @map
560
+ return @array.extract_hash if @array
561
+ return @nbArray.extract_hash if @nbArray
562
+ return @pair.extract_hash if @pair
563
+ return @primitive.extract_hash if @primitive
564
+
565
+ @text
566
+ end
567
+
568
+ def evaluate
569
+ return @primitive.evaluate if @primitive
570
+
571
+ true
572
+ end
573
+
574
+ def value_obj
575
+ return @map if @map
576
+ return @array if @array
577
+ return @nbArray if @nbArray
578
+ return @pair if @pair
579
+ return @primitive if @primitive
580
+ end
581
+
582
+ def enterModl_value(ctx)
583
+ modl_map = ctx.modl_map
584
+ modl_nb_array = ctx.modl_nb_array
585
+ modl_array = ctx.modl_array
586
+ modl_pair = ctx.modl_pair
587
+ modl_primitive = ctx.modl_primitive
588
+
589
+ if !modl_map.nil?
590
+ @map = ParsedMap.new @global
591
+ modl_map.enter_rule(@map)
592
+ elsif !modl_nb_array.nil?
593
+ @nbArray = ParsedNbArray.new @global
594
+ modl_nb_array.enter_rule(@nbArray)
595
+ elsif !modl_array.nil?
596
+ @array = ParsedArray.new @global
597
+ modl_array.enter_rule(@array)
598
+ elsif !modl_pair.nil?
599
+ @pair = ParsedPair.new @global
600
+ modl_pair.enter_rule(@pair)
601
+ elsif !modl_primitive.nil?
602
+ @primitive = ParsedPrimitive.new @global
603
+ modl_primitive.enter_rule(@primitive)
604
+ @text = @primitive.text
605
+ end
606
+ # ignoring comments!
607
+ end
608
+ end
609
+
610
+ # Class to represent a parsed grammar object
611
+ class ParsedPrimitive < Modl::Parser::MODLParserBaseListener
612
+ attr_accessor :quoted
613
+ attr_accessor :number
614
+ attr_accessor :trueVal
615
+ attr_accessor :falseVal
616
+ attr_accessor :nilVal
617
+ attr_accessor :string
618
+ attr_accessor :constant
619
+ attr_accessor :text # The simple text value rather than the object
620
+
621
+ def initialize(global)
622
+ @global = global
623
+ @constant = false
624
+ end
625
+
626
+ def find_property(key)
627
+ if @string
628
+ user_method = @global.user_method(key)
629
+ if user_method
630
+ return user_method.run(@string.string)
631
+ end
632
+ return StandardMethods.run_method(key, @string.string)
633
+ end
634
+ end
635
+
636
+ def extract_hash
637
+ result, _ignore = RefProcessor.deref(@text, @global) unless @constant
638
+ result = @text if @constant
639
+ result
640
+ end
641
+
642
+ def evaluate
643
+ return false if @nilVal
644
+ return false if @falseVal
645
+
646
+ true
647
+ end
648
+
649
+ def value_obj
650
+ return @quoted if @quoted
651
+ return @number if @number
652
+ return @trueVal if @trueVal
653
+ return @falseVal if @falseVal
654
+ return @nilVal if @nilVal
655
+ return @string if @string
656
+
657
+ @text
658
+ end
659
+
660
+ def enterModl_primitive(ctx)
661
+ ctx_number = ctx.NUMBER
662
+ ctx_string = ctx.STRING
663
+ ctx_quoted = ctx.QUOTED
664
+ ctx_null = ctx.NULL
665
+ ctx_true = ctx.TRUE
666
+ ctx_false = ctx.FALSE
667
+
668
+ if !ctx_number.nil?
669
+ @number = ParsedNumber.new(ctx_number.text)
670
+ @text = @number.num
671
+ elsif !ctx_string.nil?
672
+ @text = ctx_string.text
673
+ @constant = @text.start_with?('`') && !@text.include?('%') && !@text.include?('`.')
674
+ @text = Parsed.additional_string_processing(@text)
675
+ @string = ParsedString.new(@text)
676
+ @text = @string.string
677
+ elsif !ctx_quoted.nil?
678
+ @constant = true
679
+ @text = Sutil.toptail(ctx_quoted.text) # remove the quotes
680
+ @text = Parsed.additional_string_processing(@text)
681
+ @quoted = ParsedQuoted.new(@text)
682
+ elsif !ctx_null.nil?
683
+ @nilVal = ParsedNull.instance
684
+ @text = nil
685
+ elsif !ctx_true.nil?
686
+ @trueVal = ParsedTrue.instance
687
+ @text = true
688
+ elsif !ctx_false.nil?
689
+ @falseVal = ParsedFalse.instance
690
+ @text = false
691
+ end
692
+ # ignoring comments!
693
+ end
694
+ end
695
+
696
+ # Class to represent a parsed grammar object
697
+ class ParsedString
698
+ attr_accessor :string
699
+
700
+ def initialize(string)
701
+ @string = string
702
+ end
703
+
704
+ def text
705
+ @string
706
+ end
707
+
708
+ def extract_hash
709
+ @string
710
+ end
711
+ end
712
+
713
+ # Class to represent a parsed grammar object
714
+ class ParsedNumber
715
+ attr_accessor :num
716
+
717
+ def initialize(string)
718
+ @num = string.include?('.') ? string.to_f : string.to_i
719
+ end
720
+ end
721
+
722
+ # Class to represent a parsed grammar object
723
+ class ParsedQuoted
724
+ attr_accessor :string
725
+
726
+ def initialize(string)
727
+ @string = string
728
+ end
729
+ end
730
+
731
+ # Class to represent a parsed grammar object
732
+ class ParsedConditionTest < Modl::Parser::MODLParserBaseListener
733
+ attr_accessor :subConditionList
734
+
735
+ def initialize(global)
736
+ @global = global
737
+ @subConditionList = []
738
+ end
739
+
740
+ def evaluate
741
+ result = false
742
+ @subConditionList.each do |s|
743
+ last_operator = s.b.a
744
+ should_negate = s.b.b
745
+
746
+ partial = s.a.evaluate
747
+ case last_operator
748
+ when '&'
749
+ result &= should_negate ? !partial : partial
750
+ when '|'
751
+ result |= should_negate ? !partial : partial
752
+ else
753
+ result |= should_negate ? !partial : partial
754
+ end
755
+ end
756
+ result
757
+ end
758
+
759
+ def enterModl_condition_test(ctx)
760
+ ctx_children = ctx.children
761
+ unless ctx_children.empty?
762
+ last_operator = nil
763
+ should_negate = false
764
+ ctx_children.each do |child|
765
+ if child.is_a? MODLParser::Modl_condition_groupContext
766
+ condition_group = ParsedConditionGroup.new @global
767
+ child.enter_rule(condition_group)
768
+
769
+ p2 = OpenStruct.new
770
+ p2.a = last_operator
771
+ p2.b = should_negate
772
+
773
+ p1 = OpenStruct.new
774
+ p1.a = condition_group
775
+ p1.b = p2
776
+
777
+ @subConditionList << p1
778
+
779
+ last_operator = nil
780
+ should_negate = false
781
+ elsif child.is_a? MODLParser::Modl_conditionContext
782
+ condition = ParsedCondition.new @global
783
+ child.enter_rule(condition)
784
+ p2 = OpenStruct.new
785
+ p2.a = last_operator
786
+ p2.b = should_negate
787
+
788
+ p1 = OpenStruct.new
789
+ p1.a = condition
790
+ p1.b = p2
791
+
792
+ @subConditionList << p1
793
+
794
+ last_operator = nil
795
+ should_negate = false
796
+ else
797
+ if child.text == '!'
798
+ should_negate = true
799
+ else
800
+ last_operator = child.text
801
+ end
802
+ end
803
+ end
804
+ end
805
+ end
806
+ end
807
+
808
+ # Class to represent a parsed grammar object
809
+ class ParsedConditionGroup < Modl::Parser::MODLParserBaseListener
810
+ attr_accessor :conditionsTestList
811
+
812
+ def initialize(global)
813
+ @global = global
814
+ @conditionsTestList = []
815
+ end
816
+
817
+ def evaluate
818
+ result = false
819
+ @conditionsTestList.each do |s|
820
+ partial = s.a.evaluate
821
+ result |= partial
822
+ end
823
+ result
824
+ end
825
+
826
+ def enterModl_condition_group(ctx)
827
+ ctx_children = ctx.children
828
+ return if ctx_children.empty?
829
+
830
+ last_operator = nil
831
+ ctx_children.each do |child|
832
+ if child.is_a? MODLParser::Modl_condition_testContext
833
+ condition_test = ParsedConditionTest.new @global
834
+ child.enter_rule(condition_test)
835
+ p = OpenStruct.new
836
+ p.a = condition_test
837
+ p.b = last_operator
838
+ @conditionsTestList << p
839
+ last_operator = nil
840
+ else
841
+ last_operator = child.text if (child.text != '') && (child.text != '}')
842
+ end
843
+ end
844
+ end
845
+ end
846
+
847
+ # Class to represent a parsed grammar object
848
+ class ParsedCondition < Modl::Parser::MODLParserBaseListener
849
+ attr_accessor :values
850
+ attr_accessor :operator
851
+ attr_accessor :text
852
+
853
+ def initialize(global)
854
+ @global = global
855
+ @values = []
856
+ end
857
+
858
+ def evaluate
859
+ Evaluator.evaluate(@global, self)
860
+ end
861
+
862
+ def enterModl_condition(ctx)
863
+ modl_operator = ctx.modl_operator
864
+ @operator = modl_operator.text unless modl_operator.nil?
865
+ modl_value = ctx.modl_value
866
+ modl_value.each do |v|
867
+ value = ParsedValue.new @global
868
+ v.enter_rule(value)
869
+ @values << value
870
+ end
871
+ ctx_string = ctx.STRING
872
+ if !ctx_string.nil?
873
+ @text = Parsed.additional_string_processing(ctx_string.text)
874
+ @string = ParsedString.new(@text)
875
+ @text = @string.string
876
+ end
877
+ end
878
+ end
879
+
880
+ # Class to represent a parsed grammar object
881
+ class ParsedMapConditionalReturn < Modl::Parser::MODLParserBaseListener
882
+ attr_accessor :mapItems
883
+
884
+ def initialize(global)
885
+ @global = global
886
+ @mapItems = []
887
+ end
888
+
889
+ def extract_hash
890
+ @mapItems[0].extract_hash
891
+ end
892
+
893
+ def enterModl_map_conditional_return(ctx)
894
+ modl_map_item = ctx.modl_map_item
895
+ return if modl_map_item.empty?
896
+
897
+ modl_map_item.each do |mi|
898
+ map_item = ParsedMapItem.new @global
899
+ mi.enter_rule(map_item)
900
+ @mapItems << map_item
901
+ end
902
+ end
903
+ end
904
+
905
+ # Class to represent a parsed grammar object
906
+ class ParsedMapConditional < Modl::Parser::MODLParserBaseListener
907
+ attr_accessor :conditionTests
908
+ attr_accessor :mapConditionalReturns
909
+
910
+ def initialize(global)
911
+ @global = global
912
+ @conditionTests = []
913
+ @mapConditionalReturns = []
914
+ end
915
+
916
+ def extract_hash
917
+ result = @conditionTests[0].evaluate
918
+ return @mapConditionalReturns[0].extract_hash if result
919
+
920
+ @mapConditionalReturns[1].extract_hash
921
+ end
922
+
923
+ def enterModl_map_conditional(ctx)
924
+ i = 0
925
+ modl_condition_test = ctx.modl_condition_test
926
+ ctx_modl_map_conditional_return = ctx.modl_map_conditional_return
927
+
928
+ while i < modl_condition_test.size
929
+ condition_test = ParsedConditionTest.new @global
930
+ ctx.modl_condition_test_i(i).enter_rule(condition_test)
931
+
932
+ conditional_return = ParsedMapConditionalReturn.new @global
933
+ ctx.modl_map_conditional_return_i(i).enter_rule(conditional_return)
934
+ @conditionTests[i] = condition_test
935
+ @mapConditionalReturns[i] = conditional_return
936
+
937
+ if ctx_modl_map_conditional_return.size > modl_condition_test.size
938
+ i += 1
939
+ conditional_return = ParsedMapConditionalReturn.new @global
940
+ ctx.modl_map_conditional_return_i(ctx_modl_map_conditional_return.size - 1).enter_rule(conditional_return)
941
+ @mapConditionalReturns[i] = conditional_return
942
+ end
943
+ i += 1
944
+ end
945
+ end
946
+ end
947
+
948
+ # Class to represent a parsed grammar object
949
+ class ParsedTopLevelConditionalReturn < Modl::Parser::MODLParserBaseListener
950
+ attr_accessor :structures
951
+
952
+ def initialize(global)
953
+ @global = global
954
+ @structures = []
955
+ end
956
+
957
+ def extract_hash
958
+ return @structures[0].extract_hash if @structures.length == 1
959
+
960
+ result = []
961
+ @structures.each do |s|
962
+ hash = s.extract_hash
963
+ result << hash unless hash.nil?
964
+ end
965
+ return result unless result.length == 1
966
+ return result[0] if result.length == 1
967
+ end
968
+
969
+ def enterModl_top_level_conditional_return(ctx)
970
+ modl_structure = ctx.modl_structure
971
+ return if modl_structure.empty?
972
+
973
+ modl_structure.each do |str|
974
+ structure = ParsedStructure.new @global
975
+ str.enter_rule(structure)
976
+ @structures << structure
977
+ end
978
+ end
979
+ end
980
+
981
+ # Class to represent a parsed grammar object
982
+ class ParsedTopLevelConditional < Modl::Parser::MODLParserBaseListener
983
+ attr_accessor :conditionTests
984
+ attr_accessor :topLevelConditionalReturns
985
+
986
+ def initialize(global)
987
+ @global = global
988
+ @topLevelConditionalReturns = []
989
+ @conditionTests = []
990
+ end
991
+
992
+ def extract_hash
993
+ @conditionTests.each_index do |i|
994
+ next unless @conditionTests[i].evaluate
995
+
996
+ item = @topLevelConditionalReturns[i]
997
+ if item.structures[0].pair
998
+ key = item.structures[0].pair.key
999
+ key = Sutil.tail(key) if key[0] == '_'
1000
+ @global.pair(key, item.structures[0].pair)
1001
+ end
1002
+ return item.extract_hash
1003
+ end
1004
+ return unless @topLevelConditionalReturns.length > @conditionTests.length
1005
+
1006
+ last_item = @topLevelConditionalReturns[-1]
1007
+ if last_item.structures[0].pair
1008
+ key = last_item.structures[0].pair.key
1009
+ key = Sutil.tail(key) if key[0] == '_'
1010
+ @global.pair(key, last_item.structures[0].pair)
1011
+ end
1012
+ last_item.extract_hash
1013
+ end
1014
+
1015
+ def enterModl_top_level_conditional(ctx)
1016
+ @global.enter_condition
1017
+ i = 0
1018
+ modl_condition_test = ctx.modl_condition_test
1019
+ ctx_modl_top_level_conditional_return = ctx.modl_top_level_conditional_return
1020
+
1021
+ while i < modl_condition_test.size
1022
+ condition_test = ParsedConditionTest.new @global
1023
+ ctx.modl_condition_test_i(i).enter_rule(condition_test)
1024
+
1025
+ conditional_return = ParsedTopLevelConditionalReturn.new @global
1026
+ ctx.modl_top_level_conditional_return_i(i).enter_rule(conditional_return)
1027
+ @conditionTests[i] = condition_test
1028
+ @topLevelConditionalReturns[i] = conditional_return
1029
+ i += 1
1030
+ end
1031
+ if ctx_modl_top_level_conditional_return.size > modl_condition_test.size
1032
+ conditional_return = ParsedTopLevelConditionalReturn.new @global
1033
+ ctx.modl_top_level_conditional_return_i(ctx_modl_top_level_conditional_return.size - 1).enter_rule(conditional_return)
1034
+ @topLevelConditionalReturns[i] = conditional_return
1035
+ end
1036
+ @global.exit_condition
1037
+ end
1038
+ end
1039
+
1040
+ # Class to represent a parsed grammar object
1041
+ class ParsedArrayConditionalReturn < Modl::Parser::MODLParserBaseListener
1042
+ attr_accessor :arrayItems
1043
+
1044
+ def initialize(global)
1045
+ @global = global
1046
+ @arrayItems = []
1047
+ end
1048
+
1049
+ def extract_hash
1050
+ @arrayItems[0].arrayValueItem.text
1051
+ end
1052
+
1053
+ def enterModl_array_conditional_return(ctx)
1054
+ modl_array_item = ctx.modl_array_item
1055
+ return if modl_array_item.empty?
1056
+
1057
+ modl_array_item.each do |ai|
1058
+ array_item = ParsedArrayItem.new @global
1059
+ ai.enter_rule(array_item)
1060
+ @arrayItems << array_item
1061
+ end
1062
+ end
1063
+ end
1064
+
1065
+ # Class to represent a parsed grammar object
1066
+ class ParsedArrayConditional < Modl::Parser::MODLParserBaseListener
1067
+ attr_accessor :conditionTest
1068
+ attr_accessor :arrayConditionalReturns
1069
+
1070
+ def initialize(global)
1071
+ @global = global
1072
+ @conditionTests = []
1073
+ @arrayConditionalReturns = []
1074
+ end
1075
+
1076
+ def extract_hash
1077
+ result = @conditionTests[0].evaluate
1078
+ return @arrayConditionalReturns[0].extract_hash if result
1079
+ @arrayConditionalReturns[1].extract_hash
1080
+ end
1081
+
1082
+ def enterModl_array_conditional(ctx)
1083
+ i = 0
1084
+ ctx_modl_condition_test = ctx.modl_condition_test
1085
+ ctx_modl_array_conditional_return = ctx.modl_array_conditional_return
1086
+
1087
+ while i < ctx_modl_condition_test.size
1088
+ condition_test = ParsedConditionTest.new @global
1089
+ ctx.modl_condition_test_i(i).enter_rule(condition_test)
1090
+
1091
+ conditional_return = ParsedArrayConditionalReturn.new @global
1092
+ ctx.modl_array_conditional_return_i(i).enter_rule(conditional_return)
1093
+ @conditionTests[i] = condition_test
1094
+ @arrayConditionalReturns[i] = conditional_return
1095
+
1096
+ if ctx_modl_array_conditional_return.size > ctx_modl_condition_test.size
1097
+ i += 1
1098
+ condition_test = ParsedConditionTest.new @global
1099
+ conditional_return = ParsedArrayConditionalReturn.new @global
1100
+ ctx.modl_array_conditional_return_i(ctx_modl_array_conditional_return.size - 1).enter_rule(conditional_return)
1101
+ @conditionTests[i] = condition_test
1102
+ @arrayConditionalReturns[i] = conditional_return
1103
+ end
1104
+
1105
+
1106
+ i += 1
1107
+ end
1108
+ end
1109
+ end
1110
+
1111
+ # Class to represent a parsed grammar object
1112
+ class ParsedValueConditionalReturn < Modl::Parser::MODLParserBaseListener
1113
+ attr_accessor :valueItems
1114
+
1115
+ def initialize(global)
1116
+ @global = global
1117
+ @valueItems = []
1118
+ end
1119
+
1120
+ def extract_hash
1121
+ return @valueItems[0].value.text if @valueItems[0].value.text
1122
+
1123
+ return @valueItems[0].value.extract_hash
1124
+ end
1125
+
1126
+ def enterModl_value_conditional_return(ctx)
1127
+ modl_value_item = ctx.modl_value_item
1128
+ return if modl_value_item.empty?
1129
+
1130
+ modl_value_item.each do |vi|
1131
+ valueItem = ParsedValueItem.new @global
1132
+ vi.enter_rule(valueItem)
1133
+ @valueItems << valueItem
1134
+ end
1135
+ end
1136
+ end
1137
+
1138
+ # Class to represent a parsed grammar object
1139
+ class ParsedValueConditional < Modl::Parser::MODLParserBaseListener
1140
+ attr_accessor :conditionTests
1141
+ attr_accessor :valueConditionalReturns
1142
+
1143
+ def initialize(global)
1144
+ @global = global
1145
+ @conditionTests = []
1146
+ @valueConditionalReturns = []
1147
+ end
1148
+
1149
+ def extract_hash
1150
+ result = @conditionTests[0].evaluate
1151
+
1152
+ return result if @valueConditionalReturns.length == 0
1153
+ return @valueConditionalReturns[0].extract_hash if result
1154
+ return @valueConditionalReturns[1].extract_hash
1155
+ end
1156
+
1157
+ def enterModl_value_conditional(ctx)
1158
+ i = 0
1159
+ ctx_modl_condition_test = ctx.modl_condition_test
1160
+ ctx_modl_value_conditional_return = ctx.modl_value_conditional_return
1161
+
1162
+ while i < ctx_modl_condition_test.size
1163
+ condition_test = ParsedConditionTest.new @global
1164
+ ctx.modl_condition_test_i(i).enter_rule(condition_test)
1165
+
1166
+ @conditionTests[i] = condition_test
1167
+
1168
+ return if ctx.modl_value_conditional_return_i(i).nil?
1169
+
1170
+ conditional_return = ParsedValueConditionalReturn.new @global
1171
+
1172
+ ctx.modl_value_conditional_return_i(i).enter_rule(conditional_return)
1173
+
1174
+ @valueConditionalReturns[i] = conditional_return
1175
+
1176
+ if ctx_modl_value_conditional_return.size > ctx_modl_condition_test.size
1177
+ condition_test = ParsedConditionTest.new @global
1178
+ conditional_return = ParsedValueConditionalReturn.new @global
1179
+ ctx.modl_value_conditional_return_i(ctx_modl_value_conditional_return.size - 1).enter_rule(conditional_return)
1180
+ @conditionTests[i + 1] = condition_test
1181
+ @valueConditionalReturns[i + 1] = conditional_return
1182
+ end
1183
+
1184
+ i += 1
1185
+ end
1186
+ end
1187
+ end
1188
+
1189
+ # Class to represent a parsed grammar object
1190
+ class ParsedNbArray < Modl::Parser::MODLParserBaseListener
1191
+ attr_accessor :arrayItems
1192
+
1193
+ def initialize(global)
1194
+ @global = global
1195
+ @arrayItems = []
1196
+ end
1197
+
1198
+ def find_property(key)
1199
+ if key.is_a? Integer
1200
+ return @arrayItems[key].arrayValueItem
1201
+ else
1202
+ @arrayItems.each do |mi|
1203
+ return mi.arrayValueItem.pair if mi.arrayValueItem.pair && mi.arrayValueItem.pair.key == key
1204
+ end
1205
+ nil
1206
+ end
1207
+ end
1208
+
1209
+ def extract_hash
1210
+ result = []
1211
+
1212
+ @arrayItems.each do |i|
1213
+ result << i.extract_hash
1214
+ end
1215
+
1216
+ result
1217
+ end
1218
+
1219
+ def enterModl_nb_array(ctx)
1220
+ i = 0
1221
+ previous = nil
1222
+ ctx_children = ctx.children
1223
+ ctx_children.each do |pt|
1224
+ if pt.is_a? MODLParser::Modl_array_itemContext
1225
+ array_item = ParsedArrayItem.new @global
1226
+ pt.enter_rule(array_item)
1227
+ @arrayItems[i] = array_item
1228
+ i += 1
1229
+ elsif pt.is_a? Antlr4::Runtime::TerminalNode
1230
+ if !previous.nil? && previous.is_a?(Antlr4::Runtime::TerminalNode) && pt.is_a?(Antlr4::Runtime::TerminalNode)
1231
+ # If we get here then we have two terminal nodes in a row, so we need to output something unless # the terminal symbols are newlines
1232
+ #
1233
+ prev_symbol = previous.symbol.type
1234
+ current_symbol = pt.symbol.type
1235
+
1236
+ if prev_symbol == MODLLexer::COLON && current_symbol == MODLLexer::COLON
1237
+ array_item = Parsed.handle_empty_array_item
1238
+ @arrayItems[i] = array_item
1239
+ i += 1
1240
+ end
1241
+ end
1242
+ end
1243
+ previous = pt
1244
+ end
1245
+ end
1246
+ end
1247
+
1248
+ def self.handle_empty_array_item
1249
+ # Create something for the blank array item
1250
+ #
1251
+ # The problem is that we might not have any context to tell us what type we need to create
1252
+ # so this currently defaults to the nil value
1253
+ #
1254
+ # TODO : Is there a way to know the type to create or is nil always acceptable?
1255
+ array_item = ParsedArrayItem.new @global
1256
+ array_item.arrayValueItem = ParsedArrayValueItem.new @global
1257
+ array_item.arrayValueItem.primitive = ParsedPrimitive.new @global
1258
+ array_item.arrayValueItem.primitive.nilVal = ParsedNull.instance
1259
+ array_item
1260
+ end
1261
+
1262
+ # Class to represent a parsed grammar object
1263
+ class ParsedArray < Modl::Parser::MODLParserBaseListener
1264
+ # We now have a list of < array_item | nb_array >
1265
+ attr_accessor :abstractArrayItems
1266
+
1267
+ def initialize(global)
1268
+ @global = global
1269
+ @abstractArrayItems = []
1270
+ end
1271
+
1272
+ def find_property(key)
1273
+ if key.is_a? Integer
1274
+ return @abstractArrayItems[key]
1275
+ else
1276
+ @abstractArrayItems.each do |mi|
1277
+ return mi.arrayValueItem.pair if mi.arrayValueItem.pair && mi.arrayValueItem.pair.key == key
1278
+ end
1279
+ nil
1280
+ end
1281
+ end
1282
+
1283
+ def extract_hash
1284
+ result = []
1285
+
1286
+ abstractArrayItems.each do |i|
1287
+ result << i.extract_hash
1288
+ end
1289
+
1290
+ result
1291
+ end
1292
+
1293
+ def enterModl_array(ctx)
1294
+ # Create the new abstractArrayItems list first, sized to the total of array_item.size and nb_array.size
1295
+ i = 0
1296
+ previous = nil
1297
+ ctx_children = ctx.children
1298
+ ctx_children.each do |pt|
1299
+ if pt.is_a? MODLParser::Modl_array_itemContext
1300
+ array_item = ParsedArrayItem.new @global
1301
+ pt.enter_rule(array_item)
1302
+ @abstractArrayItems[i] = array_item
1303
+ i += 1
1304
+ elsif pt.is_a? MODLParser::Modl_nb_arrayContext
1305
+ nb_array = ParsedNbArray.new @global
1306
+ pt.enter_rule(nb_array)
1307
+ @abstractArrayItems[i] = nb_array
1308
+ i += 1
1309
+ elsif pt.is_a? Antlr4::Runtime::TerminalNode
1310
+ if !previous.nil? && previous.is_a?(Antlr4::Runtime::TerminalNode) && pt.is_a?(Antlr4::Runtime::TerminalNode)
1311
+
1312
+ # If we get here then we have two terminal nodes in a row, so we need to output something unless # the terminal symbols are newlines
1313
+ #
1314
+ prev_symbol = previous.symbol.type
1315
+ current_symbol = pt.symbol.type
1316
+
1317
+ if prev_symbol == MODLLexer::LSBRAC && current_symbol == MODLLexer::RSBRAC
1318
+ next # This allows empty arrays
1319
+ end
1320
+
1321
+ if prev_symbol == MODLLexer::STRUCT_SEP && current_symbol == MODLLexer::STRUCT_SEP
1322
+
1323
+ # Create something for the blank array item
1324
+ #
1325
+ # The problem is that we might not have any context to tell us what type we need to create
1326
+ # so this currently defaults to the nil
1327
+ #
1328
+ # TODO : Is there a way to know the type to create or is nil always acceptable?
1329
+ array_item = Parsed.handle_empty_array_item
1330
+
1331
+ @abstractArrayItems[i] = array_item
1332
+ i += 1
1333
+ end
1334
+ end
1335
+ end
1336
+ previous = pt
1337
+ end
1338
+ end
1339
+ end
1340
+
1341
+ # Class to represent a parsed grammar object
1342
+ class ParsedArrayItem < Modl::Parser::MODLParserBaseListener
1343
+ attr_accessor :arrayValueItem
1344
+ attr_accessor :arrayConditional
1345
+
1346
+ def initialize(global)
1347
+ @global = global
1348
+ end
1349
+
1350
+ def find_property(key)
1351
+ return @arrayValueItem.find_property(key)
1352
+ end
1353
+
1354
+ def enterModl_array_item(ctx)
1355
+ ctx_modl_array_conditional = ctx.modl_array_conditional
1356
+ unless ctx_modl_array_conditional.nil?
1357
+ @arrayConditional = ParsedArrayConditional.new @global
1358
+ ctx_modl_array_conditional.enter_rule(@arrayConditional)
1359
+ end
1360
+ ctx_modl_array_value_item = ctx.modl_array_value_item
1361
+ unless ctx_modl_array_value_item.nil?
1362
+ @arrayValueItem = ParsedArrayValueItem.new @global
1363
+ ctx_modl_array_value_item.enter_rule(@arrayValueItem)
1364
+ end
1365
+ end
1366
+
1367
+ def extract_hash
1368
+ return @arrayValueItem.extract_hash if @arrayValueItem
1369
+ return @arrayConditional.extract_hash if @arrayConditional
1370
+ end
1371
+
1372
+ end
1373
+
1374
+ # Singleton class to represent a true value
1375
+ class ParsedTrue < Modl::Parser::MODLParserBaseListener
1376
+ include Singleton
1377
+ end
1378
+
1379
+ # Singleton class to represent a false value
1380
+ class ParsedFalse < Modl::Parser::MODLParserBaseListener
1381
+ include Singleton
1382
+ end
1383
+
1384
+ # Singleton class to represent a null value
1385
+ class ParsedNull < Modl::Parser::MODLParserBaseListener
1386
+ include Singleton
1387
+ end
1388
+
1389
+ # Convert the parse tree to a simpler structure suitable for JSON.generate.
1390
+ def extract_hash
1391
+ result = []
1392
+ if @structures.length.positive?
1393
+ @structures.each do |s|
1394
+ value = s.extract_hash
1395
+ result << value unless value.nil?
1396
+ end
1397
+ else
1398
+ result = {}
1399
+ end
1400
+ case result.length
1401
+ when 0
1402
+ return ''
1403
+ when 1
1404
+ return result[0]
1405
+ end
1406
+ result
1407
+ end
1408
+ end
1409
+ end
1410
+ end