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.
@@ -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