modl 0.3.26 → 0.3.28
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 -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 +280 -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,293 @@
|
|
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
|
+
return MODL::Model::Modl.new MODL::Model::ModlMap.new(result) if all_pairs?(result)
|
50
|
+
|
51
|
+
MODL::Model::Modl.new MODL::Model::ModlArray.new(result)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.root_primitive?(token)
|
56
|
+
%i[string quoted null true false integer float].include?(token)
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.parse_structures(tokens)
|
60
|
+
result = []
|
61
|
+
until tokens.empty?
|
62
|
+
result.push parse_modl_value(tokens)
|
63
|
+
expect_separator = tokens.shift
|
64
|
+
if !expect_separator.nil? && expect_separator.type != :struct_sep
|
65
|
+
raise ParserError, "Expected ';' near #{tokens}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
result
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.parse_modl_value(tokens)
|
72
|
+
first_token = tokens.shift
|
73
|
+
|
74
|
+
case first_token.type
|
75
|
+
when :lbracket
|
76
|
+
tokens.unshift first_token
|
77
|
+
return parse_modl_array tokens
|
78
|
+
when :lparen
|
79
|
+
tokens.unshift first_token
|
80
|
+
return parse_modl_map tokens
|
81
|
+
when :string || :quoted
|
82
|
+
peek = tokens[0]
|
83
|
+
key = first_token.value
|
84
|
+
if !peek.nil? && peek.type == :equals
|
85
|
+
tokens.shift
|
86
|
+
return MODL::Model::ModlPair.new replace_escapes(unquote(key)), parse_pair_value(tokens)
|
87
|
+
end
|
88
|
+
|
89
|
+
if !peek.nil? && (peek.type == :lbracket || peek.type == :lparen)
|
90
|
+
return MODL::Model::ModlPair.new replace_escapes(unquote(key)), parse_pair_value(tokens)
|
91
|
+
end
|
92
|
+
|
93
|
+
if peek.nil? || peek.type == :struct_sep || peek.type == :rparen || peek.type == :rbracket
|
94
|
+
return MODL::Model::ModlString.new(replace_escapes(first_token.value)) if first_token.type == :string
|
30
95
|
|
31
|
-
|
32
|
-
class Parser
|
33
|
-
def self.parse(str, global = nil)
|
34
|
-
begin
|
35
|
-
lexer = MODL::Parser::MODLLexer.new(Antlr4::Runtime::CharStreams.from_string(str, 'String'))
|
36
|
-
lexer.remove_error_listeners
|
37
|
-
lexer.add_error_listener ThrowingErrorListener.instance
|
38
|
-
|
39
|
-
tokens = Antlr4::Runtime::CommonTokenStream.new(lexer)
|
40
|
-
|
41
|
-
parser = MODL::Parser::MODLParser.new(tokens)
|
42
|
-
parser.remove_error_listeners
|
43
|
-
parser.add_error_listener ThrowingErrorListener.instance
|
44
|
-
|
45
|
-
global = GlobalParseContext.new if global.nil?
|
46
|
-
|
47
|
-
parsed = Parsed.new(global)
|
48
|
-
parser.modl.enter_rule(parsed)
|
49
|
-
parsed
|
50
|
-
rescue Antlr4::Runtime::ParseCancellationException => e
|
51
|
-
check_modl_version(global, e)
|
52
|
-
raise ParserError, 'Parser Error: ' + e.message
|
53
|
-
rescue StandardError => e
|
54
|
-
check_modl_version(global, e)
|
55
|
-
raise InterpreterError, 'Interpreter Error: ' + e.message
|
56
|
-
rescue InterpreterError => e
|
57
|
-
check_modl_version(global, e)
|
58
|
-
raise InterpreterError, 'Interpreter Error: ' + e.message
|
96
|
+
return MODL::Model::ModlQuoted.new(replace_escapes(unquote(first_token.value)))
|
59
97
|
end
|
98
|
+
when :integer
|
99
|
+
return MODL::Model::ModlInteger.new first_token.value
|
100
|
+
when :float
|
101
|
+
return MODL::Model::ModlFloat.new first_token.value
|
102
|
+
when :null
|
103
|
+
return MODL::Model::ModlBoolNull::MODL_NULL
|
104
|
+
when true
|
105
|
+
return MODL::Model::ModlBoolNull::MODL_TRUE
|
106
|
+
when false
|
107
|
+
return MODL::Model::ModlBoolNull::MODL_FALSE
|
108
|
+
else
|
109
|
+
tokens.unshift first_token
|
110
|
+
maybe_primitive = parse_primitive tokens
|
111
|
+
return maybe_primitive unless maybe_primitive.nil?
|
60
112
|
end
|
113
|
+
raise ParserError, "Unexpected token: \'#{first_token}\'"
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.parse_pair_value(tokens)
|
117
|
+
first_token = tokens.shift
|
118
|
+
case first_token.type
|
119
|
+
when :lbracket
|
120
|
+
tokens.unshift first_token
|
121
|
+
return parse_modl_array tokens
|
122
|
+
when :lparen
|
123
|
+
tokens.unshift first_token
|
124
|
+
return parse_modl_map tokens
|
125
|
+
when :string || :quoted
|
126
|
+
peek = tokens[0]
|
127
|
+
|
128
|
+
raise ParserError, "Unexpected token: \'#{first_token}\'" if !peek.nil? && peek.type == :equals
|
129
|
+
|
130
|
+
if !peek.nil? && (peek.type == :lbracket || peek.type == :lparen)
|
131
|
+
raise ParserError, "Unexpected token: \'#{first_token}\'"
|
132
|
+
end
|
133
|
+
|
134
|
+
if peek.nil? || peek.type == :struct_sep || peek.type == :rparen || peek.type == :rbracket
|
135
|
+
return MODL::Model::ModlString.new replace_escapes(first_token.value) if first_token.type == :string
|
61
136
|
|
62
|
-
|
63
|
-
if global.syntax_version > global.interpreter_syntax_version
|
64
|
-
raise InterpreterError, 'Interpreter Error: ' + e.message + ' - MODL Version ' +
|
65
|
-
global.interpreter_syntax_version.to_s +
|
66
|
-
' interpreter cannot process this MODL Version ' +
|
67
|
-
global.syntax_version.to_s + ' file.'
|
137
|
+
return MODL::Model::ModlQuoted.new replace_escapes(unquote(first_token.value))
|
68
138
|
end
|
139
|
+
raise ParserError, "Unexpected token: \'#{first_token}\'"
|
140
|
+
when :integer
|
141
|
+
return MODL::Model::ModlInteger.new first_token.value
|
142
|
+
when :float
|
143
|
+
return MODL::Model::ModlFloat.new first_token.value
|
144
|
+
when :null
|
145
|
+
return MODL::Model::ModlBoolNull::MODL_NULL
|
146
|
+
when true
|
147
|
+
return MODL::Model::ModlBoolNull::MODL_TRUE
|
148
|
+
when false
|
149
|
+
return MODL::Model::ModlBoolNull::MODL_FALSE
|
150
|
+
else
|
151
|
+
tokens.unshift first_token
|
152
|
+
maybe_primitive = parse_primitive tokens
|
153
|
+
return maybe_primitive unless maybe_primitive.nil?
|
154
|
+
end
|
155
|
+
raise ParserError, "Unexpected token: \'#{first_token}\'"
|
156
|
+
end
|
157
|
+
|
158
|
+
def self.parse_primitive(tokens)
|
159
|
+
result = nil
|
160
|
+
tok = tokens.shift
|
161
|
+
|
162
|
+
case tok.type
|
163
|
+
when :lparen || :rparen || :lbracket || :rbracket || :equals
|
164
|
+
raise ParserError, "Unexpected token: \'#{tok}\'" if tokens.empty?
|
165
|
+
|
166
|
+
tokens.unshift tok
|
167
|
+
return nil
|
168
|
+
when :null
|
169
|
+
result = MODL::Model::ModlBoolNull::MODL_NULL
|
170
|
+
when true
|
171
|
+
result = MODL::Model::ModlBoolNull::MODL_TRUE
|
172
|
+
when false
|
173
|
+
result = MODL::Model::ModlBoolNull::MODL_FALSE
|
174
|
+
when :quoted
|
175
|
+
result = MODL::Model::ModlQuoted.new replace_escapes(unquote(tok.value))
|
176
|
+
when :string
|
177
|
+
result = MODL::Model::ModlString.new replace_escapes(tok.value)
|
178
|
+
when :integer
|
179
|
+
result = MODL::Model::ModlInteger.new tok.value
|
180
|
+
when :float
|
181
|
+
result = MODL::Model::ModlFloat.new tok.value
|
182
|
+
else
|
183
|
+
raise ParserError, "Unknown token type in: \'#{tok}\'"
|
184
|
+
end
|
185
|
+
|
186
|
+
peek = tokens[0]
|
187
|
+
raise ParserError, 'Only one primitive allowed at the root.' if !peek.nil? && peek.type == :struct_sep
|
188
|
+
|
189
|
+
if !peek.nil? && (peek.type == :lparen || peek.type == :lbracket || peek.type == :equals)
|
190
|
+
s.unshift tok
|
191
|
+
return nil
|
192
|
+
elsif !peek.nil?
|
193
|
+
raise ParserError, "Unexpected token: \'#{peek}\'"
|
69
194
|
end
|
195
|
+
result
|
196
|
+
end
|
197
|
+
|
198
|
+
def self.parse_modl_map(tokens)
|
199
|
+
first_token = tokens.shift
|
200
|
+
entries = []
|
201
|
+
|
202
|
+
until tokens.empty?
|
203
|
+
peek = tokens[0]
|
204
|
+
if !peek.nil? && peek.type == :rparen
|
205
|
+
tokens.shift
|
206
|
+
break
|
207
|
+
end
|
208
|
+
|
209
|
+
mp = parse_modl_value tokens
|
210
|
+
entries.push mp
|
211
|
+
|
212
|
+
peek = tokens[0]
|
213
|
+
|
214
|
+
raise ParserError, "Expected ')' near #{first_token}" if peek.nil?
|
215
|
+
|
216
|
+
case peek.type
|
217
|
+
when :rparen
|
218
|
+
tokens.shift
|
219
|
+
break
|
220
|
+
when :struct_sep
|
221
|
+
tokens.shift
|
222
|
+
peek = tokens[0]
|
223
|
+
raise ParserError, "Unexpected ; before ] at #{peek}" if !peek.nil? && peek.type == :rparen
|
224
|
+
end
|
225
|
+
end
|
226
|
+
MODL::Model::ModlMap.new entries
|
227
|
+
end
|
228
|
+
|
229
|
+
def self.parse_modl_array(tokens)
|
230
|
+
first_token = tokens.shift
|
231
|
+
entries = []
|
232
|
+
|
233
|
+
until tokens.empty?
|
234
|
+
peek = tokens[0]
|
235
|
+
if !peek.nil? && peek.type == :rbracket
|
236
|
+
tokens.shift
|
237
|
+
break
|
238
|
+
end
|
239
|
+
|
240
|
+
mp = parse_modl_value tokens
|
241
|
+
entries.push mp
|
242
|
+
|
243
|
+
peek = tokens[0]
|
244
|
+
|
245
|
+
raise ParserError, "Expected ']' near #{first_token}" if peek.nil?
|
246
|
+
|
247
|
+
case peek.type
|
248
|
+
when :rbracket
|
249
|
+
tokens.shift
|
250
|
+
break
|
251
|
+
when :struct_sep
|
252
|
+
tokens.shift
|
253
|
+
peek = tokens[0]
|
254
|
+
raise ParserError, "Unexpected ; before ] at #{peek}" if !peek.nil? && peek.type == :rparen
|
255
|
+
end
|
256
|
+
end
|
257
|
+
MODL::Model::ModlArray.new entries
|
258
|
+
end
|
259
|
+
|
260
|
+
def self.unquote(str)
|
261
|
+
return str unless str.instance_of?(String)
|
262
|
+
|
263
|
+
if (str.start_with?('`') && str.end_with?('`')) || (str.start_with?('"') && str.end_with?('"'))
|
264
|
+
str[1..-2]
|
265
|
+
else
|
266
|
+
str
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def self.replace_escapes(str)
|
271
|
+
return str unless str.instance_of?(String)
|
272
|
+
|
273
|
+
result = str
|
274
|
+
i = 0
|
275
|
+
while i < str.length
|
276
|
+
REPLACEMENTS.each_pair do |key, value|
|
277
|
+
if result.slice(i..).start_with?(key)
|
278
|
+
result = result.sub(key, value)
|
279
|
+
break
|
280
|
+
end
|
281
|
+
end
|
282
|
+
i += 1
|
283
|
+
end
|
284
|
+
result
|
285
|
+
end
|
286
|
+
|
287
|
+
def self.all_pairs?(arr)
|
288
|
+
result = true
|
289
|
+
arr.each { |i| result = false unless i.instance_of? MODL::Model::ModlPair }
|
290
|
+
result
|
70
291
|
end
|
71
292
|
end
|
72
293
|
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
|