modl 0.3.26 → 0.3.28

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -149
  3. data/Gemfile +4 -2
  4. data/LICENSE.txt +1 -1
  5. data/README.md +19 -11
  6. data/Rakefile +5 -3
  7. data/lib/modl/interpreter.rb +38 -0
  8. data/lib/modl/model/model.rb +264 -0
  9. data/lib/modl/parser/parser.rb +280 -59
  10. data/lib/modl/tokeniser/context.rb +113 -0
  11. data/lib/modl/tokeniser/tokeniser.rb +28 -0
  12. data/lib/modl/util/functions.rb +74 -0
  13. data/lib/modl/util/unicode.rb +44 -0
  14. data/lib/modl/version.rb +5 -0
  15. data/lib/modl.rb +7 -32
  16. data/modl.gemspec +8 -11
  17. metadata +16 -75
  18. data/.DS_Store +0 -0
  19. data/.idea/vcs.xml +0 -6
  20. data/.rspec +0 -3
  21. data/.rubocop.yml +0 -5
  22. data/.travis.yml +0 -7
  23. data/bin/console +0 -14
  24. data/bin/setup +0 -8
  25. data/lib/modl/parser/MODLLexer.interp +0 -132
  26. data/lib/modl/parser/MODLLexer.rb +0 -324
  27. data/lib/modl/parser/MODLLexer.tokens +0 -40
  28. data/lib/modl/parser/MODLParser.interp +0 -93
  29. data/lib/modl/parser/MODLParser.rb +0 -2492
  30. data/lib/modl/parser/MODLParser.tokens +0 -40
  31. data/lib/modl/parser/MODLParserBaseListener.rb +0 -164
  32. data/lib/modl/parser/MODLParserBaseVisitor.rb +0 -107
  33. data/lib/modl/parser/MODLParserListener.rb +0 -151
  34. data/lib/modl/parser/MODLParserVisitor.rb +0 -56
  35. data/lib/modl/parser/class_processor.rb +0 -411
  36. data/lib/modl/parser/evaluator.rb +0 -125
  37. data/lib/modl/parser/file_importer.rb +0 -101
  38. data/lib/modl/parser/global_parse_context.rb +0 -318
  39. data/lib/modl/parser/instruction_processor.rb +0 -82
  40. data/lib/modl/parser/interpreter.rb +0 -75
  41. data/lib/modl/parser/modl_class.rb +0 -138
  42. data/lib/modl/parser/modl_index.rb +0 -54
  43. data/lib/modl/parser/modl_keylist.rb +0 -81
  44. data/lib/modl/parser/modl_method.rb +0 -172
  45. data/lib/modl/parser/object_cache.rb +0 -88
  46. data/lib/modl/parser/orphan_handler.rb +0 -98
  47. data/lib/modl/parser/parsed.rb +0 -1469
  48. data/lib/modl/parser/ref_processor.rb +0 -258
  49. data/lib/modl/parser/substitutions.rb +0 -101
  50. data/lib/modl/parser/sutil.rb +0 -108
  51. data/lib/modl/parser/throwing_error_listener.rb +0 -44
  52. data/lib/modl/parser/unicode_escape_replacer.rb +0 -148
  53. data/lib/modl/parser/unicode_escapes.rb +0 -112
  54. data/lib/modl/parser/version.rb +0 -29
@@ -1,411 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # The MIT License (MIT)
4
- #
5
- # Copyright (c) 2019 NUM Technology Ltd
6
- #
7
- # Permission is hereby granted, free of charge, to any person obtaining a copy
8
- # of this software and associated documentation files (the "Software"), to deal
9
- # in the Software without restriction, including without limitation the rights
10
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
- # copies of the Software, and to permit persons to whom the Software is
12
- # furnished to do so, subject to the following conditions:
13
- #
14
- # The above copyright notice and this permission notice shall be included in
15
- # all copies or substantial portions of the Software.
16
- #
17
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
- # THE SOFTWARE.
24
-
25
- module MODL
26
- module Parser
27
- # This class handles the conversion of objects that refer to classes into instances of those classes.
28
- # It works recursively since class usage can be nested.
29
- class ClassProcessor
30
- # How deep can the class structure be?
31
- MAX_RECURSION_DEPTH = 50
32
- # global is a GlobalParseContext and obj is the extracted Array or Hash from MODL::Parser::Parsed.extract_json
33
- def self.process(global, obj)
34
- # Process each object in the array or just process the object if its a hash.
35
- # Any other object is ignored.
36
- raise StandardError, 'parameter "global" should be a GlobalParseContext' unless global.is_a?(GlobalParseContext)
37
-
38
- if obj.is_a? Array
39
- root_class = global.classs 'root'
40
- unless root_class.nil?
41
- raise StandardError, 'root class has no *assign statement.' if root_class.assign.nil?
42
- raise StandardError, 'root class *assign statement should be of the form "*assign=[[class_name]]".' if root_class.assign.length > 1 || root_class.assign[0].length > 1
43
- root_class_assign = root_class.assign[0][0]
44
-
45
- array_class = global.classs root_class_assign
46
- classes = array_class.keylist_of_length obj.length
47
- new_obj = []
48
-
49
- # The top level array can be an array of arrays or an array of hashes, so we need to handle both.
50
- obj.each_index do |i|
51
- item = obj[i]
52
- if item.is_a? Array
53
- new_obj << {classes[i] => item}
54
- elsif item.is_a? Hash
55
- new_obj << item
56
- end
57
- end
58
-
59
- obj = new_obj
60
- process_recursive global, obj
61
-
62
- result = []
63
- obj.each_index do |i|
64
- result << obj[i][global.classs(classes[i]).name_or_id]
65
- end
66
-
67
- return result
68
- end
69
- end
70
- process_recursive global, obj
71
- return obj
72
- end
73
-
74
- private
75
-
76
- def self.process_recursive(global, obj)
77
- if obj.is_a? Array
78
- obj.each do |o|
79
- process_obj global, o if o.is_a? Hash
80
- end
81
- elsif obj.is_a? Hash
82
- process_obj global, obj
83
- end
84
- end
85
-
86
- # Process the contents of the supplied hash obj
87
- def self.process_obj(global, obj)
88
- obj.keys.each do |k|
89
- value = obj[k]
90
- # Does the key refer to a class that we have parsed or loaded?
91
- clazz = global.classs(k)
92
- if clazz
93
- # Yes so convert this value to an instance of that class
94
- new_k, new_v = process_class global, k, value
95
- # Replace the existing object with the new class instance and a new key
96
- # We need to keep the same key order, hence this method below
97
- replace_value(obj, k, new_k, new_v)
98
- else
99
- new_v = value
100
- end
101
- # Recurse into the value in case it has contents that also refer to classes.
102
- process_recursive global, new_v
103
- end
104
- end
105
-
106
- def self.replace_value(obj, old_k, new_k, new_v)
107
- tmp = obj.dup
108
- obj.clear
109
- tmp.keys.each do |tmpk|
110
- tmpv = tmp[tmpk]
111
- if tmpk == old_k
112
- obj[new_k] = new_v
113
- else
114
- obj[tmpk] = tmpv
115
- end
116
- end
117
- end
118
-
119
- def self.has_assign_statement?(clazz, global, depth = 0)
120
- # Check for *assign statements
121
- return if depth > MAX_RECURSION_DEPTH
122
- return nil? if clazz.nil?
123
- return true unless clazz.assign.nil?
124
-
125
- superclass = clazz.superclass
126
- c = global.classs(superclass)
127
- return has_assign_statement?(c, global, depth + 1) if c
128
-
129
- false
130
- end
131
-
132
- def self.has_inherited_pairs?(clazz, global, depth = 0)
133
- # Check for *assign statements
134
- return if depth > MAX_RECURSION_DEPTH
135
- return nil? if clazz.nil?
136
- return true unless clazz.content.empty?
137
-
138
- superclass = clazz.superclass
139
- c = global.classs(superclass)
140
- return has_inherited_pairs?(c, global, depth + 1) if c
141
-
142
- false
143
- end
144
-
145
- def self.process_class(global, k, v)
146
- clazz = global.classs(k)
147
- if k != clazz.id && !(v.is_a?(Hash) || v.is_a?(Array))
148
- return [k, v]
149
- end
150
-
151
- if k == clazz.name && !v.is_a?(Array)
152
- new_value = transform_to_class(clazz, global, [v], v.is_a?(Hash))
153
- if new_value.is_a?(Array) && new_value.length == 1
154
- return [clazz.name_or_id, new_value[0]]
155
- else
156
- return [clazz.name_or_id, new_value]
157
- end
158
- end
159
-
160
- new_value = transform_to_class(clazz, global, v, v.is_a?(Hash))
161
-
162
- if v.is_a?(Array)
163
- new_value = v if new_value.empty?
164
- elsif v.is_a?(String)
165
- # Safe to ignore
166
- else
167
- # Safe to ignore
168
- end
169
-
170
- # Check the top class and do some type-specific processing
171
- tc = top_class(clazz, global)
172
- if tc.nil?
173
- # There is no defined top class so we need to infer it base on the value
174
- # and the rules defined here: https://github.com/MODLanguage/grammar/wiki/Class-Supertype-Processing
175
- #
176
- if has_assign_statement?(clazz, global)
177
- if all_assignment_keys_are_classes?(clazz, global)
178
- tc = 'arr'
179
- else
180
- tc = 'map'
181
- end
182
- else
183
- if has_inherited_pairs?(clazz, global)
184
- tc = 'map'
185
- else
186
- if v.is_a? String
187
- tc = 'str'
188
- elsif v.is_a? Numeric
189
- tc = 'num'
190
- elsif (v.is_a? TrueClass) || (v.is_a? FalseClass)
191
- tc = 'bool'
192
- elsif v.nil?
193
- tc = 'null'
194
- elsif v.is_a? Array
195
- tc = 'arr'
196
- elsif v.is_a? Hash
197
- tc = 'map'
198
- end
199
- end
200
- end
201
- end
202
- if tc == 'str'
203
- raise InterpreterError, "Interpreter Error: Cannot convert null value to string." if v.nil?
204
- new_value = v.to_s
205
- elsif tc == 'num'
206
- if (v.is_a? String) && (v.to_i.to_s == v.to_s)
207
- new_value = v.to_i
208
- elsif (v.is_a? String) && (v.to_f.to_s == v.to_s)
209
- new_value = v.to_f
210
- elsif v.is_a? TrueClass
211
- new_value = 1
212
- elsif v.is_a? FalseClass
213
- new_value = 0
214
- elsif v.is_a? Numeric
215
- new_value = v
216
- else
217
- raise InterpreterError, 'Superclass of "' + clazz.id + '" is num - cannot assign value "' + v.to_s + '"'
218
- end
219
- elsif tc == 'bool'
220
- new_value = v
221
- elsif tc == 'null'
222
- new_value = nil
223
- elsif tc == 'arr'
224
- if v.is_a? Array
225
- if new_value.is_a? Hash
226
- new_value = new_value.values
227
- end
228
- elsif v.is_a? Hash
229
- raise InterpreterError, 'Interpreter Error: Cannot convert map to array: ' + v.to_s
230
- else
231
- new_value = [v]
232
- end
233
- elsif tc == 'map'
234
- if new_value.is_a? Hash
235
- # Bring down values from the superclass hierarchy
236
- new_value = copy_from_superclasses(clazz, global, new_value, v)
237
- elsif v.is_a? Array
238
- raise InterpreterError, 'Interpreter Error: Cannot convert array to map: ' + v.to_s
239
- else
240
- new_value = {}
241
- new_value['value'] = v
242
- # Bring down values from the superclass hierarchy
243
- new_value = copy_from_superclasses(clazz, global, new_value, v)
244
- end
245
- elsif tc.nil? && (v.is_a? Hash)
246
- new_value = v
247
- end
248
-
249
- [clazz.name_or_id, new_value]
250
- end
251
-
252
- def self.all_assignment_keys_are_classes? clazz, global
253
- lists = key_lists global, clazz
254
- result = true
255
- lists.each do |list|
256
- list.each do |item|
257
- item = Sutil.head(item) if item.end_with? '*'
258
- global_class = global.classs(item)
259
- result &= (!global_class.nil? && has_assign_statement?(global_class, global))
260
- end
261
- end
262
- result
263
- end
264
-
265
- # Bring down values from the superclass hierarchy
266
- def self.copy_from_superclasses(clazz, global, new_value, v)
267
- if v.is_a? Hash
268
- new_value = v.merge(new_value)
269
- end
270
-
271
- clazz.merge_content(new_value)
272
-
273
- depth = 0
274
- loop do
275
- clazz = global.classs(clazz.superclass)
276
- break if clazz.nil? || depth > MAX_RECURSION_DEPTH
277
-
278
- clazz.merge_content(new_value)
279
- depth += 1
280
- end
281
- new_value
282
- end
283
-
284
- # Transfer the keys from val to the new_value object.
285
- def self.copy_keys_to_new_value(new_value, val)
286
- val.each do |value|
287
- next unless value&.is_a?(Hash)
288
-
289
- new_value.merge!(value)
290
- end
291
- end
292
-
293
- # If the new_value has nested class references then process those recursively as well.
294
- def self.process_nested_classes(global, new_value)
295
- if new_value.is_a?(Hash)
296
-
297
- new_value.keys.each do |nk|
298
- clazz = global.classs(nk)
299
- nv = new_value[nk]
300
- next unless clazz # skip it if it doesn't refer to a class
301
-
302
- if !nv.nil? && !nv.is_a?(String) && !nv.is_a?(Numeric)
303
- new_k, new_v = process_class global, nk, nv
304
- else
305
- new_k = clazz.name_or_id
306
- new_v = nv
307
- end
308
-
309
- # Replace the value for this key if we've changed anything.
310
- if new_value[new_k] != new_v
311
- replace_value(new_value, new_k, new_k, new_v)
312
- end
313
- end
314
- elsif new_value.is_a?(Array)
315
- new_value.each do |nk|
316
- process_nested_classes(global, nk)
317
- end
318
- end
319
- end
320
-
321
- # Process the *assign lists ('keylist' in this code) and any extra pairs defined by the class.
322
- # The id, name, and superclass can be ignored here.
323
- def self.transform_to_class(clazz, global, v, ignore_assign = false)
324
- new_value = {} # the replacement for val after conversion to a class instance
325
- process_nested_classes(global, v)
326
-
327
- # Process the key list if we found one otherwise raise an error
328
- # Slightly different processing for hashes and arrays
329
- unless ignore_assign
330
- raise StandardError, 'cannot use "*assign" to populate a map: ' + clazz.id if has_assign_statement?(clazz, global) && clazz.superclass == 'map' && !v.is_a?(Array)
331
- if v.is_a? Array
332
- keys = key_list(global, clazz, v.length)
333
- if keys.empty?
334
- return v
335
- else
336
- lam = ->(i) { v[i] }
337
- end
338
- elsif !v.is_a?(Hash)
339
- keys = key_list(global, clazz, 1)
340
- lam = ->(i) { v }
341
- return v if keys.length.zero?
342
- else
343
- raise StandardError, 'cannot use "*assign" to populate a map: ' + clazz.id if has_assign_statement?(clazz, global)
344
- end
345
- end
346
-
347
- if keys.nil?
348
- if v.is_a?(Hash)
349
- new_value.merge! v
350
- elsif v.is_a?(Array) && v.length > 0
351
- v.each do |item|
352
- if item.is_a?(Hash)
353
- new_value.merge! item
354
- end
355
- end
356
- end
357
- new_value.keys do |nk|
358
- process_obj global, new_value[nk]
359
- end
360
-
361
- process_nested_classes(global, new_value)
362
- clazz.merge_content(new_value)
363
- elsif v.is_a? String
364
- new_value[keys[0]] = v
365
- new_value
366
- else
367
- keys.each_index do |i|
368
- tmp_value = {keys[i] => v[i]}
369
- process_obj global, tmp_value
370
- if !global.classs(keys[i]).nil? && !tmp_value[keys[i]].nil? && (tmp_value[keys[i]].is_a?(Hash) || tmp_value[keys[i]].is_a?(Array))
371
- new_value[i] = tmp_value[keys[i]]
372
- else
373
- new_value.merge! tmp_value
374
- end
375
- end
376
- new_value
377
- end
378
- end
379
-
380
- # Find a *assign key list of a specific length
381
- def self.key_list(global, clazz, len)
382
- return [] if clazz.nil?
383
- list = clazz.keylist_of_length(len)
384
- return list if !list.nil? && list.length > 0
385
- superclass = global.classs(clazz.superclass)
386
- key_list(global, superclass, len)
387
- end
388
-
389
- # Find all *assign key lists
390
- def self.key_lists(global, clazz)
391
- return [] if clazz.nil?
392
- list = clazz.assign
393
- superclass = global.classs(clazz.superclass)
394
- return list + key_lists(global, superclass)
395
- end
396
-
397
- # Get the top level class for the supplied class
398
- def self.top_class(clazz, global, depth = 0)
399
- # Check for self-referential classes that cause infinite recursion
400
- return if depth > MAX_RECURSION_DEPTH
401
- return nil? if clazz.nil?
402
-
403
- superclass = clazz.superclass
404
- c = global.classs(superclass)
405
- return top_class(c, global, depth + 1) if c
406
-
407
- superclass
408
- end
409
- end
410
- end
411
- end
@@ -1,125 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # The MIT License (MIT)
4
- #
5
- # Copyright (c) 2019 NUM Technology Ltd
6
- #
7
- # Permission is hereby granted, free of charge, to any person obtaining a copy
8
- # of this software and associated documentation files (the "Software"), to deal
9
- # in the Software without restriction, including without limitation the rights
10
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
- # copies of the Software, and to permit persons to whom the Software is
12
- # furnished to do so, subject to the following conditions:
13
- #
14
- # The above copyright notice and this permission notice shall be included in
15
- # all copies or substantial portions of the Software.
16
- #
17
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
- # THE SOFTWARE.
24
-
25
- module MODL
26
- module Parser
27
- # Evaluate a conditional expression
28
- class Evaluator
29
- # Evaluate the given condition
30
- def self.evaluate(global, condition)
31
- return false if global.nil? || !global.is_a?(GlobalParseContext) || !condition.is_a?(MODL::Parser::Parsed::ParsedCondition)
32
-
33
- start = 0
34
- if condition.text
35
- value1, success = value(global, condition.text, true)
36
- else
37
- start = 1
38
- value1, success = value(global, condition.values[0].text, true)
39
- end
40
-
41
-
42
- # Handle single-value conditions of the form '{x?}'
43
- if condition.values.length == start
44
- return false if value1.nil?
45
- return false if value1.is_a?(FalseClass)
46
- return true if value1.is_a?(TrueClass)
47
-
48
- return success ? value1 : false
49
- end
50
-
51
- # Handle the right-hand side, which might have many values and operators, e.g. '{x=a|b|c|d?}'
52
- i = start
53
- result = false
54
- while i < condition.values.length
55
- item = condition.values[i]
56
- if item.primitive.constant
57
- value2 = Substitutions.process(item.text)
58
- else
59
- value2, success = value(global, item.text, false)
60
- end
61
- partial = false
62
- case condition.operator
63
- when '='
64
- wild = value2.is_a?(String) && value2.include?('*') ? true : false
65
- if wild
66
- regex = '^'.dup
67
- value2.each_char do |c|
68
- regex << (c == '*' ? '.*' : c)
69
- end
70
- partial |= !value1.match(regex).nil?
71
- else
72
- partial |= value1 == value2
73
- end
74
- when '>'
75
- partial |= value1 > value2
76
- when '<'
77
- partial |= value1 < value2
78
- when '>='
79
- partial |= value1 >= value2
80
- when '<='
81
- partial |= value1 <= value2
82
- end
83
- i += 1
84
- result |= partial
85
- end
86
- result
87
- end
88
-
89
- def self.value(global, k, replaceFromPairIfPossible)
90
- success = false
91
- if k.is_a?(String) && k.include?('%')
92
- value1, _ignore = MODL::Parser::RefProcessor.deref(k, global)
93
- success = true
94
- elsif k.is_a?(FalseClass)
95
- value1 = false
96
- success = true
97
- elsif k.is_a?(TrueClass)
98
- value1 = true
99
- success = true
100
- elsif k.is_a?(NilClass)
101
- value1 = nil
102
- else
103
- key = k
104
- ikey = key.to_i
105
- if ikey.to_s == key
106
- index_val = global.index[ikey]
107
- value1 = index_val.respond_to?(:text) ? index_val.text : nil
108
- value1 = Substitutions.process(value1)
109
- else
110
- pair = global.pair(key)
111
- return Substitutions.process(k) unless pair
112
- if replaceFromPairIfPossible
113
- value1 = Substitutions.process(pair.text)
114
- else
115
- value1 = k
116
- end
117
- end
118
- success = true
119
- end
120
- [value1, success]
121
- end
122
- end
123
- end
124
- end
125
-
@@ -1,101 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # The MIT License (MIT)
4
- #
5
- # Copyright (c) 2019 NUM Technology Ltd
6
- #
7
- # Permission is hereby granted, free of charge, to any person obtaining a copy
8
- # of this software and associated documentation files (the "Software"), to deal
9
- # in the Software without restriction, including without limitation the rights
10
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
- # copies of the Software, and to permit persons to whom the Software is
12
- # furnished to do so, subject to the following conditions:
13
- #
14
- # The above copyright notice and this permission notice shall be included in
15
- # all copies or substantial portions of the Software.
16
- #
17
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
- # THE SOFTWARE.
24
-
25
- require 'singleton'
26
- require 'modl/parser/object_cache'
27
- require 'modl/parser/sutil'
28
-
29
- module MODL
30
- module Parser
31
-
32
- # This class handled file loading from local or remote file systems.
33
- class FileImporter
34
- include Singleton
35
-
36
- CACHE_DISABLED = false
37
-
38
- def initialize
39
- @cache = ObjectCache.new
40
- end
41
-
42
- # Supply a single file name as a string or an array of file names.
43
- def import_files(files, global)
44
- file_names = []
45
- file_names += files if files.is_a? Array
46
- file_names << files if files.is_a? String
47
-
48
- file_names.each do |file_name|
49
- force = file_name.end_with?('!')
50
- file_name = Sutil.head(file_name) if force
51
- file_name << '.modl' unless file_name.end_with?('.txt', '.modl')
52
- file_name, _new_val = RefProcessor.deref file_name, global if file_name.include?('%')
53
- if force
54
- # Don't use the cache if we're forcing a reload.
55
- @cache.evict(file_name)
56
- parsed = nil
57
- else
58
- # Do we have a cached version?
59
- parsed = @cache.get(file_name)
60
- end
61
-
62
- # Did we hit the cache?
63
- if parsed.nil? || CACHE_DISABLED
64
- # No.
65
-
66
- begin
67
- if file_name.start_with?('http')
68
- uri = URI(file_name)
69
- txt = Net::HTTP.get(uri)
70
- else
71
- txt = File.readlines(file_name).join
72
- end
73
- rescue StandardError => e
74
- # Force load from the cache if possible
75
- parsed = @cache.force_get(file_name)
76
- if parsed.nil?
77
- raise InterpreterError, 'File not found: ' + file_name + ', error: ' + e.message
78
- end
79
- end
80
- global.loaded_file(file_name)
81
-
82
- # Parse the downloaded file ands extract the classes
83
- new_parse_context = GlobalParseContext.new
84
- new_parse_context.merge_pairs(global)
85
- parsed = MODL::Parser::Parser.parse txt, new_parse_context
86
- # Save it for next time
87
- @cache.put(file_name, parsed) unless CACHE_DISABLED
88
- else
89
- global.loaded_file(file_name)
90
- end
91
- # Extract the JSON content and add the classes and pairs to the existing GlobalParseContext hashes.
92
- parsed.extract_hash
93
- global.merge_classes(parsed.global)
94
- global.merge_pairs(parsed.global)
95
- global.merge_loaded_files(parsed.global)
96
- global.structures.concat(parsed.structures)
97
- end
98
- end
99
- end
100
- end
101
- end