modl 0.3.10 → 0.3.11
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 +5 -0
- data/grammar_tests/b.modl +1 -1
- data/grammar_tests/base_tests.json +2964 -1340
- data/grammar_tests/c.modl +1 -1
- data/grammar_tests/error_tests.json +3 -3
- data/grammar_tests/files/a-b.txt +1 -0
- data/grammar_tests/files/c-d.txt +1 -0
- data/lib/modl/parser/MODLLexer.interp +6 -10
- data/lib/modl/parser/MODLLexer.rb +226 -228
- data/lib/modl/parser/MODLLexer.tokens +25 -26
- data/lib/modl/parser/MODLParser.interp +2 -4
- data/lib/modl/parser/MODLParser.rb +356 -370
- data/lib/modl/parser/MODLParser.tokens +25 -26
- data/lib/modl/parser/evaluator.rb +4 -3
- data/lib/modl/parser/file_importer.rb +2 -2
- data/lib/modl/parser/modl_index.rb +1 -1
- data/lib/modl/parser/modl_method.rb +11 -7
- data/lib/modl/parser/parsed.rb +30 -14
- data/lib/modl/parser/ref_processor.rb +47 -11
- data/lib/modl/parser/substitutions.rb +9 -4
- data/lib/modl/parser/sutil.rb +8 -0
- data/lib/modl/parser/version.rb +1 -1
- metadata +4 -2
@@ -12,30 +12,29 @@ LSBRAC=11
|
|
12
12
|
RSBRAC=12
|
13
13
|
NUMBER=13
|
14
14
|
COMMENT=14
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
RCBRAC=30
|
15
|
+
QUOTED=15
|
16
|
+
STRING=16
|
17
|
+
HASH_PREFIX=17
|
18
|
+
LCBRAC=18
|
19
|
+
CWS=19
|
20
|
+
QMARK=20
|
21
|
+
FSLASH=21
|
22
|
+
GTHAN=22
|
23
|
+
LTHAN=23
|
24
|
+
ASTERISK=24
|
25
|
+
AMP=25
|
26
|
+
PIPE=26
|
27
|
+
EXCLAM=27
|
28
|
+
CCOMMENT=28
|
29
|
+
RCBRAC=29
|
31
30
|
','=8
|
32
|
-
'{'=
|
33
|
-
'?'=
|
34
|
-
'/'=
|
35
|
-
'>'=
|
36
|
-
'<'=
|
37
|
-
'*'=
|
38
|
-
'&'=
|
39
|
-
'|'=
|
40
|
-
'!'=
|
41
|
-
'}'=
|
31
|
+
'{'=18
|
32
|
+
'?'=20
|
33
|
+
'/'=21
|
34
|
+
'>'=22
|
35
|
+
'<'=23
|
36
|
+
'*'=24
|
37
|
+
'&'=25
|
38
|
+
'|'=26
|
39
|
+
'!'=27
|
40
|
+
'}'=29
|
@@ -54,7 +54,7 @@ module MODL
|
|
54
54
|
while i < condition.values.length
|
55
55
|
item = condition.values[i]
|
56
56
|
if item.primitive.constant
|
57
|
-
value2 = item.text
|
57
|
+
value2 = Substitutions.process(item.text)
|
58
58
|
else
|
59
59
|
value2, success = value(global, item.text)
|
60
60
|
end
|
@@ -105,11 +105,12 @@ module MODL
|
|
105
105
|
if ikey.to_s == key
|
106
106
|
index_val = global.index[ikey]
|
107
107
|
value1 = index_val.respond_to?(:text) ? index_val.text : nil
|
108
|
+
value1 = Substitutions.process(value1)
|
108
109
|
else
|
109
110
|
pair = global.pair(key)
|
110
|
-
return k unless pair
|
111
|
+
return Substitutions.process(k) unless pair
|
111
112
|
|
112
|
-
value1 = pair.text
|
113
|
+
value1 = Substitutions.process(pair.text)
|
113
114
|
end
|
114
115
|
success = true
|
115
116
|
end
|
@@ -70,8 +70,8 @@ module MODL
|
|
70
70
|
else
|
71
71
|
txt = File.readlines(file_name).join
|
72
72
|
end
|
73
|
-
rescue
|
74
|
-
raise InterpreterError, 'File not found: ' + file_name
|
73
|
+
rescue StandardError => e
|
74
|
+
raise InterpreterError, 'File not found: ' + file_name + ', error: ' + e.message
|
75
75
|
end
|
76
76
|
global.loaded_file(file_name)
|
77
77
|
|
@@ -33,7 +33,7 @@ module MODL
|
|
33
33
|
# collect all values from the object
|
34
34
|
if item.is_a? Parsed::ParsedValueItem
|
35
35
|
if item&.value&.text
|
36
|
-
global.
|
36
|
+
global.add_to_index(item.value.text)
|
37
37
|
elsif item&.value&.array
|
38
38
|
item.value.array.abstractArrayItems.each do |avi|
|
39
39
|
global.add_to_index(avi.arrayValueItem)
|
@@ -40,18 +40,18 @@ module MODL
|
|
40
40
|
transform = @transform
|
41
41
|
while transform && transform.length > 0
|
42
42
|
if transform.start_with? 'replace'
|
43
|
-
close_bracket = transform.index('
|
43
|
+
close_bracket = transform.index('>')
|
44
44
|
m = Sutil.head(transform, close_bracket + 1).sub!('replace', 'r')
|
45
45
|
str = StandardMethods.run_method(m, str)
|
46
46
|
# Consume the subst clause
|
47
|
-
close_bracket = transform.index('
|
47
|
+
close_bracket = transform.index('>')
|
48
48
|
transform = Sutil.tail(transform, close_bracket + 2)
|
49
49
|
elsif transform.start_with? 'trim'
|
50
|
-
close_bracket = transform.index('
|
50
|
+
close_bracket = transform.index('>')
|
51
51
|
m = Sutil.head(transform, close_bracket + 1).sub!('trim', 't')
|
52
52
|
str = StandardMethods.run_method(m, str)
|
53
53
|
# Consume the trunc clause
|
54
|
-
close_bracket = transform.index('
|
54
|
+
close_bracket = transform.index('>')
|
55
55
|
transform = Sutil.tail(transform, close_bracket + 2)
|
56
56
|
elsif transform.start_with? 'initcap'
|
57
57
|
str = str.split.map(&:capitalize) * ' '
|
@@ -129,7 +129,7 @@ module MODL
|
|
129
129
|
CGI.escape(str)
|
130
130
|
when 'r', 'replace'
|
131
131
|
s1, s2 = get_subst_parts(mthd)
|
132
|
-
str.
|
132
|
+
str.gsub(s1, s2)
|
133
133
|
when 't', 'trim'
|
134
134
|
s1 = extract_params mthd
|
135
135
|
i = str.index(s1)
|
@@ -153,11 +153,15 @@ module MODL
|
|
153
153
|
# Extract the method parameter
|
154
154
|
def self.extract_params(str)
|
155
155
|
# should be of the form .r(s1,s2)
|
156
|
-
Sutil.between(str, '
|
156
|
+
Sutil.between(str, '<', '>')
|
157
157
|
end
|
158
158
|
|
159
159
|
def self.valid_method?(mthd)
|
160
|
-
|
160
|
+
m = mthd
|
161
|
+
if m.include?('<')
|
162
|
+
m = Sutil.until(m, '<')
|
163
|
+
end
|
164
|
+
return @@mthd_names.include?(m)
|
161
165
|
end
|
162
166
|
end
|
163
167
|
end
|
data/lib/modl/parser/parsed.rb
CHANGED
@@ -202,12 +202,15 @@ module MODL
|
|
202
202
|
attr_accessor :type # A string set to the type of pair that we have found bases on its key
|
203
203
|
attr_accessor :text # The simple text value rather than the object
|
204
204
|
attr_accessor :final
|
205
|
+
attr_accessor :loaded # For *load instructions so we don't load twice
|
206
|
+
|
205
207
|
|
206
208
|
def initialize(global)
|
207
209
|
@global = global
|
208
210
|
@needs_defref = true
|
209
211
|
@final = false
|
210
212
|
@file_importer = FileImporter.instance
|
213
|
+
@loaded = false
|
211
214
|
end
|
212
215
|
|
213
216
|
def find_property(key)
|
@@ -271,11 +274,7 @@ module MODL
|
|
271
274
|
value = @valueItem.extract_hash if @valueItem
|
272
275
|
value = @map.extract_hash if @map
|
273
276
|
|
274
|
-
|
275
|
-
@text, _ignore = RefProcessor.deref(@text, @global)
|
276
|
-
else
|
277
|
-
@text = value
|
278
|
-
end
|
277
|
+
@text = value
|
279
278
|
|
280
279
|
return if @type == 'index'
|
281
280
|
return if @type == 'hidden'
|
@@ -294,8 +293,6 @@ module MODL
|
|
294
293
|
|
295
294
|
ctx_string = ctx.STRING
|
296
295
|
@key = ctx_string.to_s unless ctx_string.nil?
|
297
|
-
ctx_string = ctx.NUMBER
|
298
|
-
@key = ctx_string.to_s unless ctx_string.nil?
|
299
296
|
ctx_quoted = ctx.QUOTED
|
300
297
|
unless ctx_quoted.nil?
|
301
298
|
@key = ctx_quoted.to_s
|
@@ -365,7 +362,7 @@ module MODL
|
|
365
362
|
when 'import'
|
366
363
|
files = @valueItem.extract_hash if @valueItem
|
367
364
|
files = @array.extract_hash if @array
|
368
|
-
@file_importer.import_files files, @global
|
365
|
+
@file_importer.import_files files, @global unless @global.in_condition?
|
369
366
|
when 'index'
|
370
367
|
IndexExtractor.extract(self, @global)
|
371
368
|
when 'hidden'
|
@@ -664,7 +661,7 @@ module MODL
|
|
664
661
|
if user_method
|
665
662
|
return user_method.run(@string.string)
|
666
663
|
end
|
667
|
-
return StandardMethods.run_method(key, @string.string)
|
664
|
+
return StandardMethods.run_method(key, Substitutions.process(@string.string))
|
668
665
|
end
|
669
666
|
end
|
670
667
|
|
@@ -707,13 +704,11 @@ module MODL
|
|
707
704
|
@text = ctx_string.text
|
708
705
|
|
709
706
|
@constant = @text.start_with?('`') && !@text.include?('%') && !@text.include?('`.')
|
710
|
-
@text = Parsed.additional_string_processing(@text)
|
711
707
|
@string = ParsedString.new(@text)
|
712
708
|
@text = @string.string
|
713
709
|
elsif !ctx_quoted.nil?
|
714
710
|
@constant = true
|
715
|
-
@text =
|
716
|
-
@text = Parsed.additional_string_processing(@text)
|
711
|
+
@text = ctx_quoted.text
|
717
712
|
@quoted = ParsedQuoted.new(@text)
|
718
713
|
elsif !ctx_null.nil?
|
719
714
|
@nilVal = ParsedNull.instance
|
@@ -1023,6 +1018,7 @@ module MODL
|
|
1023
1018
|
@global = global
|
1024
1019
|
@topLevelConditionalReturns = []
|
1025
1020
|
@conditionTests = []
|
1021
|
+
@file_importer = FileImporter.instance
|
1026
1022
|
end
|
1027
1023
|
|
1028
1024
|
def extract_hash
|
@@ -1033,7 +1029,17 @@ module MODL
|
|
1033
1029
|
if item.structures[0].pair
|
1034
1030
|
key = item.structures[0].pair.key
|
1035
1031
|
key = Sutil.tail(key) if key[0] == '_'
|
1036
|
-
|
1032
|
+
pair = item.structures[0].pair
|
1033
|
+
@global.pair(key, pair)
|
1034
|
+
|
1035
|
+
if key.downcase.start_with? '*l'
|
1036
|
+
files = pair.valueItem.extract_hash if pair.valueItem
|
1037
|
+
files = pair.array.extract_hash if pair.array
|
1038
|
+
unless pair.loaded
|
1039
|
+
@file_importer.import_files files, @global
|
1040
|
+
pair.loaded = true
|
1041
|
+
end
|
1042
|
+
end
|
1037
1043
|
end
|
1038
1044
|
return item.extract_hash
|
1039
1045
|
end
|
@@ -1043,7 +1049,17 @@ module MODL
|
|
1043
1049
|
if last_item.structures[0].pair
|
1044
1050
|
key = last_item.structures[0].pair.key
|
1045
1051
|
key = Sutil.tail(key) if key[0] == '_'
|
1046
|
-
|
1052
|
+
pair = last_item.structures[0].pair
|
1053
|
+
@global.pair(key, pair)
|
1054
|
+
|
1055
|
+
if key.downcase.start_with? '*l'
|
1056
|
+
files = pair.valueItem.extract_hash if pair.valueItem
|
1057
|
+
files = pair.array.extract_hash if pair.array
|
1058
|
+
unless pair.loaded
|
1059
|
+
@file_importer.import_files files, @global
|
1060
|
+
pair.loaded = true
|
1061
|
+
end
|
1062
|
+
end
|
1047
1063
|
end
|
1048
1064
|
last_item.extract_hash
|
1049
1065
|
end
|
@@ -32,18 +32,19 @@ module MODL
|
|
32
32
|
class RefProcessor
|
33
33
|
|
34
34
|
NESTED_SEPARATOR = '.'
|
35
|
-
MATCHER = Regexp.new('((
|
35
|
+
MATCHER = Regexp.new('((%\w+)(\.\w*<`?\w*`?,`\w*`>)+|(%` ?[\w-]+`[\w.<>,]*%?)|(%\*?[\w]+(\.%?\w*<?[\w,]*>?)*%?))')
|
36
|
+
MAX_RECURSE_DEPTH = 10
|
36
37
|
|
37
38
|
def self.trivial_reject(str)
|
38
39
|
# do a fast check to see if we need to deref - save processing the regex if we don't have to.
|
39
|
-
str.is_a?(String) && (str.nil? || str.include?('%') || str.include?('`'))
|
40
|
+
!(str.is_a?(String) && !str.start_with?('%*') && (str.nil? || str.include?('%') || str.include?('`')))
|
40
41
|
end
|
41
42
|
|
42
43
|
# Check str for references and process them.
|
43
44
|
# Return the processed string and a new_value if there is one.
|
44
45
|
def self.deref(str, global)
|
45
46
|
obj = str
|
46
|
-
obj, new_value = split_by_ref_tokens str, global
|
47
|
+
obj, new_value = split_by_ref_tokens str, global unless trivial_reject(str)
|
47
48
|
[obj, new_value]
|
48
49
|
end
|
49
50
|
|
@@ -80,9 +81,9 @@ module MODL
|
|
80
81
|
|
81
82
|
|
82
83
|
ref = match[0]
|
83
|
-
ref = Sutil.head(ref) if ref.end_with?('%')
|
84
84
|
text = Sutil.after(text, ref)
|
85
|
-
|
85
|
+
|
86
|
+
new_value, remainder = expand(0, global, ref)
|
86
87
|
ref = Sutil.until(ref, remainder)
|
87
88
|
if new_value.is_a?(String)
|
88
89
|
str = str.sub(ref, new_value)
|
@@ -101,7 +102,7 @@ module MODL
|
|
101
102
|
str = if ref == str
|
102
103
|
new_value.text
|
103
104
|
else
|
104
|
-
str.sub(ref, new_value.text.to_s)
|
105
|
+
str.sub(ref, Sutil.unquote(new_value.text.to_s))
|
105
106
|
end
|
106
107
|
new_value = nil
|
107
108
|
else
|
@@ -115,7 +116,10 @@ module MODL
|
|
115
116
|
return new_value, str
|
116
117
|
end
|
117
118
|
|
118
|
-
def self.expand(global, ref)
|
119
|
+
def self.expand(depth, global, ref)
|
120
|
+
if depth > MAX_RECURSE_DEPTH
|
121
|
+
raise InterpreterError, 'Recursing too deep to resolve: "' + ref + '"'
|
122
|
+
end
|
119
123
|
result = nil
|
120
124
|
prev = nil
|
121
125
|
|
@@ -123,12 +127,13 @@ module MODL
|
|
123
127
|
|
124
128
|
parts = Sutil.tail(degraved).split('.') if degraved[0] == '%'
|
125
129
|
parts = degraved.split('.') unless degraved[0] == '%'
|
130
|
+
parts[-1] = Sutil.head(parts[-1]) if parts[-1].end_with?('%')
|
126
131
|
|
127
132
|
if degraved.include?('%')
|
128
133
|
resolved = 0
|
129
134
|
parts.each do |p|
|
130
135
|
if p.include?('%')
|
131
|
-
p, _ignore = expand(global, p)
|
136
|
+
p, _ignore = expand(depth + 1, global, p)
|
132
137
|
if p.is_a?(MODL::Parser::MODLParserBaseListener)
|
133
138
|
p = p.text
|
134
139
|
end
|
@@ -154,7 +159,8 @@ module MODL
|
|
154
159
|
end
|
155
160
|
end
|
156
161
|
elsif result.is_a? Parsed::ParsedPair
|
157
|
-
|
162
|
+
prop = result.find_property(p)
|
163
|
+
if result.text && !prop
|
158
164
|
if StandardMethods.valid_method?(p)
|
159
165
|
StandardMethods.run_method(p, result.text)
|
160
166
|
else
|
@@ -166,7 +172,27 @@ module MODL
|
|
166
172
|
end
|
167
173
|
end
|
168
174
|
else
|
169
|
-
|
175
|
+
prop
|
176
|
+
end
|
177
|
+
elsif result.is_a? Parsed::ParsedArrayValueItem
|
178
|
+
prop = result.find_property(p)
|
179
|
+
if result.text && !prop
|
180
|
+
if StandardMethods.valid_method?(p)
|
181
|
+
result_text = result.text
|
182
|
+
if result_text.start_with?('`') && result_text.end_with?('`')
|
183
|
+
result_text = Sutil.toptail(result_text)
|
184
|
+
end
|
185
|
+
StandardMethods.run_method(p, result_text)
|
186
|
+
else
|
187
|
+
mthd = global.user_method(p)
|
188
|
+
if !mthd.nil?
|
189
|
+
mthd.run(result.text)
|
190
|
+
else
|
191
|
+
mthd
|
192
|
+
end
|
193
|
+
end
|
194
|
+
else
|
195
|
+
prop
|
170
196
|
end
|
171
197
|
elsif result.is_a? Array
|
172
198
|
nil
|
@@ -175,7 +201,14 @@ module MODL
|
|
175
201
|
raise InterpreterError, 'Interpreter Error: Invalid object reference: ' + degraved
|
176
202
|
end
|
177
203
|
if result.nil?
|
178
|
-
|
204
|
+
unless ref.start_with?('%`')
|
205
|
+
a_pair = global.pair(p)
|
206
|
+
end
|
207
|
+
if a_pair.nil?
|
208
|
+
p
|
209
|
+
else
|
210
|
+
a_pair
|
211
|
+
end
|
179
212
|
else
|
180
213
|
result.find_property(p)
|
181
214
|
end
|
@@ -192,6 +225,9 @@ module MODL
|
|
192
225
|
else
|
193
226
|
remainder = resolved < parts.length ? '.' + parts[resolved..parts.length].join('.') : ''
|
194
227
|
end
|
228
|
+
if (prev == Sutil.between(ref, '%', '%')) || (ref.start_with?('%') && prev == Sutil.tail(ref))
|
229
|
+
prev = ref
|
230
|
+
end
|
195
231
|
[prev, remainder]
|
196
232
|
else
|
197
233
|
# Remove the graves if there are any.
|
@@ -76,14 +76,19 @@ module MODL
|
|
76
76
|
# Replace all escape sequences in the supplied string and return the new value.
|
77
77
|
def self.process(str)
|
78
78
|
return str unless str.is_a? String
|
79
|
+
|
80
|
+
# Remove unescaped graves and double quotes
|
81
|
+
new_str = Sutil.unquote(str)
|
82
|
+
|
83
|
+
# Handle escape sequences
|
79
84
|
@@subs.each do |s|
|
80
85
|
loop do
|
81
|
-
prev =
|
82
|
-
|
83
|
-
break unless
|
86
|
+
prev = new_str
|
87
|
+
new_str = new_str.sub(s[0], s[1])
|
88
|
+
break unless new_str && new_str != prev
|
84
89
|
end
|
85
90
|
end
|
86
|
-
|
91
|
+
new_str
|
87
92
|
end
|
88
93
|
end
|
89
94
|
end
|
data/lib/modl/parser/sutil.rb
CHANGED
@@ -97,4 +97,12 @@ module Sutil
|
|
97
97
|
end
|
98
98
|
result
|
99
99
|
end
|
100
|
+
|
101
|
+
def self.unquote(str)
|
102
|
+
new_str = str
|
103
|
+
if (str.start_with?('`') && str.end_with?('`')) || (str.start_with?('"') && str.end_with?('"'))
|
104
|
+
new_str = Sutil.toptail(str)
|
105
|
+
end
|
106
|
+
new_str
|
107
|
+
end
|
100
108
|
end
|
data/lib/modl/parser/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: modl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tony Walmsley
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-06-
|
11
|
+
date: 2019-06-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: antlr4-runtime
|
@@ -109,6 +109,8 @@ files:
|
|
109
109
|
- grammar_tests/c.modl
|
110
110
|
- grammar_tests/demo_config.modl
|
111
111
|
- grammar_tests/error_tests.json
|
112
|
+
- grammar_tests/files/a-b.txt
|
113
|
+
- grammar_tests/files/c-d.txt
|
112
114
|
- grammar_tests/import_config.modl
|
113
115
|
- grammar_tests/test_import_dir/nested_import1.txt
|
114
116
|
- grammar_tests/test_import_dir/nested_import2.txt
|