modl 0.3.25 → 0.3.27
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1 -140
- 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 -84
- 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 -1470
- 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
|