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
data/lib/modl/parser/parser.rb
CHANGED
@@ -1,72 +1,285 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
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 'modl/parser/throwing_error_listener'
|
26
|
-
require 'modl/parser/parsed'
|
27
|
-
|
28
3
|
module MODL
|
4
|
+
# A Parser module
|
29
5
|
module Parser
|
6
|
+
REPLACEMENTS = {
|
7
|
+
'\t' => "\t",
|
8
|
+
'\n' => "\n",
|
9
|
+
'\b' => "\b",
|
10
|
+
'\f' => "\f",
|
11
|
+
'\r' => "\r",
|
12
|
+
'~t' => "\t",
|
13
|
+
'~n' => "\n",
|
14
|
+
'~b' => "\b",
|
15
|
+
'~f' => "\f",
|
16
|
+
'~r' => "\r",
|
17
|
+
'~\\' => '\\',
|
18
|
+
'\\\\' => '\\',
|
19
|
+
'~~' => '~',
|
20
|
+
'\~' => '~',
|
21
|
+
'~(' => '(',
|
22
|
+
'\(' => '(',
|
23
|
+
'~)' => ')',
|
24
|
+
'\)' => ')',
|
25
|
+
'~[' => '[',
|
26
|
+
'\[' => '[',
|
27
|
+
'~]' => ']',
|
28
|
+
'\]' => ']',
|
29
|
+
'~;' => ';',
|
30
|
+
'\;' => ';',
|
31
|
+
'~`' => '`',
|
32
|
+
'\`' => '`',
|
33
|
+
'~"' => '"',
|
34
|
+
'"' => '"',
|
35
|
+
'~=' => '=',
|
36
|
+
'\=' => '='
|
37
|
+
}.freeze
|
38
|
+
|
39
|
+
def self.parse_modl(str)
|
40
|
+
tokens = MODL::Tokeniser.tokenise str
|
41
|
+
if tokens.nil? || tokens.empty?
|
42
|
+
MODL::Model::Modl.new nil
|
43
|
+
elsif root_primitive? tokens[0]
|
44
|
+
MODL::Model::Modl.new tokens[0].value
|
45
|
+
else
|
46
|
+
result = parse_structures(tokens)
|
47
|
+
return MODL::Model::Modl.new result[0] if result.length == 1
|
48
|
+
|
49
|
+
MODL::Model::Modl.new ModlArray.new(result)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.root_primitive?(token)
|
54
|
+
%i[string quoted null true false integer float].include?(token)
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.parse_structures(tokens)
|
58
|
+
result = []
|
59
|
+
until tokens.empty?
|
60
|
+
result.push parse_modl_value(tokens)
|
61
|
+
expect_separator = tokens.shift
|
62
|
+
if !expect_separator.nil? && expect_separator.type == :struct_sep
|
63
|
+
raise ParserError, "Expected ';' near #{tokens}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
result
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.parse_modl_value(tokens)
|
70
|
+
first_token = tokens.shift
|
71
|
+
|
72
|
+
case first_token.type
|
73
|
+
when :lbracket
|
74
|
+
tokens.unshift first_token
|
75
|
+
return parse_modl_array tokens
|
76
|
+
when :lparen
|
77
|
+
tokens.unshift first_token
|
78
|
+
return parse_modl_map tokens
|
79
|
+
when :string || :quoted
|
80
|
+
peek = tokens[0]
|
81
|
+
key = first_token.value
|
82
|
+
if !peek.nil? && peek.type == :equals
|
83
|
+
tokens.shift
|
84
|
+
return MODL::Model::ModlPair.new replace_escapes(unquote(key)), parse_pair_value(tokens)
|
85
|
+
end
|
86
|
+
|
87
|
+
if !peek.nil? && (peek.type == :lbracket || peek.type == :lparen)
|
88
|
+
return MODL::Model::ModlPair.new replace_escapes(unquote(key)), parse_pair_value(tokens)
|
89
|
+
end
|
90
|
+
|
91
|
+
if peek.nil? || peek.type == :struct_sep || peek.type == :rparen || peek.type == :rbracket
|
92
|
+
return MODL::Model::ModlString.new(replace_escapes(first_token.value)) if first_token.type == :string
|
93
|
+
|
94
|
+
return MODL::Model::ModlQuoted.new(replace_escapes(unquote(first_token.value)))
|
95
|
+
end
|
96
|
+
when :integer
|
97
|
+
return MODL::Model::ModlInteger.new first_token.value
|
98
|
+
when :float
|
99
|
+
return MODL::Model::ModlFloat.new first_token.value
|
100
|
+
when :null
|
101
|
+
return MODL::Model::ModlBoolNull::MODL_NULL
|
102
|
+
when true
|
103
|
+
return MODL::Model::ModlBoolNull::MODL_TRUE
|
104
|
+
when false
|
105
|
+
return MODL::Model::ModlBoolNull::MODL_FALSE
|
106
|
+
else
|
107
|
+
tokens.unshift first_token
|
108
|
+
maybe_primitive = parse_primitive tokens
|
109
|
+
return maybe_primitive unless maybe_primitive.nil?
|
110
|
+
end
|
111
|
+
raise ParserError, "Unexpected token: \'#{first_token}\'"
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.parse_pair_value(tokens)
|
115
|
+
first_token = tokens.shift
|
116
|
+
case first_token.type
|
117
|
+
when :lbracket
|
118
|
+
tokens.unshift first_token
|
119
|
+
return parse_modl_array tokens
|
120
|
+
when :lparen
|
121
|
+
tokens.unshift first_token
|
122
|
+
return parse_modl_map tokens
|
123
|
+
when :string || :quoted
|
124
|
+
peek = tokens[0]
|
125
|
+
|
126
|
+
raise ParserError, "Unexpected token: \'#{first_token}\'" if !peek.nil? && peek.type == :equals
|
127
|
+
|
128
|
+
if !peek.nil? && (peek.type == :lbracket || peek.type == :lparen)
|
129
|
+
raise ParserError, "Unexpected token: \'#{first_token}\'"
|
130
|
+
end
|
131
|
+
|
132
|
+
if peek.nil? || peek.type == :struct_sep || peek.type == :rparen || peek.type == :rbracket
|
133
|
+
return MODL::Model::ModlString.new replace_escapes(first_token.value) if first_token.type == :string
|
134
|
+
|
135
|
+
return MODL::Model::ModlQuoted.new replace_escapes(unquote(first_token.value))
|
136
|
+
end
|
137
|
+
raise ParserError, "Unexpected token: \'#{first_token}\'"
|
138
|
+
when :integer
|
139
|
+
return MODL::Model::ModlInteger.new first_token.value
|
140
|
+
when :float
|
141
|
+
return MODL::Model::ModlFloat.new first_token.value
|
142
|
+
when :null
|
143
|
+
return MODL::Model::ModlBoolNull::MODL_NULL
|
144
|
+
when true
|
145
|
+
return MODL::Model::ModlBoolNull::MODL_TRUE
|
146
|
+
when false
|
147
|
+
return MODL::Model::ModlBoolNull::MODL_FALSE
|
148
|
+
else
|
149
|
+
tokens.unshift first_token
|
150
|
+
maybe_primitive = parse_primitive tokens
|
151
|
+
return maybe_primitive unless maybe_primitive.nil?
|
152
|
+
end
|
153
|
+
raise ParserError, "Unexpected token: \'#{first_token}\'"
|
154
|
+
end
|
30
155
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
156
|
+
def self.parse_primitive(tokens)
|
157
|
+
result = nil
|
158
|
+
tok = tokens.shift
|
159
|
+
|
160
|
+
case tok.type
|
161
|
+
when :lparen || :rparen || :lbracket || :rbracket || :equals
|
162
|
+
raise ParserError, "Unexpected token: \'#{tok}\'" if tokens.empty?
|
163
|
+
|
164
|
+
tokens.unshift tok
|
165
|
+
return nil
|
166
|
+
when :null
|
167
|
+
result = MODL::Model::ModlBoolNull::MODL_NULL
|
168
|
+
when true
|
169
|
+
result = MODL::Model::ModlBoolNull::MODL_TRUE
|
170
|
+
when false
|
171
|
+
result = MODL::Model::ModlBoolNull::MODL_FALSE
|
172
|
+
when :quoted
|
173
|
+
result = MODL::Model::ModlQuoted.new replace_escapes(unquote(tok.value))
|
174
|
+
when :string
|
175
|
+
result = MODL::Model::ModlString.new replace_escapes(tok.value)
|
176
|
+
when :integer
|
177
|
+
result = MODL::Model::ModlInteger.new tok.value
|
178
|
+
when :float
|
179
|
+
result = MODL::Model::ModlFloat.new tok.value
|
180
|
+
else
|
181
|
+
raise ParserError, "Unknown token type in: \'#{tok}\'"
|
182
|
+
end
|
183
|
+
|
184
|
+
peek = tokens[0]
|
185
|
+
raise ParserError, 'Only one primitive allowed at the root.' if !peek.nil? && peek.type == :struct_sep
|
186
|
+
|
187
|
+
if !peek.nil? && (peek.type == :lparen || peek.type == :lbracket || peek.type == :equals)
|
188
|
+
s.unshift tok
|
189
|
+
return nil
|
190
|
+
elsif !peek.nil?
|
191
|
+
raise ParserError, "Unexpected token: \'#{peek}\'"
|
192
|
+
end
|
193
|
+
result
|
194
|
+
end
|
195
|
+
|
196
|
+
def self.parse_modl_map(tokens)
|
197
|
+
first_token = tokens.shift
|
198
|
+
entries = []
|
199
|
+
|
200
|
+
until tokens.empty?
|
201
|
+
peek = tokens[0]
|
202
|
+
if !peek.nil? && peek.type == :rparen
|
203
|
+
tokens.shift
|
204
|
+
break
|
205
|
+
end
|
206
|
+
|
207
|
+
mp = parse_modl_value tokens
|
208
|
+
entries.push mp
|
209
|
+
|
210
|
+
peek = tokens[0]
|
211
|
+
|
212
|
+
raise ParserError, "Expected ')' near #{first_token}" if peek.nil?
|
213
|
+
|
214
|
+
case peek.type
|
215
|
+
when :rparen
|
216
|
+
tokens.shift
|
217
|
+
break
|
218
|
+
when :struct_sep
|
219
|
+
tokens.shift
|
220
|
+
peek = tokens[0]
|
221
|
+
raise ParserError, "Unexpected ; before ] at #{peek}" if !peek.nil? && peek.type == :rparen
|
222
|
+
end
|
223
|
+
end
|
224
|
+
MODL::Model::ModlMap.new entries
|
225
|
+
end
|
226
|
+
|
227
|
+
def self.parse_modl_array(tokens)
|
228
|
+
first_token = tokens.shift
|
229
|
+
entries = []
|
230
|
+
|
231
|
+
until tokens.empty?
|
232
|
+
peek = tokens[0]
|
233
|
+
if !peek.nil? && peek.type == :rbracket
|
234
|
+
tokens.shift
|
235
|
+
break
|
236
|
+
end
|
237
|
+
|
238
|
+
mp = parse_modl_value tokens
|
239
|
+
entries.push mp
|
240
|
+
|
241
|
+
peek = tokens[0]
|
242
|
+
|
243
|
+
raise ParserError, "Expected ']' near #{first_token}" if peek.nil?
|
244
|
+
|
245
|
+
case peek.type
|
246
|
+
when :rbracket
|
247
|
+
tokens.shift
|
248
|
+
break
|
249
|
+
when :struct_sep
|
250
|
+
tokens.shift
|
251
|
+
peek = tokens[0]
|
252
|
+
raise ParserError, "Unexpected ; before ] at #{peek}" if !peek.nil? && peek.type == :rparen
|
59
253
|
end
|
60
254
|
end
|
255
|
+
MODL::Model::ModlArray.new entries
|
256
|
+
end
|
257
|
+
|
258
|
+
def self.unquote(str)
|
259
|
+
return str unless str.instance_of?(String)
|
260
|
+
|
261
|
+
if (str.start_with?('`') && str.end_with?('`')) || (str.start_with?('"') && str.end_with?('"'))
|
262
|
+
str[1..-2]
|
263
|
+
else
|
264
|
+
str
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
def self.replace_escapes(str)
|
269
|
+
return str unless str.instance_of?(String)
|
61
270
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
271
|
+
result = str
|
272
|
+
i = 0
|
273
|
+
while i < str.length
|
274
|
+
REPLACEMENTS.each_pair do |key, value|
|
275
|
+
if result.slice(i..).start_with?(key)
|
276
|
+
result = result.sub(key, value)
|
277
|
+
break
|
278
|
+
end
|
68
279
|
end
|
280
|
+
i += 1
|
69
281
|
end
|
282
|
+
result
|
70
283
|
end
|
71
284
|
end
|
72
285
|
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MODL
|
4
|
+
module Tokeniser
|
5
|
+
# A parsing context for a MODL String
|
6
|
+
class Context
|
7
|
+
WS = " \t\r\n"
|
8
|
+
INTEGER_REGEX = '^-?(?:0|[1-9]\d*)$'
|
9
|
+
FLOAT_REGEX = '^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$'
|
10
|
+
NON_STRING_TOKENS = '[]();"=`'
|
11
|
+
|
12
|
+
def initialize(str)
|
13
|
+
@str = str
|
14
|
+
@tokens = []
|
15
|
+
@tok_start = 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse
|
19
|
+
while next_token
|
20
|
+
end
|
21
|
+
@tokens
|
22
|
+
end
|
23
|
+
|
24
|
+
def next_token
|
25
|
+
while @tok_start < @str.length
|
26
|
+
break unless WS.include?(@str[@tok_start])
|
27
|
+
|
28
|
+
@tok_start += 1
|
29
|
+
end
|
30
|
+
|
31
|
+
return false if @tok_start >= @str.length
|
32
|
+
|
33
|
+
case @str[@tok_start]
|
34
|
+
when '('
|
35
|
+
tok_type = :lparen
|
36
|
+
tok_end = @tok_start + 1
|
37
|
+
when ')'
|
38
|
+
tok_type = :rparen
|
39
|
+
tok_end = @tok_start + 1
|
40
|
+
when '['
|
41
|
+
tok_type = :lbracket
|
42
|
+
tok_end = @tok_start + 1
|
43
|
+
when ']'
|
44
|
+
tok_type = :rbracket
|
45
|
+
tok_end = @tok_start + 1
|
46
|
+
when ';'
|
47
|
+
tok_type = :struct_sep
|
48
|
+
tok_end = @tok_start + 1
|
49
|
+
when '='
|
50
|
+
tok_type = :equals
|
51
|
+
tok_end = @tok_start + 1
|
52
|
+
when '"'
|
53
|
+
tok_type = :quoted
|
54
|
+
tok_end = scan_to_end_of_quoted(@str, @tok_start, '"')
|
55
|
+
when '`'
|
56
|
+
tok_type = :quoted
|
57
|
+
tok_end = scan_to_end_of_quoted(@str, @tok_start, '`')
|
58
|
+
else
|
59
|
+
tok_type = :string
|
60
|
+
tok_end = scan_to_end_of_string(@str, @tok_start)
|
61
|
+
end
|
62
|
+
|
63
|
+
tok_value = @str[@tok_start..(tok_end - 1)].strip
|
64
|
+
|
65
|
+
if tok_value.match? INTEGER_REGEX
|
66
|
+
number = tok_value.to_i
|
67
|
+
@tokens.push Token.new(:integer, number, @tok_start, tok_end)
|
68
|
+
elsif tok_value.match? FLOAT_REGEX
|
69
|
+
number = tok_value.to_f
|
70
|
+
@tokens.push Token.new(:float, number, @tok_start, tok_end)
|
71
|
+
elsif tok_value == 'null'
|
72
|
+
@tokens.push Token.new(:null, nil, @tok_start, tok_end)
|
73
|
+
elsif tok_value == 'true'
|
74
|
+
@tokens.push Token.new(true, true, @tok_start, tok_end)
|
75
|
+
elsif tok_value == 'false'
|
76
|
+
@tokens.push Token.new(false, false, @tok_start, tok_end)
|
77
|
+
else
|
78
|
+
@tokens.push Token.new(tok_type, tok_value, @tok_start, tok_end)
|
79
|
+
end
|
80
|
+
|
81
|
+
@tok_start = tok_end
|
82
|
+
tok_end < @str.length
|
83
|
+
end
|
84
|
+
|
85
|
+
def scan_to_end_of_quoted(str, start, quote_char)
|
86
|
+
end_str = start + 1
|
87
|
+
while end_str < str.length
|
88
|
+
end_char = str[end_str]
|
89
|
+
prev_char = str[end_str - 1]
|
90
|
+
|
91
|
+
break if end_char == quote_char && prev_char != '\\' && prev_char != '~'
|
92
|
+
|
93
|
+
end_str += 1
|
94
|
+
end
|
95
|
+
end_str + 1
|
96
|
+
end
|
97
|
+
|
98
|
+
def scan_to_end_of_string(str, start)
|
99
|
+
end_str = start + 1
|
100
|
+
while end_str < str.length
|
101
|
+
break if NON_STRING_TOKENS.include?(str[end_str]) && !escaped?(str, end_str - 1)
|
102
|
+
|
103
|
+
end_str += 1
|
104
|
+
end
|
105
|
+
end_str
|
106
|
+
end
|
107
|
+
|
108
|
+
def escaped?(str, pos)
|
109
|
+
pos >= 0 && (str[pos] == '~' || str[pos] == '\\')
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'modl/tokeniser/context'
|
4
|
+
|
5
|
+
module MODL
|
6
|
+
# A MODL Tokeniser module
|
7
|
+
module Tokeniser
|
8
|
+
# A class to represent MODL tokens
|
9
|
+
class Token
|
10
|
+
attr_reader :type, :value, :from, :to
|
11
|
+
|
12
|
+
def initialize(type, value, from, to)
|
13
|
+
@type = type
|
14
|
+
@value = value
|
15
|
+
@from = from
|
16
|
+
@to = to
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
"type: #{@type}, from: #{@from}, to: #{@to}, value: \"#{@value}\""
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.tokenise(str)
|
25
|
+
Context.new(str).parse
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MODL
|
4
|
+
# Helper functions for MODL.generate
|
5
|
+
module UTIL
|
6
|
+
SHOULD_BE_GRAVE_QUOTED = /.*[()\[\];{}="].*/.freeze
|
7
|
+
IS_NUMERIC = /^-?[0-9]*\.?[0-9]+(?:[Ee][+-]?[0-9]+)?$/.freeze
|
8
|
+
|
9
|
+
def self.escape_graves(str)
|
10
|
+
return str unless str
|
11
|
+
|
12
|
+
str.gsub(/`/, '~`')
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.escape_double_quotes(str)
|
16
|
+
return str unless str
|
17
|
+
|
18
|
+
i = str.index('"', 1) # ignore the initial quote
|
19
|
+
result = str
|
20
|
+
# Don't affect the final quote in the string
|
21
|
+
while !i.nil? && i < (result.length - 1)
|
22
|
+
result = result.slice(0..(i - 1)) + '~u0022' + result.slice((i + 1)..)
|
23
|
+
i = result.index('"', 1)
|
24
|
+
end
|
25
|
+
result
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.grave_quote_if_necessary(str)
|
29
|
+
return str unless str
|
30
|
+
|
31
|
+
if str.match?(SHOULD_BE_GRAVE_QUOTED) ||
|
32
|
+
(str.match?(IS_NUMERIC) && str != '00' && str != '01' && str != '000') ||
|
33
|
+
(str.strip.empty? ||
|
34
|
+
str.match?(IS_NUMERIC) ||
|
35
|
+
str == 'true' ||
|
36
|
+
str == 'false' ||
|
37
|
+
str == 'null')
|
38
|
+
"`#{str}`"
|
39
|
+
else
|
40
|
+
str
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.double_quote_if_necessary(str)
|
45
|
+
if str && str.include?('~`')
|
46
|
+
"\"#{str}\""
|
47
|
+
else
|
48
|
+
str
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.replace_nbsp(str)
|
53
|
+
str.gsub('\u00a0', ' ')
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.escape_and_quote(str)
|
57
|
+
double_quote_if_necessary(
|
58
|
+
grave_quote_if_necessary(
|
59
|
+
escape_graves(
|
60
|
+
UNICODE.escape(
|
61
|
+
replace_nbsp(
|
62
|
+
escape_double_quotes(str)
|
63
|
+
)
|
64
|
+
)
|
65
|
+
)
|
66
|
+
)
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.non_string_primitive?(str)
|
71
|
+
str == 'true' || str == 'false' || str == 'null' || str.match?(IS_NUMERIC)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
module MODL
|
4
|
+
# Handle escaping of unicode characters.
|
5
|
+
module UNICODE
|
6
|
+
VERTICAL_TAB = 0x0b
|
7
|
+
BACKSPACE = 0x08
|
8
|
+
FORMFEED = 0x0c
|
9
|
+
LINEFEED = 0x0a
|
10
|
+
CARRIAGE_RETURN = 0x0d
|
11
|
+
TAB = 0x09
|
12
|
+
|
13
|
+
def self.escape_char(chr)
|
14
|
+
return '' unless chr
|
15
|
+
|
16
|
+
if chr >= 32 && chr <= 127
|
17
|
+
'' << chr
|
18
|
+
elsif chr == VERTICAL_TAB
|
19
|
+
'~u000B'
|
20
|
+
elsif chr == BACKSPACE
|
21
|
+
'\\b'
|
22
|
+
elsif chr == FORMFEED
|
23
|
+
'\\f'
|
24
|
+
elsif chr == LINEFEED
|
25
|
+
'\\n'
|
26
|
+
elsif chr == CARRIAGE_RETURN
|
27
|
+
'\\r'
|
28
|
+
elsif chr == TAB
|
29
|
+
'\\t'
|
30
|
+
else
|
31
|
+
'' << chr
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.escape(str)
|
36
|
+
result = ''
|
37
|
+
|
38
|
+
str.each_codepoint do |c|
|
39
|
+
result << escape_char(c)
|
40
|
+
end
|
41
|
+
result
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/modl/version.rb
ADDED
data/lib/modl.rb
CHANGED
@@ -1,34 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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 "antlr4/runtime"
|
26
|
-
require "modl/parser/version"
|
27
|
-
require 'modl/parser/MODLParserListener'
|
28
|
-
require 'modl/parser/MODLParserVisitor'
|
29
|
-
require 'modl/parser/MODLParserBaseListener'
|
30
|
-
require 'modl/parser/MODLParserBaseVisitor'
|
31
|
-
require 'modl/parser/MODLLexer'
|
32
|
-
require 'modl/parser/MODLParser'
|
33
|
-
require 'modl/parser/interpreter'
|
34
|
-
require 'modl/parser/instruction_processor'
|
3
|
+
require 'modl/version'
|
4
|
+
require 'modl/model/model'
|
5
|
+
require 'modl/tokeniser/tokeniser'
|
6
|
+
require 'modl/parser/parser'
|
7
|
+
require 'modl/util/unicode'
|
8
|
+
require 'modl/util/functions'
|
9
|
+
require 'modl/interpreter'
|
data/modl.gemspec
CHANGED
@@ -1,30 +1,27 @@
|
|
1
|
-
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require 'modl/
|
3
|
+
require 'modl/version'
|
5
4
|
|
6
5
|
Gem::Specification.new do |spec|
|
7
6
|
spec.name = 'modl'
|
8
|
-
spec.version = MODL::
|
7
|
+
spec.version = MODL::VERSION
|
9
8
|
spec.authors = ['Tony Walmsley']
|
10
9
|
spec.email = ['tony@aosd.co.uk']
|
11
10
|
|
12
|
-
spec.summary = '
|
13
|
-
spec.description = '
|
14
|
-
spec.homepage = 'https://github.com/MODLanguage/ruby-
|
11
|
+
spec.summary = 'A Ruby inretpreter for the MODL data serialisation language.'
|
12
|
+
spec.description = 'A command line program for interpreting MODL objects.'
|
13
|
+
spec.homepage = 'https://github.com/MODLanguage/ruby-modl'
|
15
14
|
spec.license = 'MIT'
|
16
15
|
|
17
16
|
# Specify which files should be added to the gem when it is released.
|
18
17
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
19
|
-
spec.files = Dir.chdir(File.expand_path(
|
18
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
20
19
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
21
20
|
end
|
22
21
|
spec.bindir = 'exe'
|
23
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
24
23
|
spec.require_paths = ['lib']
|
25
24
|
|
26
|
-
spec.add_development_dependency
|
25
|
+
spec.add_development_dependency 'rake', '>= 12.3.3'
|
27
26
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
28
|
-
spec.add_runtime_dependency 'antlr4-runtime', '= 0.2.10'
|
29
|
-
spec.add_runtime_dependency 'punycode4r', '>= 0.2.0'
|
30
27
|
end
|