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,164 @@
1
+ module Modl
2
+ module Parser
3
+ # Evaluate a conditional expression
4
+ class Evaluator
5
+ # Evaluate the given condition
6
+ def self.evaluate(global, condition)
7
+ return false if global.nil? || !global.is_a?(GlobalParseContext) || !condition.is_a?(Modl::Parser::Parsed::ParsedCondition)
8
+
9
+ start = 0
10
+ if condition.text
11
+ value1, success = value(global, condition.text)
12
+ else
13
+ start = 1
14
+ value1, success = value(global, condition.values[0].text)
15
+ end
16
+
17
+
18
+ # Handle single-value conditions of the form '{x?}'
19
+ if condition.values.length == start
20
+ return false if value1.nil?
21
+ return false if value1.is_a?(FalseClass)
22
+ return true if value1.is_a?(TrueClass)
23
+
24
+ return success ? value1 : false
25
+ end
26
+
27
+ # Handle the right-hand side, which might have many values and operators, e.g. '{x=a|b|c|d?}'
28
+ i = start
29
+ result = false
30
+ while i < condition.values.length
31
+ item = condition.values[i]
32
+ if item.primitive.constant
33
+ value2 = item.text
34
+ else
35
+ value2, success = value(global, item.text)
36
+ end
37
+ partial = false
38
+ case condition.operator
39
+ when '='
40
+ wild = value2.is_a?(String) && value2.include?('*') ? true : false
41
+ if wild
42
+ regex = '^'
43
+ value2.each_char do |c|
44
+ regex << (c == '*' ? '.*' : c)
45
+ end
46
+ partial |= !value1.match(regex).nil?
47
+ else
48
+ partial |= value1 == value2
49
+ end
50
+ when '>'
51
+ partial |= value1 > value2
52
+ when '<'
53
+ partial |= value1 < value2
54
+ when '>='
55
+ partial |= value1 >= value2
56
+ when '<='
57
+ partial |= value1 <= value2
58
+ end
59
+ i += 1
60
+ result |= partial
61
+ end
62
+ result
63
+ end
64
+
65
+ def self.value(global, k)
66
+ success = false
67
+ if k.is_a?(String) && k.include?('%')
68
+ value1, _ignore = Modl::Parser::RefProcessor.deref(k, global)
69
+ success = true
70
+ elsif k.is_a?(FalseClass)
71
+ value1 = false
72
+ success = true
73
+ elsif k.is_a?(TrueClass)
74
+ value1 = true
75
+ success = true
76
+ elsif k.is_a?(NilClass)
77
+ value1 = nil
78
+ else
79
+ key = k
80
+ ikey = key.to_i
81
+ if ikey.to_s == key
82
+ index_val = global.index[ikey]
83
+ value1 = index_val.respond_to?(:text) ? index_val.text : nil
84
+ else
85
+ pair = global.pair(key)
86
+ return k unless pair
87
+
88
+ value1 = pair.text
89
+ end
90
+ success = true
91
+ end
92
+ [value1, success]
93
+ end
94
+
95
+ def evaluate_old
96
+ result = false
97
+ if @key
98
+ if @key.include?('%')
99
+ value1, _ignore = Modl::Parser::RefProcessor.deref(@key, @global)
100
+ else
101
+ key = @key
102
+ ikey = key.to_i
103
+ if ikey.to_s == key
104
+ index_val = @global.index[ikey]
105
+ value1 = index_val.respond_to?(:text) ? index_val.text : nil
106
+ else
107
+ pair = @global.pair(key)
108
+ return false unless pair
109
+
110
+ value1 = pair.text
111
+ end
112
+ end
113
+
114
+ @values.each do |value|
115
+ value2 = value.text
116
+ value2, _ignore = Modl::Parser::RefProcessor.deref(value2, @global) if value2.is_a?(String) && value2.include?('%')
117
+ value2 = @global.pair(value.text).text if @global.pair(value.text)
118
+
119
+ case @operator
120
+ when '='
121
+ wild = value2.is_a?(String) && value2.include?('*') ? true : false
122
+ if wild
123
+ regex = '^'
124
+ value2.each_char do |c|
125
+ regex << (c == '*' ? '.*' : c)
126
+ end
127
+ result |= !value1.match(regex).nil?
128
+ else
129
+ result |= value1 == value2
130
+ end
131
+ when '>'
132
+ result |= value1 > value2
133
+ when '<'
134
+ result |= value1 < value2
135
+ when '>='
136
+ result |= value1 >= value2
137
+ when '<='
138
+ result |= value1 <= value2
139
+ end
140
+ break if result # shortcut if we have a matching value
141
+ end
142
+ elsif @values.length == 1
143
+ key = @values[0].text
144
+ if key.is_a?(String)
145
+ key = key.start_with?('%') ? Sutil.tail(key) : key
146
+ end
147
+ the_pair = @global.pair(key)
148
+ if the_pair
149
+ result = the_pair.text
150
+ else
151
+ return true if @values[0].trueVal
152
+ return false if @values[0].falseVal
153
+ return false if @values[0].string
154
+
155
+ result = @values[0].evaluate
156
+ end
157
+ end
158
+ result
159
+ end
160
+
161
+ end
162
+ end
163
+ end
164
+
@@ -0,0 +1,64 @@
1
+ require 'modl/parser/object_cache'
2
+ require 'modl/parser/sutil'
3
+
4
+ module Modl
5
+ module Parser
6
+
7
+ # This class handled file loading from local or remote file systems.
8
+ class FileImporter
9
+
10
+ def initialize
11
+ @cache = ObjectCache.new
12
+ end
13
+
14
+ # Supply a single file name as a string or an array of file names.
15
+ def import_files(files, global)
16
+ file_names = []
17
+ file_names += files if files.is_a? Array
18
+ file_names << files if files.is_a? String
19
+
20
+ file_names.each do |file_name|
21
+ force = file_name.end_with?('!')
22
+ if force
23
+ # Don't use the cache if we're forcing a reload.
24
+ @cache.evict(file_name)
25
+ file_name = Sutil.head(file_name)
26
+ parsed = nil
27
+ else
28
+ # Do we have a cached version?
29
+ parsed = @cache.get(file_name)
30
+ end
31
+
32
+ # Did we hit the cache?
33
+ unless parsed
34
+ # No.
35
+ file_name << '.modl' unless file_name.end_with?('.txt', '.modl')
36
+ file_name, new_val = RefProcessor.deref file_name, global if file_name.include?('%')
37
+
38
+ begin
39
+ uri = URI(file_name)
40
+ txt = Net::HTTP.get(uri)
41
+ rescue
42
+ begin
43
+ txt = File.readlines(file_name).join
44
+ rescue
45
+ raise InterpreterError, 'File not found: ' + file_name
46
+ end
47
+ end
48
+
49
+ global.loaded_file(file_name)
50
+
51
+ # Parse the downloaded file ands extract the classes
52
+ parsed = Modl::Parser::Parser.parse txt, global
53
+ # Save it for next time
54
+ @cache.put(file_name, parsed)
55
+ end
56
+ # Extract the JSON content and add the classes and pairs to the existing GlobalParseContext hashes.
57
+ parsed.extract_hash
58
+ global.merge_classes(parsed.global)
59
+ global.merge_pairs(parsed.global)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,249 @@
1
+ module Modl
2
+ module Parser
3
+ # Each time we run the parser on a MODL file we need to keep track of a few things so they can be made available
4
+ # to other areas of the code. This class is the container for that contextual information, which is gathered
5
+ # as the Modl::Parser:Parsed object processes the parse tree.
6
+ class GlobalParseContext
7
+
8
+ attr_accessor :syntax_version
9
+ attr_reader :interpreter_syntax_version
10
+
11
+ def initialize
12
+ # Holds the index array from a MODL file, e.g. '?=a:b:c:d'
13
+ @index = []
14
+ # Contains all pairs as they are encountered in the parsing process.
15
+ @pairs = {}
16
+ # Contains all defined and loaded classes for the current MODL document.
17
+ @classes_by_id = {}
18
+ @classes_by_name = {}
19
+ # Hold the user-defined methods.
20
+ @methods_hash = {}
21
+ @methods_by_id = {}
22
+ # Tracks the nesting depth for conditional clauses.
23
+ @conditional = 0
24
+ # Defaults to 1 and can be overridden by the *version command.
25
+ @syntax_version = 1
26
+ @interpreter_syntax_version = 1
27
+ @loaded_files = []
28
+ end
29
+
30
+ def loaded_file(str)
31
+ @loaded_files << str unless str.nil?
32
+ end
33
+
34
+ def index_value(n, default)
35
+ return default if n > @index.length
36
+
37
+ @index[n]
38
+ end
39
+
40
+ def in_condition?
41
+ @conditional.positive?
42
+ end
43
+
44
+ def enter_condition
45
+ @conditional += 1
46
+ end
47
+
48
+ def exit_condition
49
+ @conditional -= 1
50
+ end
51
+
52
+ def pair(key, val = nil)
53
+ return @pairs[key] unless val
54
+
55
+ @pairs[key] = val
56
+ end
57
+
58
+ def classs(key)
59
+ if key.is_a? String
60
+ result = @classes_by_id[key]
61
+ result = @classes_by_name[key] if result.nil?
62
+ result
63
+ elsif key.is_a? MODLClass
64
+ @classes_by_id[key.id] = key if key.id
65
+ @classes_by_name[key.name] = key if key.name
66
+ end
67
+ end
68
+
69
+ def merge_pairs(other)
70
+ @pairs.merge!(other.all_pairs)
71
+ end
72
+
73
+ def merge_classes(other)
74
+ @classes_by_id.merge!(other.all_classes_by_id)
75
+ @classes_by_name.merge!(other.all_classes_by_name)
76
+ end
77
+
78
+ def has_pairs?
79
+ @pairs.length.positive?
80
+ end
81
+
82
+ def has_class?(key)
83
+ @classes_by_id.keys.include?(key) || @classes_by_name.keys.include?(key)
84
+ end
85
+
86
+ def has_user_method?(key)
87
+ @methods_hash.keys.include?(key)
88
+ end
89
+
90
+ def add_to_index(item)
91
+ @index << item
92
+ end
93
+
94
+ def user_method(key, val = nil)
95
+ return @methods_hash[key] unless val
96
+
97
+ @methods_hash[key] = val
98
+ end
99
+
100
+ def user_method_id(key, val)
101
+ @methods_hash[key] = val
102
+ @methods_by_id[key] = val
103
+ end
104
+
105
+ def class_list
106
+ result = []
107
+ @classes_by_id.values.each do |clazz|
108
+ new_item = {}
109
+ new_item[clazz.id] = class_to_hash(clazz)
110
+ result << new_item
111
+ end
112
+ result
113
+ end
114
+
115
+ def method_list
116
+ result = []
117
+ @methods_by_id.values.each do |m|
118
+ new_item = {}
119
+ new_item[m.id] = method_to_hash(m)
120
+ result << new_item
121
+ end
122
+ result
123
+ end
124
+
125
+ def file_list
126
+ @loaded_files.dup
127
+ end
128
+
129
+ def id_list
130
+ ids = {}
131
+ ids['methods'] = @methods_by_id.keys.dup.sort!
132
+ ids['classes'] = @classes_by_id.keys.dup.sort!
133
+ ids
134
+ end
135
+
136
+ def name_list
137
+ names = {}
138
+ names['methods'] = @methods_hash.keys.dup.sort!
139
+ names['classes'] = @classes_by_name.keys.dup.sort!
140
+ names
141
+ end
142
+
143
+ def superclass_list
144
+ result = {}
145
+ @classes_by_id.each do |c|
146
+ result[c[0]] = c[1].superclass
147
+ end
148
+ result
149
+ end
150
+
151
+ def assign_list
152
+ result = {}
153
+ @classes_by_id.each do |c|
154
+ result[c[0]] = c[1].assign
155
+ end
156
+ result
157
+ end
158
+
159
+ def transform_list
160
+ result = {}
161
+ @methods_by_id.each do |c|
162
+ result[c[0]] = c[1].transform
163
+ end
164
+ result
165
+ end
166
+
167
+ def allow_list
168
+ result = {}
169
+ @classes_by_id.each do |c|
170
+ result[c[0]] = c[1].allow.nil? ? nil : c[1].allow.extract_hash
171
+ end
172
+ result
173
+ end
174
+
175
+ protected
176
+
177
+ def all_classes_by_id
178
+ @classes_by_id
179
+ end
180
+
181
+ def all_classes_by_name
182
+ @classes_by_name
183
+ end
184
+
185
+ def all_pairs
186
+ @pairs
187
+ end
188
+
189
+ private
190
+
191
+ def class_to_hash(clazz)
192
+ map = {}
193
+ # name
194
+ map['name'] = clazz.name
195
+
196
+ # superclass
197
+ map['superclass'] = clazz.superclass
198
+
199
+ # assign
200
+ if clazz.assign
201
+ map['assign'] = clazz.assign
202
+ end
203
+
204
+ # allow
205
+ if clazz.allow
206
+ map['allow'] = clazz.allow.extract_hash
207
+ end
208
+
209
+ # content
210
+ if clazz.content.length.positive?
211
+ clazz.content.each do |item|
212
+ map[item[0]] = item[1].extract_hash
213
+ end
214
+ end
215
+
216
+ map
217
+ end
218
+
219
+ def method_to_hash(mthd)
220
+ map = {}
221
+ # name
222
+ map['name'] = mthd.name
223
+
224
+ # superclass
225
+ map['transform'] = mthd.transform
226
+
227
+ map
228
+ end
229
+
230
+ def new_map_item(key, value)
231
+ map_item = Parsed::ParsedMapItem.new(self)
232
+ map_item.pair = Parsed::ParsedPair.new(self)
233
+ map_item.pair.key = key
234
+ map_item.pair.valueItem = Parsed::ParsedValueItem.new(self)
235
+ if value.is_a? Parsed::ParsedValue
236
+ map_item.pair.valueItem.value = value
237
+ else
238
+ map_item.pair.valueItem.value = Parsed::ParsedValue.new(self)
239
+ map_item.pair.valueItem.value.primitive = Parsed::ParsedPrimitive.new(self)
240
+ map_item.pair.valueItem.value.primitive = Parsed::ParsedPrimitive.new(self)
241
+ map_item.pair.valueItem.value.primitive.string = Parsed::ParsedString.new(value)
242
+ map_item.pair.valueItem.value.primitive.text = value
243
+ map_item.pair.valueItem.value.text = value
244
+ end
245
+ map_item
246
+ end
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,58 @@
1
+ module Modl
2
+ module Parser
3
+ # This class handles the conversion of objects that refer to classes into instances of those classes.
4
+ # It works recursively since class usage can be nested.
5
+ class InstructionProcessor
6
+ def self.process(global, obj)
7
+ if obj.is_a? Hash
8
+ nvals = {}
9
+ obj.keys.each do |k|
10
+ o = obj[k]
11
+ if o.is_a? String
12
+ nv = process_instruction(global, o)
13
+ nvals[k] = nv unless nv.nil?
14
+ else
15
+ process(global, o)
16
+ end
17
+ end
18
+ obj.merge!(nvals)
19
+ elsif obj.is_a? Array
20
+ i = 0
21
+ while i < obj.length
22
+ o = obj[i]
23
+ if o.is_a? String
24
+ nv = process_instruction(global, o)
25
+ obj[i] = nv unless nv.nil?
26
+ else
27
+ process(global, o)
28
+ end
29
+ i += 1
30
+ end
31
+ end
32
+ end
33
+
34
+ def self.process_instruction(global, str)
35
+ case str
36
+ when '%*class'
37
+ return global.class_list
38
+ when '%*method'
39
+ return global.method_list
40
+ when '%*load'
41
+ return global.file_list
42
+ when '%*id'
43
+ return global.id_list
44
+ when '%*name'
45
+ return global.name_list
46
+ when '%*superclass'
47
+ return global.superclass_list
48
+ when '%*assign'
49
+ return global.assign_list
50
+ when '%*transform'
51
+ return global.transform_list
52
+ when '%*allow'
53
+ return global.allow_list
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,38 @@
1
+ require 'modl/parser/MODLParserListener'
2
+ require 'modl/parser/MODLParserVisitor'
3
+ require 'modl/parser/MODLLexer'
4
+ require 'modl/parser/MODLParser'
5
+ require 'modl/parser/class_processor'
6
+ require 'modl/parser/parser'
7
+ require 'json'
8
+
9
+ module Modl
10
+ # Interpreter-specific errors
11
+ class InterpreterError < StandardError
12
+ end
13
+
14
+ # This is the main Ruby Interpreter entry point. Supply a String containing MODL text and it will return a String
15
+ # containing the JSON equivalent. The JSON isn't pretty-printed unless pretty is true
16
+ class Interpreter
17
+ def self.interpret(str, pretty = false)
18
+ # Parse the MODL string into a Modl::Parser::Parsed object.
19
+ parsed = Modl::Parser::Parser.parse str
20
+
21
+ # Convert the Parsed object into a simpler structure of and Array or Hash
22
+ interpreted = parsed.extract_hash
23
+
24
+ # Process any class definitions used by the MODL file.
25
+ Modl::Parser::ClassProcessor.process(parsed.global, interpreted)
26
+ Modl::Parser::InstructionProcessor.process(parsed.global, interpreted)
27
+ # If the result is a simple string then just return it.
28
+ return interpreted if interpreted.is_a? String
29
+
30
+ # Otherwise generate a JSON string.
31
+ if pretty
32
+ JSON.pretty_generate interpreted
33
+ else
34
+ JSON.generate interpreted
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,102 @@
1
+ module Modl
2
+ module Parser
3
+ # Represents a *class defined, or loaded by, a MODL document.
4
+ class MODLClass
5
+ attr_accessor :id
6
+ attr_accessor :name
7
+ attr_accessor :superclass
8
+ attr_accessor :assign
9
+ attr_accessor :content
10
+ attr_accessor :allow
11
+
12
+ def initialize
13
+ @content = {}
14
+ end
15
+
16
+ # Find a keylist of the right length from the *assign array of arrays.
17
+ def keylist_of_length(len)
18
+ return [] if @assign.nil?
19
+
20
+ @assign.each do |kl|
21
+ return kl if kl.length == len
22
+ end
23
+ raise InterpreterError,
24
+ 'No key list of the correct length in class ' + @id + ' - looking for one of length ' + len.to_s
25
+ end
26
+
27
+ def merge_content(new_value)
28
+ @content.each do |k, v|
29
+ new_value[k] = v.extract_hash
30
+ end
31
+ new_value
32
+ end
33
+
34
+ def name_or_id
35
+ @name.nil? ? @id : @name
36
+ end
37
+ end
38
+
39
+ # Extract a class from a ParsedPair object
40
+ class ClassExtractor
41
+ def self.extract(pair, global)
42
+ return unless pair.type == 'class'
43
+
44
+ clazz = MODLClass.new
45
+ map = pair.map if pair.map
46
+ map = pair.valueItem&.value&.map if pair.valueItem&.value&.map
47
+
48
+ map.mapItems.each do |item|
49
+ next unless item&.pair&.type
50
+
51
+ case item&.pair&.type
52
+ when 'id'
53
+ str_value = item.pair.valueItem.value.primitive.string.string
54
+ raise InterpreterError, 'Reserved class id - cannot redefine: ' + str_value if reserved?(str_value)
55
+
56
+ clazz.id = str_value
57
+ when 'name'
58
+ str_value = item.pair.valueItem.value.primitive.string.string
59
+ raise InterpreterError, 'Reserved class name - cannot redefine: ' + str_value if reserved?(str_value)
60
+
61
+ clazz.name = str_value
62
+ when 'superclass'
63
+ str_value = item.pair.valueItem.value.primitive.string.string
64
+ clazz.superclass = str_value
65
+ when 'keylist'
66
+ clazz.assign = item.pair.key_lists
67
+ when 'allow'
68
+ clazz.allow = item.pair.array if item.pair.array
69
+ clazz.allow = item.pair.valueItem.value.array if item.pair.valueItem.value.array
70
+ else
71
+ clazz.content[item.pair.key] = item.pair.array if item.pair.array
72
+ clazz.content[item.pair.key] = item.pair.map if item.pair.map
73
+ clazz.content[item.pair.key] = item.pair.valueItem.value if item.pair.valueItem.value
74
+ end
75
+ end
76
+
77
+ superclass = clazz.superclass
78
+
79
+ if superclass && !reserved?(superclass) && !global.has_class?(superclass)
80
+ raise InterpreterError, 'Invalid superclass: ' + superclass.to_s
81
+ end
82
+ raise InterpreterError, 'Missing id for class' if clazz.id.nil?
83
+
84
+ # Make sure the class name isn't redefining an existing class
85
+ if !global.has_class?(clazz.id) && !global.has_class?(clazz.name)
86
+
87
+ # store the classes by id and name to make them easier to find later
88
+ global.classs(clazz)
89
+ else
90
+ id = clazz.id.nil? ? 'undefined' : clazz.id
91
+ name = clazz.name.nil? ? 'undefined' : clazz.name
92
+ raise InterpreterError, 'Class name or id already defined - cannot redefine: ' + id + ', ' + name
93
+ end
94
+ end
95
+
96
+ # Check for a reserved class name
97
+ def self.reserved?(str)
98
+ %w[map str arr num].include?(str)
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,30 @@
1
+ module Modl
2
+ module Parser
3
+ # Extracts an index definition from a ParsedPair
4
+ class IndexExtractor
5
+ def self.extract(pair, global)
6
+ item = pair.valueItem if pair.valueItem
7
+ item = pair.array if pair.array
8
+
9
+ # collect all values from the object
10
+ if item.is_a? Parsed::ParsedValueItem
11
+ if item&.value&.text
12
+ global.index << item.value.text
13
+ elsif item&.value&.array
14
+ item.value.array.abstractArrayItems.each do |avi|
15
+ global.add_to_index(avi.arrayValueItem)
16
+ end
17
+ elsif item&.value&.nbArray
18
+ item.value.nbArray.arrayItems.each do |avi|
19
+ global.add_to_index(avi.arrayValueItem)
20
+ end
21
+ end
22
+ elsif item.is_a? Parsed::ParsedArray
23
+ item.abstractArrayItems.each do |avi|
24
+ global.add_to_index(avi.arrayValueItem)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end