modl 0.0.2 → 0.3.0

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