kaiser-ruby 0.5.1 → 0.7
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 +70 -29
- data/Gemfile.lock +3 -3
- data/README.md +60 -39
- data/TODO.md +0 -8
- data/exe/kaiser-ruby +1 -0
- data/kaiser-ruby.gemspec +1 -1
- data/lib/kaiser_ruby.rb +30 -19
- data/lib/kaiser_ruby/cli.rb +11 -3
- data/lib/kaiser_ruby/parser.rb +710 -0
- data/lib/kaiser_ruby/refinements.rb +200 -0
- data/lib/kaiser_ruby/transformer.rb +358 -0
- data/lib/kaiser_ruby/version.rb +1 -1
- metadata +13 -12
- data/lib/kaiser_ruby/rockstar_parser.rb +0 -310
- data/lib/kaiser_ruby/rockstar_transform.rb +0 -224
@@ -0,0 +1,710 @@
|
|
1
|
+
module KaiserRuby
|
2
|
+
class Parser
|
3
|
+
attr_reader :lines, :raw_input, :tree
|
4
|
+
|
5
|
+
POETIC_STRING_KEYWORDS = %w(says)
|
6
|
+
POETIC_NUMBER_KEYWORDS = %w(is was were are 's 're)
|
7
|
+
POETIC_NUMBER_CONTRACTIONS = %w('s 're)
|
8
|
+
POETIC_TYPE_KEYWORDS = %w(is)
|
9
|
+
PRINT_KEYWORDS = %w(say whisper shout scream)
|
10
|
+
LISTEN_TO_KEYWORDS = ['listen to']
|
11
|
+
LISTEN_KEYWORDS = ['listen']
|
12
|
+
BREAK_KEYWORDS = ['break', 'break it down']
|
13
|
+
CONTINUE_KEYWORDS = ['continue', 'take it to the top']
|
14
|
+
RETURN_KEYWORDS = ['give back']
|
15
|
+
|
16
|
+
INCREMENT_FIRST_KEYWORDS = %w(build)
|
17
|
+
INCREMENT_SECOND_KEYWORDS = %w(up)
|
18
|
+
DECREMENT_FIRST_KEYWORDS = %w(knock)
|
19
|
+
DECREMENT_SECOND_KEYWORDS = %w(down)
|
20
|
+
ASSIGNMENT_FIRST_KEYWORDS = %w(put)
|
21
|
+
ASSIGNMENT_SECOND_KEYWORDS = %w(into)
|
22
|
+
|
23
|
+
FUNCTION_KEYWORDS = %w(takes)
|
24
|
+
FUNCTION_SEPARATORS = ['and', ', and', "'n'", '&', ',']
|
25
|
+
FUNCTION_CALL_KEYWORDS = %w(taking)
|
26
|
+
FUNCTION_CALL_SEPARATORS = [', and', "'n'", '&', ',']
|
27
|
+
IF_KEYWORDS = %w(if)
|
28
|
+
UNTIL_KEYWORDS = %w(until)
|
29
|
+
WHILE_KEYWORDS = %w(while)
|
30
|
+
ELSE_KEYWORDS = %w(else)
|
31
|
+
|
32
|
+
NULL_TYPE = %w(null nothing nowhere nobody gone empty)
|
33
|
+
TRUE_TYPE = %w(true yes ok right)
|
34
|
+
FALSE_TYPE = %w(false no lies wrong)
|
35
|
+
NIL_TYPE = %w(mysterious)
|
36
|
+
POETIC_TYPE_LITERALS = NIL_TYPE + NULL_TYPE + TRUE_TYPE + FALSE_TYPE
|
37
|
+
|
38
|
+
COMMON_VARIABLE_KEYWORDS = %w(a an the my your)
|
39
|
+
PRONOUN_KEYWORDS = %w(he him she her it its they them)
|
40
|
+
|
41
|
+
ADDITION_KEYWORDS = %w(plus with)
|
42
|
+
SUBTRACTION_KEYWORDS = %w(minus without)
|
43
|
+
MULTIPLICATION_KEYWORDS = %w(times of)
|
44
|
+
DIVISION_KEYWORDS = %w(over)
|
45
|
+
MATH_OP_KEYWORDS = ADDITION_KEYWORDS + SUBTRACTION_KEYWORDS + MULTIPLICATION_KEYWORDS + DIVISION_KEYWORDS
|
46
|
+
|
47
|
+
EQUALITY_KEYWORDS = %w(is)
|
48
|
+
INEQUALITY_KEYWORDS = %w(isn't isnt ain't aint is\ not)
|
49
|
+
GT_KEYWORDS = ['is higher than', 'is greater than', 'is bigger than', 'is stronger than']
|
50
|
+
GTE_KEYWORDS = ['is as high as', 'is as great as', 'is as big as', 'is as strong as']
|
51
|
+
LT_KEYWORDS = ['is lower than', 'is less than', 'is smaller than', 'is weaker than']
|
52
|
+
LTE_KEYWORDS = ['is as low as', 'is as little as', 'is as small as', 'is as weak as']
|
53
|
+
COMPARISON_KEYWORDS = EQUALITY_KEYWORDS + INEQUALITY_KEYWORDS + GT_KEYWORDS + GTE_KEYWORDS + LT_KEYWORDS + LTE_KEYWORDS
|
54
|
+
|
55
|
+
FUNCTION_RESTRICTED_KEYWORDS = MATH_OP_KEYWORDS + ['(?<!, )and', 'is', 'or', 'into', 'nor']
|
56
|
+
|
57
|
+
AND_KEYWORDS = %w(and)
|
58
|
+
OR_KEYWORDS = %w(or)
|
59
|
+
NOR_KEYWORDS = %w(nor)
|
60
|
+
NOT_KEYWORDS = ['(?<!is )not']
|
61
|
+
LOGIC_KEYWORDS = AND_KEYWORDS + OR_KEYWORDS + NOT_KEYWORDS + NOR_KEYWORDS
|
62
|
+
|
63
|
+
RESERVED_KEYWORDS = LOGIC_KEYWORDS + MATH_OP_KEYWORDS + POETIC_TYPE_LITERALS
|
64
|
+
|
65
|
+
def initialize(input)
|
66
|
+
@raw_input = input
|
67
|
+
@lines = input.split /\n/
|
68
|
+
end
|
69
|
+
|
70
|
+
def parse
|
71
|
+
@tree = []
|
72
|
+
|
73
|
+
@tree.extend(Hashie::Extensions::DeepLocate)
|
74
|
+
@function_temp = []
|
75
|
+
@nesting = 0
|
76
|
+
@nesting_start_line = 0
|
77
|
+
@lnum = 0
|
78
|
+
|
79
|
+
# parse through lines to get the general structure (statements/flow control/functions/etc) out of it
|
80
|
+
@lines.each_with_index do |line, lnum|
|
81
|
+
@lnum = lnum
|
82
|
+
parse_line(line)
|
83
|
+
end
|
84
|
+
|
85
|
+
func_calls = @tree.deep_locate(:passed_function_call)
|
86
|
+
func_calls.each do |func|
|
87
|
+
str = func[:passed_function_call]
|
88
|
+
num = Integer(str.split('_').last)
|
89
|
+
|
90
|
+
func[:passed_function_call] = parse_function_call(@function_temp[num])
|
91
|
+
end
|
92
|
+
|
93
|
+
@tree
|
94
|
+
end
|
95
|
+
|
96
|
+
def parse_line(line)
|
97
|
+
if line.strip.empty?
|
98
|
+
if @nesting > 0
|
99
|
+
@nesting -= 1
|
100
|
+
@nesting_start_line = nil
|
101
|
+
end
|
102
|
+
|
103
|
+
add_to_tree(parse_empty_line)
|
104
|
+
else
|
105
|
+
obj = parse_line_content(line)
|
106
|
+
add_to_tree obj
|
107
|
+
update_nesting obj
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def update_nesting(object)
|
112
|
+
if %i(if function until while).include? object.keys.first
|
113
|
+
@nesting += 1
|
114
|
+
@nesting_start_line = @lnum
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def parse_line_content(line)
|
119
|
+
words = line.split /\s/
|
120
|
+
|
121
|
+
if matches_first?(words, IF_KEYWORDS)
|
122
|
+
return parse_if(line)
|
123
|
+
elsif matches_first?(words, ELSE_KEYWORDS)
|
124
|
+
return parse_else
|
125
|
+
elsif matches_first?(words, WHILE_KEYWORDS)
|
126
|
+
return parse_while(line)
|
127
|
+
elsif matches_first?(words, UNTIL_KEYWORDS)
|
128
|
+
return parse_until(line)
|
129
|
+
elsif matches_separate?(words, ASSIGNMENT_FIRST_KEYWORDS, ASSIGNMENT_SECOND_KEYWORDS)
|
130
|
+
return parse_assignment(line)
|
131
|
+
elsif matches_several_first?(line, RETURN_KEYWORDS)
|
132
|
+
return parse_return(line)
|
133
|
+
elsif matches_first?(words, PRINT_KEYWORDS)
|
134
|
+
return parse_print(line)
|
135
|
+
else
|
136
|
+
if matches_any?(words, POETIC_STRING_KEYWORDS)
|
137
|
+
return parse_poetic_string(line)
|
138
|
+
elsif matches_any?(words, POETIC_NUMBER_KEYWORDS)
|
139
|
+
return parse_poetic_type_all(line)
|
140
|
+
else
|
141
|
+
return(parse_listen_to(line)) if matches_several_first?(line, LISTEN_TO_KEYWORDS)
|
142
|
+
return(parse_listen) if matches_first?(words, LISTEN_KEYWORDS)
|
143
|
+
return(parse_break) if matches_several_first?(line, BREAK_KEYWORDS)
|
144
|
+
return(parse_continue) if matches_several_first?(line, CONTINUE_KEYWORDS)
|
145
|
+
return(parse_function_call(line)) if matches_any?(words, FUNCTION_CALL_KEYWORDS)
|
146
|
+
|
147
|
+
if matches_separate?(words, INCREMENT_FIRST_KEYWORDS, INCREMENT_SECOND_KEYWORDS)
|
148
|
+
return parse_increment(line)
|
149
|
+
end
|
150
|
+
|
151
|
+
if matches_separate?(words, DECREMENT_FIRST_KEYWORDS, DECREMENT_SECOND_KEYWORDS)
|
152
|
+
return parse_decrement(line)
|
153
|
+
end
|
154
|
+
|
155
|
+
if matches_any?(words, FUNCTION_KEYWORDS)
|
156
|
+
return(parse_function(line))
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
raise KaiserRuby::RockstarSyntaxError, "couldn't parse line: #{line}"
|
162
|
+
end
|
163
|
+
|
164
|
+
# statements
|
165
|
+
def parse_print(line)
|
166
|
+
words = line.partition prepared_regexp(PRINT_KEYWORDS)
|
167
|
+
arg = consume_function_calls(words.last.strip)
|
168
|
+
argument = parse_argument(arg)
|
169
|
+
|
170
|
+
{ print: argument }
|
171
|
+
end
|
172
|
+
|
173
|
+
def parse_listen_to(line)
|
174
|
+
words = line.split prepared_regexp(LISTEN_TO_KEYWORDS)
|
175
|
+
arg = parse_variables(words.last.strip)
|
176
|
+
arg[:type] = :assignment
|
177
|
+
{ listen_to: arg }
|
178
|
+
end
|
179
|
+
|
180
|
+
def parse_listen
|
181
|
+
{ listen: nil }
|
182
|
+
end
|
183
|
+
|
184
|
+
def parse_return(line)
|
185
|
+
words = line.split prepared_regexp(RETURN_KEYWORDS)
|
186
|
+
arg = consume_function_calls(words.last.strip)
|
187
|
+
argument = parse_argument(arg)
|
188
|
+
{ return: argument }
|
189
|
+
end
|
190
|
+
|
191
|
+
def parse_increment(line)
|
192
|
+
match_rxp = prepared_capture(INCREMENT_FIRST_KEYWORDS, INCREMENT_SECOND_KEYWORDS)
|
193
|
+
var = line.match(match_rxp).captures.first.strip
|
194
|
+
capture = parse_variables(var)
|
195
|
+
|
196
|
+
capture[:amount] = line.split(var).last.scan(/\bup\b/i).count
|
197
|
+
{ increment: capture }
|
198
|
+
end
|
199
|
+
|
200
|
+
def parse_decrement(line)
|
201
|
+
match_rxp = prepared_capture(DECREMENT_FIRST_KEYWORDS, DECREMENT_SECOND_KEYWORDS)
|
202
|
+
var = line.match(match_rxp).captures.first.strip
|
203
|
+
capture = parse_variables(var)
|
204
|
+
|
205
|
+
capture[:amount] = line.split(var).last.scan(/\bdown\b/i).count
|
206
|
+
{ decrement: capture }
|
207
|
+
end
|
208
|
+
|
209
|
+
def parse_else
|
210
|
+
{ else: nil }
|
211
|
+
end
|
212
|
+
|
213
|
+
def parse_empty_line
|
214
|
+
{ empty_line: nil }
|
215
|
+
end
|
216
|
+
|
217
|
+
def parse_break
|
218
|
+
{ break: nil }
|
219
|
+
end
|
220
|
+
|
221
|
+
def parse_continue
|
222
|
+
{ continue: nil }
|
223
|
+
end
|
224
|
+
|
225
|
+
def parse_function_definition_arguments(string)
|
226
|
+
words = string.split Regexp.new(FUNCTION_SEPARATORS.join('|'))
|
227
|
+
arguments = []
|
228
|
+
words.each do |w|
|
229
|
+
arg = parse_value_or_variable(w.strip)
|
230
|
+
arg[:local_variable_name] = arg.delete(:variable_name)
|
231
|
+
arguments << arg
|
232
|
+
end
|
233
|
+
|
234
|
+
{ argument_list: arguments }
|
235
|
+
end
|
236
|
+
|
237
|
+
def parse_function_call_arguments(string)
|
238
|
+
words = string.split(Regexp.new(FUNCTION_CALL_SEPARATORS.join('|')))
|
239
|
+
arguments = []
|
240
|
+
words.each do |w|
|
241
|
+
arguments << parse_value_or_variable(w.strip)
|
242
|
+
end
|
243
|
+
|
244
|
+
{ argument_list: arguments }
|
245
|
+
end
|
246
|
+
|
247
|
+
def parse_function_call(line)
|
248
|
+
words = line.split /\s/
|
249
|
+
if matches_any?(words, FUNCTION_CALL_KEYWORDS)
|
250
|
+
words = line.split prepared_regexp(FUNCTION_CALL_KEYWORDS)
|
251
|
+
left = parse_function_name(words.first.strip)
|
252
|
+
right = parse_function_call_arguments(words.last.strip)
|
253
|
+
{ function_call: { left: left, right: right } }
|
254
|
+
else
|
255
|
+
return false
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
def parse_poetic_string(line)
|
260
|
+
words = line.partition prepared_regexp(POETIC_STRING_KEYWORDS)
|
261
|
+
left = parse_variables(words.first.strip)
|
262
|
+
right = { string: "\"#{words.last.strip}\"" }
|
263
|
+
left[:type] = :assignment
|
264
|
+
{ poetic_string: { left: left, right: right } }
|
265
|
+
end
|
266
|
+
|
267
|
+
def parse_poetic_type_all(line)
|
268
|
+
words = line.partition prepared_regexp(POETIC_NUMBER_KEYWORDS)
|
269
|
+
left = parse_variables(words.first.strip)
|
270
|
+
right = parse_type_value(words.last.strip)
|
271
|
+
left[:type] = :assignment
|
272
|
+
{ poetic_type: { left: left, right: right } }
|
273
|
+
end
|
274
|
+
|
275
|
+
def parse_type_value(string)
|
276
|
+
words = string.split /\s/
|
277
|
+
|
278
|
+
if matches_first?(words, NIL_TYPE)
|
279
|
+
raise KaiserRuby::RockstarSyntaxError, "extra words are not allowed after literal type keyword" if words.count > 1
|
280
|
+
{ type: 'nil' }
|
281
|
+
elsif matches_first?(words, NULL_TYPE)
|
282
|
+
raise KaiserRuby::RockstarSyntaxError, "extra words are not allowed after literal type keyword" if words.count > 1
|
283
|
+
{ type: 'null' }
|
284
|
+
elsif matches_first?(words, TRUE_TYPE)
|
285
|
+
raise KaiserRuby::RockstarSyntaxError, "extra words are not allowed after literal type keyword" if words.count > 1
|
286
|
+
{ type: 'true' }
|
287
|
+
elsif matches_first?(words, FALSE_TYPE)
|
288
|
+
raise KaiserRuby::RockstarSyntaxError, "extra words are not allowed after literal type keyword" if words.count > 1
|
289
|
+
{ type: 'false' }
|
290
|
+
elsif string.strip.start_with?('"') && string.strip.end_with?('"')
|
291
|
+
parse_literal_string(string)
|
292
|
+
else
|
293
|
+
parse_poetic_number_value(string)
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
def parse_type_literal(string)
|
298
|
+
words = string.split /\s/
|
299
|
+
raise SyntaxError, "too many words in poetic type literal: #{string}" if words.size > 1
|
300
|
+
|
301
|
+
if matches_first?(words, NIL_TYPE)
|
302
|
+
{ type: 'nil' }
|
303
|
+
elsif matches_first?(words, NULL_TYPE)
|
304
|
+
{ type: 'null' }
|
305
|
+
elsif matches_first?(words, TRUE_TYPE)
|
306
|
+
{ type: 'true' }
|
307
|
+
elsif matches_first?(words, FALSE_TYPE)
|
308
|
+
{ type: 'false' }
|
309
|
+
else
|
310
|
+
raise SyntaxError, "unknown poetic type literal: #{string}"
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def parse_assignment(line)
|
315
|
+
match_rxp = prepared_capture(ASSIGNMENT_FIRST_KEYWORDS, ASSIGNMENT_SECOND_KEYWORDS)
|
316
|
+
right = parse_argument(line.match(match_rxp).captures.first.strip)
|
317
|
+
left = parse_variables(line.match(match_rxp).captures.last.strip)
|
318
|
+
left[:type] = :assignment
|
319
|
+
{ assignment: { left: left, right: right } }
|
320
|
+
end
|
321
|
+
|
322
|
+
def parse_if(line)
|
323
|
+
words = line.split prepared_regexp(IF_KEYWORDS)
|
324
|
+
|
325
|
+
arg = consume_function_calls(words.last.strip)
|
326
|
+
argument = parse_argument(arg)
|
327
|
+
{ if: { argument: argument } }
|
328
|
+
end
|
329
|
+
|
330
|
+
def parse_until(line)
|
331
|
+
words = line.split prepared_regexp(UNTIL_KEYWORDS)
|
332
|
+
arg = consume_function_calls(words.last.strip)
|
333
|
+
argument = parse_argument(arg)
|
334
|
+
{ until: { argument: argument } }
|
335
|
+
end
|
336
|
+
|
337
|
+
def parse_while(line)
|
338
|
+
words = line.split prepared_regexp(WHILE_KEYWORDS)
|
339
|
+
arg = consume_function_calls(words.last.strip)
|
340
|
+
argument = parse_argument(arg)
|
341
|
+
{ while: { argument: argument } }
|
342
|
+
end
|
343
|
+
|
344
|
+
def parse_function(line)
|
345
|
+
words = line.split prepared_regexp(FUNCTION_KEYWORDS)
|
346
|
+
funcname = parse_function_name(words.first.strip)
|
347
|
+
argument = parse_function_definition_arguments(words.last.strip)
|
348
|
+
{ function: { name: funcname, argument: argument } }
|
349
|
+
end
|
350
|
+
|
351
|
+
def consume_function_calls(string)
|
352
|
+
if string =~ prepared_regexp(FUNCTION_CALL_KEYWORDS)
|
353
|
+
words = string.split prepared_regexp(FUNCTION_RESTRICTED_KEYWORDS)
|
354
|
+
found_string = words.select { |w| w =~ (/\btaking\b/) }.first
|
355
|
+
@function_temp << found_string
|
356
|
+
string = string.gsub(found_string, " func_#{@function_temp.count - 1} ")
|
357
|
+
end
|
358
|
+
|
359
|
+
string
|
360
|
+
end
|
361
|
+
|
362
|
+
def pass_function_calls(string)
|
363
|
+
if string.strip =~ /func_\d+\Z/
|
364
|
+
{ passed_function_call: string }
|
365
|
+
else
|
366
|
+
return false
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
def parse_argument(string)
|
371
|
+
str = parse_literal_string(string)
|
372
|
+
return str if str
|
373
|
+
|
374
|
+
exp = parse_logic_operation(string)
|
375
|
+
return exp if exp
|
376
|
+
|
377
|
+
cmp = parse_comparison(string)
|
378
|
+
return cmp if cmp
|
379
|
+
|
380
|
+
math = parse_math_operations(string)
|
381
|
+
return math if math
|
382
|
+
|
383
|
+
fcl2 = pass_function_calls(string)
|
384
|
+
return fcl2 if fcl2
|
385
|
+
|
386
|
+
fcl = parse_function_call(string)
|
387
|
+
return fcl if fcl
|
388
|
+
|
389
|
+
vals = parse_value_or_variable(string)
|
390
|
+
return vals if vals
|
391
|
+
end
|
392
|
+
|
393
|
+
def parse_value_or_variable(string)
|
394
|
+
nt = parse_not(string)
|
395
|
+
return nt if nt
|
396
|
+
|
397
|
+
str = parse_literal_string(string)
|
398
|
+
return str if str
|
399
|
+
|
400
|
+
num = parse_literal_number(string)
|
401
|
+
return num if num
|
402
|
+
|
403
|
+
vars = parse_variables(string)
|
404
|
+
return vars if vars
|
405
|
+
|
406
|
+
tpl = parse_type_literal(string)
|
407
|
+
return tpl if tpl
|
408
|
+
end
|
409
|
+
|
410
|
+
def parse_poetic_number_value(string)
|
411
|
+
num = parse_literal_number(string)
|
412
|
+
if num
|
413
|
+
return num
|
414
|
+
else
|
415
|
+
return { number_literal: string.strip }
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
def parse_logic_operation(string)
|
420
|
+
testable = string.partition(prepared_regexp(LOGIC_KEYWORDS))
|
421
|
+
return false if testable.first.count('"').odd? || testable.last.count('"').odd?
|
422
|
+
|
423
|
+
if string =~ prepared_regexp(AND_KEYWORDS)
|
424
|
+
return parse_and(string)
|
425
|
+
elsif string =~ prepared_regexp(OR_KEYWORDS)
|
426
|
+
return parse_or(string)
|
427
|
+
elsif string =~ prepared_regexp(NOR_KEYWORDS)
|
428
|
+
return parse_nor(string)
|
429
|
+
end
|
430
|
+
|
431
|
+
return false
|
432
|
+
end
|
433
|
+
|
434
|
+
def parse_and(string)
|
435
|
+
words = string.rpartition prepared_regexp(AND_KEYWORDS)
|
436
|
+
left = parse_argument(words.first.strip)
|
437
|
+
right = parse_argument(words.last.strip)
|
438
|
+
|
439
|
+
{ and: { left: left, right: right } }
|
440
|
+
end
|
441
|
+
|
442
|
+
def parse_or(string)
|
443
|
+
words = string.rpartition prepared_regexp(OR_KEYWORDS)
|
444
|
+
|
445
|
+
left = parse_argument(words.first.strip)
|
446
|
+
right = parse_argument(words.last.strip)
|
447
|
+
|
448
|
+
{ or: { left: left, right: right } }
|
449
|
+
end
|
450
|
+
|
451
|
+
def parse_nor(string)
|
452
|
+
words = string.rpartition prepared_regexp(NOR_KEYWORDS)
|
453
|
+
|
454
|
+
left = parse_argument(words.first.strip)
|
455
|
+
right = parse_argument(words.last.strip)
|
456
|
+
|
457
|
+
{ nor: { left: left, right: right } }
|
458
|
+
end
|
459
|
+
|
460
|
+
def parse_not(string)
|
461
|
+
return false if string !~ /(?<!is )\bnot\b/i
|
462
|
+
words = string.split prepared_regexp(NOT_KEYWORDS)
|
463
|
+
argument = parse_argument(words.last.strip)
|
464
|
+
|
465
|
+
{ not: argument }
|
466
|
+
end
|
467
|
+
|
468
|
+
def parse_comparison(string)
|
469
|
+
return false if string.strip.start_with?('"') && string.strip.strip.end_with?('"') && string.count('"') == 2
|
470
|
+
words = string.split(/\s/)
|
471
|
+
|
472
|
+
if string =~ prepared_regexp(GT_KEYWORDS)
|
473
|
+
return parse_gt(string)
|
474
|
+
elsif string =~ prepared_regexp(GTE_KEYWORDS)
|
475
|
+
return parse_gte(string)
|
476
|
+
elsif string =~ prepared_regexp(LT_KEYWORDS)
|
477
|
+
return parse_lt(string)
|
478
|
+
elsif string =~ prepared_regexp(LTE_KEYWORDS)
|
479
|
+
return parse_lte(string)
|
480
|
+
elsif string =~ prepared_regexp(INEQUALITY_KEYWORDS)
|
481
|
+
return parse_inequality(string)
|
482
|
+
elsif string =~ prepared_regexp(EQUALITY_KEYWORDS)
|
483
|
+
return parse_equality(string)
|
484
|
+
end
|
485
|
+
|
486
|
+
return false
|
487
|
+
end
|
488
|
+
|
489
|
+
def parse_equality(string)
|
490
|
+
words = string.rpartition prepared_regexp(EQUALITY_KEYWORDS)
|
491
|
+
left = parse_argument(words.first.strip)
|
492
|
+
right = parse_argument(words.last.strip)
|
493
|
+
|
494
|
+
{ equality: { left: left, right: right } }
|
495
|
+
end
|
496
|
+
|
497
|
+
def parse_inequality(string)
|
498
|
+
words = string.rpartition prepared_regexp(INEQUALITY_KEYWORDS)
|
499
|
+
left = parse_argument(words.first.strip)
|
500
|
+
right = parse_argument(words.last.strip)
|
501
|
+
|
502
|
+
{ inequality: { left: left, right: right } }
|
503
|
+
end
|
504
|
+
|
505
|
+
def parse_gt(string)
|
506
|
+
words = string.rpartition prepared_regexp(GT_KEYWORDS)
|
507
|
+
left = parse_argument(words.first.strip)
|
508
|
+
right = parse_argument(words.last.strip)
|
509
|
+
|
510
|
+
{ gt: { left: left, right: right } }
|
511
|
+
end
|
512
|
+
|
513
|
+
def parse_gte(string)
|
514
|
+
words = string.rpartition prepared_regexp(GTE_KEYWORDS)
|
515
|
+
left = parse_argument(words.first.strip)
|
516
|
+
right = parse_argument(words.last.strip)
|
517
|
+
|
518
|
+
{ gte: { left: left, right: right } }
|
519
|
+
end
|
520
|
+
|
521
|
+
def parse_lt(string)
|
522
|
+
words = string.rpartition prepared_regexp(LT_KEYWORDS)
|
523
|
+
left = parse_argument(words.first.strip)
|
524
|
+
right = parse_argument(words.last.strip)
|
525
|
+
|
526
|
+
{ lt: { left: left, right: right } }
|
527
|
+
end
|
528
|
+
|
529
|
+
def parse_lte(string)
|
530
|
+
words = string.rpartition prepared_regexp(LTE_KEYWORDS)
|
531
|
+
left = parse_argument(words.first.strip)
|
532
|
+
right = parse_argument(words.last.strip)
|
533
|
+
|
534
|
+
{ lte: { left: left, right: right } }
|
535
|
+
end
|
536
|
+
|
537
|
+
def parse_variables(string)
|
538
|
+
words = string.split /\s/
|
539
|
+
words = words.map { |e| e.chars.select { |c| c =~ /[[:alnum:]]|\./ }.join }
|
540
|
+
string = words.join(' ')
|
541
|
+
|
542
|
+
if string =~ prepared_regexp(PRONOUN_KEYWORDS)
|
543
|
+
return parse_pronoun
|
544
|
+
elsif matches_first?(words, COMMON_VARIABLE_KEYWORDS)
|
545
|
+
return parse_common_variable(string)
|
546
|
+
elsif matches_all?(words, /\A[[:upper:]]/) && string !~ prepared_regexp(RESERVED_KEYWORDS)
|
547
|
+
return parse_proper_variable(string)
|
548
|
+
end
|
549
|
+
|
550
|
+
return false
|
551
|
+
end
|
552
|
+
|
553
|
+
def parse_function_name(string)
|
554
|
+
fname = parse_variables(string)
|
555
|
+
fname[:function_name] = fname.delete(:variable_name)
|
556
|
+
fname
|
557
|
+
end
|
558
|
+
|
559
|
+
def parse_common_variable(string)
|
560
|
+
words = string.split(/\s/)
|
561
|
+
|
562
|
+
copied = words.dup
|
563
|
+
copied.shift
|
564
|
+
copied.each do |w|
|
565
|
+
raise SyntaxError, "invalid common variable name: #{string}" if w =~ /[[:upper:]]/
|
566
|
+
end
|
567
|
+
|
568
|
+
words = words.map { |e| e.chars.select { |c| c =~ /[[:alpha:]]/ }.join }
|
569
|
+
{ variable_name: words.map { |w| w.downcase }.join('_') }
|
570
|
+
end
|
571
|
+
|
572
|
+
def parse_proper_variable(string)
|
573
|
+
words = string.split(/\s/)
|
574
|
+
|
575
|
+
copied = words.dup
|
576
|
+
copied.shift
|
577
|
+
copied.each do |w|
|
578
|
+
raise SyntaxError, "invalid proper variable name: #{string}" unless w =~ /\A[[:upper:]]/
|
579
|
+
end
|
580
|
+
|
581
|
+
words = words.map { |e| e.chars.select { |c| c =~ /[[:alpha:]]/ }.join }
|
582
|
+
{ variable_name: words.map { |w| w.downcase }.join('_') }
|
583
|
+
end
|
584
|
+
|
585
|
+
def parse_pronoun
|
586
|
+
{ pronoun: nil }
|
587
|
+
end
|
588
|
+
|
589
|
+
def parse_math_operations(string)
|
590
|
+
return false if string.strip.start_with?('"') && string.strip.end_with?('"') && string.count('"') == 2
|
591
|
+
words = string.split(/\s/)
|
592
|
+
|
593
|
+
if matches_any?(words, MULTIPLICATION_KEYWORDS)
|
594
|
+
return parse_multiplication(string)
|
595
|
+
elsif matches_any?(words, DIVISION_KEYWORDS)
|
596
|
+
return parse_division(string)
|
597
|
+
elsif matches_any?(words, ADDITION_KEYWORDS)
|
598
|
+
return parse_addition(string)
|
599
|
+
elsif matches_any?(words, SUBTRACTION_KEYWORDS)
|
600
|
+
return parse_subtraction(string)
|
601
|
+
end
|
602
|
+
|
603
|
+
return false
|
604
|
+
end
|
605
|
+
|
606
|
+
def parse_addition(string)
|
607
|
+
words = string.rpartition prepared_regexp(ADDITION_KEYWORDS)
|
608
|
+
left = parse_argument(words.first.strip)
|
609
|
+
right = parse_argument(words.last.strip)
|
610
|
+
|
611
|
+
{ addition: { left: left, right: right } }
|
612
|
+
end
|
613
|
+
|
614
|
+
def parse_subtraction(string)
|
615
|
+
words = string.rpartition prepared_regexp(SUBTRACTION_KEYWORDS)
|
616
|
+
left = parse_argument(words.first.strip)
|
617
|
+
right = parse_argument(words.last.strip)
|
618
|
+
|
619
|
+
{ subtraction: { left: left, right: right } }
|
620
|
+
end
|
621
|
+
|
622
|
+
def parse_multiplication(string)
|
623
|
+
words = string.rpartition prepared_regexp(MULTIPLICATION_KEYWORDS)
|
624
|
+
left = parse_argument(words.first.strip)
|
625
|
+
right = parse_argument(words.last.strip)
|
626
|
+
|
627
|
+
{ multiplication: { left: left, right: right } }
|
628
|
+
end
|
629
|
+
|
630
|
+
def parse_division(string)
|
631
|
+
words = string.rpartition prepared_regexp(DIVISION_KEYWORDS)
|
632
|
+
left = parse_argument(words.first.strip)
|
633
|
+
right = parse_argument(words.last.strip)
|
634
|
+
|
635
|
+
{ division: { left: left, right: right } }
|
636
|
+
end
|
637
|
+
|
638
|
+
def parse_literal_string(string)
|
639
|
+
if string.strip.start_with?('"') && string.strip.end_with?('"') && string.count('"') == 2
|
640
|
+
{ string: string }
|
641
|
+
else
|
642
|
+
return false
|
643
|
+
end
|
644
|
+
end
|
645
|
+
|
646
|
+
def parse_literal_number(string)
|
647
|
+
num = Float(string) rescue string
|
648
|
+
if num.is_a?(Float)
|
649
|
+
{ number: num }
|
650
|
+
else
|
651
|
+
return false
|
652
|
+
end
|
653
|
+
end
|
654
|
+
|
655
|
+
#private
|
656
|
+
|
657
|
+
def add_to_tree(object)
|
658
|
+
object.extend(Hashie::Extensions::DeepLocate)
|
659
|
+
|
660
|
+
if @nesting > 0
|
661
|
+
object[:nesting_start_line] = @nesting_start_line
|
662
|
+
object[:nesting] = @nesting
|
663
|
+
end
|
664
|
+
@tree << object
|
665
|
+
end
|
666
|
+
|
667
|
+
def prepared_regexp(array)
|
668
|
+
rxp = array.map { |a| '\b' + a + '\b' }.join('|')
|
669
|
+
Regexp.new(rxp, Regexp::IGNORECASE)
|
670
|
+
end
|
671
|
+
|
672
|
+
def prepared_capture(farr, sarr)
|
673
|
+
frxp = farr.map { |a| '\b' + a + '\b' }.join('|')
|
674
|
+
srxp = sarr.map { |a| '\b' + a + '\b' }.join('|')
|
675
|
+
Regexp.new(frxp + '(.*?)' + srxp + '(.*)', Regexp::IGNORECASE)
|
676
|
+
end
|
677
|
+
|
678
|
+
def matches_any?(words, rxp)
|
679
|
+
regexp = rxp.is_a?(Regexp) ? rxp : prepared_regexp(rxp)
|
680
|
+
words.any? { |w| w =~ regexp }
|
681
|
+
end
|
682
|
+
|
683
|
+
def matches_all?(words, rxp)
|
684
|
+
regexp = rxp.is_a?(Regexp) ? rxp : prepared_regexp(rxp)
|
685
|
+
words.all? { |w| w =~ regexp }
|
686
|
+
end
|
687
|
+
|
688
|
+
def matches_consecutive?(words, first_rxp, second_rxp)
|
689
|
+
first_idx = words.index { |w| w =~ prepared_regexp(first_rxp) }
|
690
|
+
second_idx = words.index { |w| w =~ prepared_regexp(second_rxp) }
|
691
|
+
|
692
|
+
second_idx != nil && first_idx != nil && second_idx.to_i - first_idx.to_i == 1
|
693
|
+
end
|
694
|
+
|
695
|
+
def matches_first?(words, rxp)
|
696
|
+
words.index { |w| w =~ prepared_regexp(rxp) } == 0
|
697
|
+
end
|
698
|
+
|
699
|
+
def matches_several_first?(line, rxp)
|
700
|
+
(line =~ prepared_regexp(rxp)) == 0
|
701
|
+
end
|
702
|
+
|
703
|
+
def matches_separate?(words, first_rxp, second_rxp)
|
704
|
+
first_idx = words.index { |w| w =~ prepared_regexp(first_rxp) }
|
705
|
+
second_idx = words.index { |w| w =~ prepared_regexp(second_rxp) }
|
706
|
+
|
707
|
+
second_idx != nil && first_idx != nil && second_idx.to_i > first_idx.to_i
|
708
|
+
end
|
709
|
+
end
|
710
|
+
end
|