modl 0.3.26 → 0.3.27
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1 -149
- data/Gemfile +4 -2
- data/LICENSE.txt +1 -1
- data/README.md +19 -11
- data/Rakefile +5 -3
- data/lib/modl/interpreter.rb +38 -0
- data/lib/modl/model/model.rb +264 -0
- data/lib/modl/parser/parser.rb +272 -59
- data/lib/modl/tokeniser/context.rb +113 -0
- data/lib/modl/tokeniser/tokeniser.rb +28 -0
- data/lib/modl/util/functions.rb +74 -0
- data/lib/modl/util/unicode.rb +44 -0
- data/lib/modl/version.rb +5 -0
- data/lib/modl.rb +7 -32
- data/modl.gemspec +8 -11
- metadata +16 -75
- data/.DS_Store +0 -0
- data/.idea/vcs.xml +0 -6
- data/.rspec +0 -3
- data/.rubocop.yml +0 -5
- data/.travis.yml +0 -7
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/lib/modl/parser/MODLLexer.interp +0 -132
- data/lib/modl/parser/MODLLexer.rb +0 -324
- data/lib/modl/parser/MODLLexer.tokens +0 -40
- data/lib/modl/parser/MODLParser.interp +0 -93
- data/lib/modl/parser/MODLParser.rb +0 -2492
- data/lib/modl/parser/MODLParser.tokens +0 -40
- data/lib/modl/parser/MODLParserBaseListener.rb +0 -164
- data/lib/modl/parser/MODLParserBaseVisitor.rb +0 -107
- data/lib/modl/parser/MODLParserListener.rb +0 -151
- data/lib/modl/parser/MODLParserVisitor.rb +0 -56
- data/lib/modl/parser/class_processor.rb +0 -411
- data/lib/modl/parser/evaluator.rb +0 -125
- data/lib/modl/parser/file_importer.rb +0 -101
- data/lib/modl/parser/global_parse_context.rb +0 -318
- data/lib/modl/parser/instruction_processor.rb +0 -82
- data/lib/modl/parser/interpreter.rb +0 -75
- data/lib/modl/parser/modl_class.rb +0 -138
- data/lib/modl/parser/modl_index.rb +0 -54
- data/lib/modl/parser/modl_keylist.rb +0 -81
- data/lib/modl/parser/modl_method.rb +0 -172
- data/lib/modl/parser/object_cache.rb +0 -88
- data/lib/modl/parser/orphan_handler.rb +0 -98
- data/lib/modl/parser/parsed.rb +0 -1469
- data/lib/modl/parser/ref_processor.rb +0 -258
- data/lib/modl/parser/substitutions.rb +0 -101
- data/lib/modl/parser/sutil.rb +0 -108
- data/lib/modl/parser/throwing_error_listener.rb +0 -44
- data/lib/modl/parser/unicode_escape_replacer.rb +0 -148
- data/lib/modl/parser/unicode_escapes.rb +0 -112
- 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
|