kaiser-ruby 0.7.1 → 0.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +11 -0
- data/.ruby-version +1 -1
- data/.travis.yml +5 -4
- data/CHANGELOG.md +34 -10
- data/Gemfile +4 -2
- data/Gemfile.lock +39 -24
- data/Rakefile +5 -3
- data/bin/console +7 -3
- data/exe/kaiser-ruby +8 -6
- data/kaiser-ruby.gemspec +19 -16
- data/lib/kaiser_ruby.rb +4 -6
- data/lib/kaiser_ruby/cli.rb +19 -7
- data/lib/kaiser_ruby/parser.rb +189 -144
- data/lib/kaiser_ruby/refinements.rb +409 -28
- data/lib/kaiser_ruby/transformer.rb +112 -60
- data/lib/kaiser_ruby/version.rb +3 -1
- metadata +39 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 040ea9398c1f1cb0499f3a5d21038b9ee52434a9370145ec8efd5e1217264431
|
4
|
+
data.tar.gz: b7eb871f93fb4f68570685d9376c75698ca23e16b12752e6157cd046acacc6ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bdd9abbab639f244d3a97799a25dc8c0ef5b5dfc582011a6b1d3cc02df804c7ca2d3ca8746214ba5b7df101af8967021e9fe20a010e86c8121bd122b68ea98f0
|
7
|
+
data.tar.gz: 3c04422053c184d14c59740bceb57212b4f8b1d3483c916ee8c131128af2315cf30a2040dbf0c431dd31c27100372b858580b3180739975f593a639e6f336945
|
data/.rubocop.yml
ADDED
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.6.2
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,26 @@
|
|
1
|
+
# 0.8
|
2
|
+
|
3
|
+
Language implementation:
|
4
|
+
|
5
|
+
- [x] Add support for simple variables
|
6
|
+
- [x] Add support for rest of the pronouns
|
7
|
+
- [x] Return integers instead of floats where applicable so it will show `5` instead of `5.0` if there's no need for a Float
|
8
|
+
- [x] Introduce proper `mysterious` type to allow `nil` to be used more predictably
|
9
|
+
- [x] Update pronouns only on variable assignment, not on usage too
|
10
|
+
- [x] Fix double elses without preceding empty line to end the `if/else` block explicitly
|
11
|
+
- [x] Comments are properly ignored now (even multiline ones though they're a really bad idea anyway)
|
12
|
+
- [x] Math operators like `+` are also handled in addition to `plus`
|
13
|
+
- [x] Strings are no longer considered falsy if they contain a falsy alias (`lies`, `false` etc), they're always truthy
|
14
|
+
- [x] Support `let <variable> be <expression>`
|
15
|
+
- [x] Handle recursion
|
16
|
+
- [x] Handle nested functions and their scoping
|
17
|
+
|
18
|
+
Other changes:
|
19
|
+
|
20
|
+
- [x] Add new reference tests from main language repository
|
21
|
+
- [x] Fix else nesting
|
22
|
+
- [x] Fix older Ruby versions
|
23
|
+
|
1
24
|
# 0.7.1
|
2
25
|
|
3
26
|
- [x] Strips all lines while parsing, so you can indent the .rock file however you want
|
@@ -7,7 +30,7 @@
|
|
7
30
|
|
8
31
|
# 0.7
|
9
32
|
|
10
|
-
Language
|
33
|
+
Language implementation:
|
11
34
|
|
12
35
|
- [x] Full language implementation according to the spec
|
13
36
|
|
@@ -27,7 +50,7 @@ Language Implementation
|
|
27
50
|
- [x] Multiple increments/decrements with `build X up, up up` and `knock Y down, down, down`
|
28
51
|
- [x] Handle global variables if they're declared after function definition that uses them
|
29
52
|
|
30
|
-
Other
|
53
|
+
Other changes:
|
31
54
|
|
32
55
|
- [x] Replaced parsing with Parslet with a hand-written parser in plain Ruby
|
33
56
|
- [x] Implemented reference tests
|
@@ -38,26 +61,27 @@ Other
|
|
38
61
|
|
39
62
|
# 0.6 - unreleased
|
40
63
|
|
64
|
+
Other changes:
|
65
|
+
|
41
66
|
- [x] Fixed error in input from STDIN
|
42
67
|
- [x] The transpiler now throws a SyntaxError instead of Parslet exception
|
43
|
-
|
44
|
-
Test Suite
|
45
|
-
|
46
68
|
- [x] Refactored the test suite to make more sense
|
47
69
|
- [x] Added a ton of new negative and positive tests
|
48
70
|
|
49
71
|
# 0.5
|
50
72
|
|
73
|
+
Language implementation:
|
74
|
+
|
51
75
|
- [x] Fixed converting decimals so "Conversion is lovestruck. lovestruck and essential seasick" results in "conversion = 0.0397" as one would expect.
|
52
76
|
|
53
|
-
Other
|
77
|
+
Other changes:
|
54
78
|
|
55
79
|
- [x] Updated the REPL to work on Ruby versions earlier than 2.5 (2.3 is the minimum supported version)
|
56
80
|
- [x] Travis CI tests on all supported Ruby versions
|
57
81
|
|
58
82
|
# 0.4
|
59
83
|
|
60
|
-
Language
|
84
|
+
Language implementation:
|
61
85
|
|
62
86
|
- [x] Handle non-alpha values in quoted strings
|
63
87
|
- [x] Ignore comments in parentheses
|
@@ -67,7 +91,7 @@ Language Implementation:
|
|
67
91
|
- [x] Better handle input of integers from STDIN
|
68
92
|
- [x] Print can print returned values from functions
|
69
93
|
|
70
|
-
Other
|
94
|
+
Other changes:
|
71
95
|
|
72
96
|
- [x] Updated the FizzBuzz example
|
73
97
|
- [x] Catch exceptions in the REPL
|
@@ -77,7 +101,7 @@ Other stuff:
|
|
77
101
|
|
78
102
|
# 0.3
|
79
103
|
|
80
|
-
Language
|
104
|
+
Language implementation:
|
81
105
|
|
82
106
|
- [x] Handle null type differently - nil in Ruby isn't really comparable to 0
|
83
107
|
- [x] Handle mysterious type - probably this should be nil and what is now nil should be 0 instead
|
@@ -91,7 +115,7 @@ Language Implementation:
|
|
91
115
|
- [x] Handle function calls
|
92
116
|
- [x] Return can return math operations directly
|
93
117
|
|
94
|
-
Other
|
118
|
+
Other changes:
|
95
119
|
|
96
120
|
- [x] FizzBuzz example is working
|
97
121
|
- [x] Fibonacci example is working
|
data/Gemfile
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
source 'https://rubygems.org'
|
4
|
+
|
5
|
+
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
4
6
|
|
5
7
|
# Specify your gem's dependencies in kaiser-ruby.gemspec
|
6
8
|
gemspec
|
data/Gemfile.lock
CHANGED
@@ -1,45 +1,60 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
kaiser-ruby (0.
|
4
|
+
kaiser-ruby (0.8)
|
5
5
|
hashie
|
6
|
-
thor
|
6
|
+
thor
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
|
+
ast (2.4.0)
|
11
12
|
coderay (1.1.2)
|
12
13
|
diff-lcs (1.3)
|
13
14
|
hashie (3.6.0)
|
14
|
-
|
15
|
-
|
15
|
+
jaro_winkler (1.5.2)
|
16
|
+
method_source (0.9.2)
|
17
|
+
parallel (1.15.0)
|
18
|
+
parser (2.6.2.0)
|
19
|
+
ast (~> 2.4.0)
|
20
|
+
pry (0.12.2)
|
16
21
|
coderay (~> 1.1.0)
|
17
22
|
method_source (~> 0.9.0)
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
rspec-
|
23
|
-
|
24
|
-
rspec-
|
25
|
-
rspec-
|
23
|
+
psych (3.1.0)
|
24
|
+
rainbow (3.0.0)
|
25
|
+
rake (12.3.2)
|
26
|
+
rspec (3.8.0)
|
27
|
+
rspec-core (~> 3.8.0)
|
28
|
+
rspec-expectations (~> 3.8.0)
|
29
|
+
rspec-mocks (~> 3.8.0)
|
30
|
+
rspec-core (3.8.0)
|
31
|
+
rspec-support (~> 3.8.0)
|
32
|
+
rspec-expectations (3.8.2)
|
26
33
|
diff-lcs (>= 1.2.0, < 2.0)
|
27
|
-
rspec-support (~> 3.
|
28
|
-
rspec-mocks (3.
|
34
|
+
rspec-support (~> 3.8.0)
|
35
|
+
rspec-mocks (3.8.0)
|
29
36
|
diff-lcs (>= 1.2.0, < 2.0)
|
30
|
-
rspec-support (~> 3.
|
31
|
-
rspec-support (3.
|
32
|
-
|
37
|
+
rspec-support (~> 3.8.0)
|
38
|
+
rspec-support (3.8.0)
|
39
|
+
rubocop (0.66.0)
|
40
|
+
jaro_winkler (~> 1.5.1)
|
41
|
+
parallel (~> 1.10)
|
42
|
+
parser (>= 2.5, != 2.5.1.1)
|
43
|
+
psych (>= 3.1.0)
|
44
|
+
rainbow (>= 2.2.2, < 4.0)
|
45
|
+
ruby-progressbar (~> 1.7)
|
46
|
+
unicode-display_width (>= 1.4.0, < 1.6)
|
47
|
+
ruby-progressbar (1.10.0)
|
48
|
+
thor (0.20.3)
|
49
|
+
unicode-display_width (1.5.0)
|
33
50
|
|
34
51
|
PLATFORMS
|
35
52
|
ruby
|
36
53
|
|
37
54
|
DEPENDENCIES
|
38
|
-
bundler (~> 1.
|
55
|
+
bundler (~> 1.17.0)
|
39
56
|
kaiser-ruby!
|
40
|
-
pry
|
41
|
-
rake
|
42
|
-
rspec
|
43
|
-
|
44
|
-
BUNDLED WITH
|
45
|
-
1.16.2
|
57
|
+
pry
|
58
|
+
rake
|
59
|
+
rspec
|
60
|
+
rubocop
|
data/Rakefile
CHANGED
data/bin/console
CHANGED
data/exe/kaiser-ruby
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require "bundler/setup"
|
4
|
-
require "kaiser_ruby"
|
5
|
-
require "kaiser_ruby/cli"
|
6
|
-
require "kaiser_ruby/refinements"
|
2
|
+
# frozen_string_literal: true
|
7
3
|
|
8
|
-
|
4
|
+
require 'rubygems'
|
5
|
+
require 'bundler/setup'
|
6
|
+
require 'kaiser_ruby'
|
7
|
+
require 'kaiser_ruby/cli'
|
8
|
+
require 'kaiser_ruby/refinements'
|
9
|
+
|
10
|
+
KaiserRuby::CLI.start(ARGV)
|
data/kaiser-ruby.gemspec
CHANGED
@@ -1,30 +1,33 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
2
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
-
require
|
5
|
+
require 'kaiser_ruby/version'
|
4
6
|
|
5
7
|
Gem::Specification.new do |spec|
|
6
|
-
spec.name =
|
8
|
+
spec.name = 'kaiser-ruby'
|
7
9
|
spec.version = KaiserRuby::VERSION
|
8
|
-
spec.authors = [
|
9
|
-
spec.email = [
|
10
|
+
spec.authors = ['Marcin Ruszkiewicz']
|
11
|
+
spec.email = ['marcin.ruszkiewicz@polcode.net']
|
10
12
|
|
11
|
-
spec.summary =
|
12
|
-
spec.homepage =
|
13
|
-
spec.license =
|
13
|
+
spec.summary = 'Transpiler of Rockstar language to Ruby'
|
14
|
+
spec.homepage = 'https://github.com/marcinruszkiewicz/kaiser-ruby'
|
15
|
+
spec.license = 'MIT'
|
14
16
|
|
15
17
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
16
18
|
f.match(%r{^(spec|examples)/})
|
17
19
|
end
|
18
|
-
spec.bindir =
|
20
|
+
spec.bindir = 'exe'
|
19
21
|
spec.executables = ['kaiser-ruby']
|
20
|
-
spec.require_paths = [
|
22
|
+
spec.require_paths = ['lib']
|
21
23
|
|
22
24
|
spec.required_ruby_version = '>= 2.3'
|
23
25
|
|
24
|
-
spec.add_development_dependency
|
25
|
-
spec.add_development_dependency
|
26
|
-
spec.add_development_dependency
|
27
|
-
spec.add_development_dependency
|
28
|
-
spec.
|
29
|
-
spec.add_dependency
|
26
|
+
spec.add_development_dependency 'bundler', '~> 1.17.0'
|
27
|
+
spec.add_development_dependency 'pry'
|
28
|
+
spec.add_development_dependency 'rake'
|
29
|
+
spec.add_development_dependency 'rspec'
|
30
|
+
spec.add_development_dependency 'rubocop'
|
31
|
+
spec.add_dependency 'hashie'
|
32
|
+
spec.add_dependency 'thor'
|
30
33
|
end
|
data/lib/kaiser_ruby.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'hashie'
|
2
4
|
require 'kaiser_ruby/parser'
|
3
|
-
require 'kaiser_ruby/transformer'
|
4
5
|
require 'kaiser_ruby/refinements'
|
6
|
+
require 'kaiser_ruby/transformer'
|
5
7
|
require 'pry'
|
6
8
|
|
9
|
+
# Transpile Rockstar into Ruby code
|
7
10
|
module KaiserRuby
|
8
11
|
class RockstarSyntaxError < SyntaxError
|
9
12
|
end
|
10
13
|
|
11
14
|
def self.parse(input)
|
12
|
-
# eat comments since we don't care about them
|
13
|
-
input = input.gsub(/\n *\(.*?\) *\n/m, "\n\n")
|
14
|
-
input = input.gsub(/\(.*?\)\s*\n/m, "\n").gsub(/ +/, ' ')
|
15
15
|
parser = KaiserRuby::Parser.new(input)
|
16
16
|
parser.parse
|
17
17
|
end
|
@@ -29,8 +29,6 @@ module KaiserRuby
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
-
private
|
33
|
-
|
34
32
|
def self.with_captured_stdout
|
35
33
|
old_stdout = $stdout
|
36
34
|
$stdout = StringIO.new
|
data/lib/kaiser_ruby/cli.rb
CHANGED
@@ -1,20 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'thor'
|
2
4
|
require_relative 'version'
|
3
5
|
|
4
6
|
module KaiserRuby
|
7
|
+
# command line interface for kaiser-ruby command
|
5
8
|
class CLI < Thor
|
6
9
|
package_name "Kaiser-Ruby v#{KaiserRuby::VERSION}"
|
7
10
|
|
8
|
-
desc
|
9
|
-
|
10
|
-
|
11
|
+
desc 'parse FILE', 'parse a .rock FILE and output the intermediate tree'
|
12
|
+
def parse(filename)
|
13
|
+
file = File.read filename
|
14
|
+
output = KaiserRuby.parse(file)
|
15
|
+
|
16
|
+
puts output
|
17
|
+
say
|
18
|
+
end
|
19
|
+
|
20
|
+
desc 'transpile FILE', 'transpile a .rock FILE and output the resulting Ruby code'
|
21
|
+
option 'show-source'.to_sym, type: :boolean, desc: 'prints out the source file along with the transpiled output'
|
22
|
+
option :save, desc: 'saves the transpiled output in SAVE'
|
11
23
|
def transpile(filename)
|
12
24
|
file = File.read filename
|
13
25
|
output = KaiserRuby.transpile(file)
|
14
26
|
|
15
27
|
if options['show-source'.to_sym]
|
16
28
|
say file
|
17
|
-
say
|
29
|
+
say '-' * 40, :green
|
18
30
|
end
|
19
31
|
|
20
32
|
if options[:save]
|
@@ -35,7 +47,7 @@ module KaiserRuby
|
|
35
47
|
|
36
48
|
using KaiserRuby::Refinements
|
37
49
|
|
38
|
-
desc
|
50
|
+
desc 'execute FILE', 'transpiles and runs a .rock FILE'
|
39
51
|
def execute(filename)
|
40
52
|
file = File.read filename
|
41
53
|
output = KaiserRuby.transpile(file)
|
@@ -43,8 +55,8 @@ module KaiserRuby
|
|
43
55
|
say
|
44
56
|
end
|
45
57
|
|
46
|
-
desc
|
47
|
-
option :debug, type: :boolean, desc:
|
58
|
+
desc 'rock', 'opens an interactive console that accepts and evaluates Rockstar code'
|
59
|
+
option :debug, type: :boolean, desc: 'also shows transpiled code'
|
48
60
|
def rock
|
49
61
|
say "Type 'exit' to exit the console. Otherwise, rock on!"
|
50
62
|
|
data/lib/kaiser_ruby/parser.rb
CHANGED
@@ -1,70 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module KaiserRuby
|
4
|
+
# Parser class goes through the Rockstar code provided and generates
|
5
|
+
# an intermediate tree from which we can output a proper Ruby program
|
2
6
|
class Parser
|
3
7
|
attr_reader :lines, :raw_input, :tree
|
4
8
|
|
5
|
-
POETIC_STRING_KEYWORDS = %w
|
6
|
-
POETIC_NUMBER_KEYWORDS = %w
|
7
|
-
POETIC_NUMBER_CONTRACTIONS = %w
|
8
|
-
POETIC_TYPE_KEYWORDS = %w
|
9
|
-
PRINT_KEYWORDS = %w
|
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
|
17
|
-
INCREMENT_SECOND_KEYWORDS = %w
|
18
|
-
DECREMENT_FIRST_KEYWORDS = %w
|
19
|
-
DECREMENT_SECOND_KEYWORDS = %w
|
20
|
-
ASSIGNMENT_FIRST_KEYWORDS = %w
|
21
|
-
ASSIGNMENT_SECOND_KEYWORDS = %w
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
9
|
+
POETIC_STRING_KEYWORDS = %w[says].freeze
|
10
|
+
POETIC_NUMBER_KEYWORDS = %w[is was were are 's 're].freeze
|
11
|
+
POETIC_NUMBER_CONTRACTIONS = %w['s 're].freeze
|
12
|
+
POETIC_TYPE_KEYWORDS = %w[is].freeze
|
13
|
+
PRINT_KEYWORDS = %w[say whisper shout scream].freeze
|
14
|
+
LISTEN_TO_KEYWORDS = ['listen to'].freeze
|
15
|
+
LISTEN_KEYWORDS = ['listen'].freeze
|
16
|
+
BREAK_KEYWORDS = ['break', 'break it down'].freeze
|
17
|
+
CONTINUE_KEYWORDS = ['continue', 'take it to the top'].freeze
|
18
|
+
RETURN_KEYWORDS = ['give back'].freeze
|
19
|
+
|
20
|
+
INCREMENT_FIRST_KEYWORDS = %w[build].freeze
|
21
|
+
INCREMENT_SECOND_KEYWORDS = %w[up].freeze
|
22
|
+
DECREMENT_FIRST_KEYWORDS = %w[knock].freeze
|
23
|
+
DECREMENT_SECOND_KEYWORDS = %w[down].freeze
|
24
|
+
ASSIGNMENT_FIRST_KEYWORDS = %w[put].freeze
|
25
|
+
ASSIGNMENT_SECOND_KEYWORDS = %w[into].freeze
|
26
|
+
LET_ASSIGNMENT_FIRST_KEYWORDS = %w[let].freeze
|
27
|
+
LET_ASSIGNMENT_SECOND_KEYWORDS = %w[be].freeze
|
28
|
+
|
29
|
+
FUNCTION_KEYWORDS = %w[takes].freeze
|
30
|
+
FUNCTION_SEPARATORS = ['and', ', and', "'n'", '&', ','].freeze
|
31
|
+
FUNCTION_CALL_KEYWORDS = %w[taking].freeze
|
32
|
+
FUNCTION_CALL_SEPARATORS = [', and', "'n'", '&', ','].freeze
|
33
|
+
IF_KEYWORDS = %w[if].freeze
|
34
|
+
UNTIL_KEYWORDS = %w[until].freeze
|
35
|
+
WHILE_KEYWORDS = %w[while].freeze
|
36
|
+
ELSE_KEYWORDS = %w[else].freeze
|
37
|
+
|
38
|
+
NULL_TYPE = %w[null nothing nowhere nobody gone empty].freeze
|
39
|
+
TRUE_TYPE = %w[true yes ok right].freeze
|
40
|
+
FALSE_TYPE = %w[false no lies wrong].freeze
|
41
|
+
MYSTERIOUS_TYPE = %w[mysterious].freeze
|
42
|
+
POETIC_TYPE_LITERALS = MYSTERIOUS_TYPE + NULL_TYPE + TRUE_TYPE + FALSE_TYPE
|
43
|
+
|
44
|
+
COMMON_VARIABLE_KEYWORDS = %w[a an the my your].freeze
|
45
|
+
PRONOUN_KEYWORDS = %w[he him she her it its they them ze hir zie zir xe xem ve ver].freeze
|
46
|
+
|
47
|
+
ADDITION_KEYWORDS = %w[plus with +].freeze
|
48
|
+
SUBTRACTION_KEYWORDS = %w[minus without -].freeze
|
49
|
+
MULTIPLICATION_KEYWORDS = %w[times of *].freeze
|
50
|
+
DIVISION_KEYWORDS = %w[over /].freeze
|
45
51
|
MATH_OP_KEYWORDS = ADDITION_KEYWORDS + SUBTRACTION_KEYWORDS + MULTIPLICATION_KEYWORDS + DIVISION_KEYWORDS
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
52
|
+
MATH_TOKENS = %w[+ / * -].freeze
|
53
|
+
|
54
|
+
EQUALITY_KEYWORDS = %w[is].freeze
|
55
|
+
INEQUALITY_KEYWORDS = %w[isn't isnt ain't aint is\ not].freeze
|
56
|
+
GT_KEYWORDS = ['is higher than', 'is greater than', 'is bigger than', 'is stronger than'].freeze
|
57
|
+
GTE_KEYWORDS = ['is as high as', 'is as great as', 'is as big as', 'is as strong as'].freeze
|
58
|
+
LT_KEYWORDS = ['is lower than', 'is less than', 'is smaller than', 'is weaker than'].freeze
|
59
|
+
LTE_KEYWORDS = ['is as low as', 'is as little as', 'is as small as', 'is as weak as'].freeze
|
53
60
|
COMPARISON_KEYWORDS = EQUALITY_KEYWORDS + INEQUALITY_KEYWORDS + GT_KEYWORDS + GTE_KEYWORDS + LT_KEYWORDS + LTE_KEYWORDS
|
54
61
|
|
55
62
|
FUNCTION_RESTRICTED_KEYWORDS = MATH_OP_KEYWORDS + ['(?<!, )and', 'is', 'or', 'into', 'nor']
|
56
63
|
|
57
|
-
AND_KEYWORDS =
|
58
|
-
OR_KEYWORDS = %w
|
59
|
-
NOR_KEYWORDS = %w
|
60
|
-
NOT_KEYWORDS = ['(?<!is )not']
|
64
|
+
AND_KEYWORDS = ['(?<!, )and'].freeze
|
65
|
+
OR_KEYWORDS = %w[or].freeze
|
66
|
+
NOR_KEYWORDS = %w[nor].freeze
|
67
|
+
NOT_KEYWORDS = ['(?<!is )not'].freeze
|
61
68
|
LOGIC_KEYWORDS = AND_KEYWORDS + OR_KEYWORDS + NOT_KEYWORDS + NOR_KEYWORDS
|
62
69
|
|
63
70
|
RESERVED_KEYWORDS = LOGIC_KEYWORDS + MATH_OP_KEYWORDS + POETIC_TYPE_LITERALS
|
64
71
|
|
65
72
|
def initialize(input)
|
66
73
|
@raw_input = input
|
67
|
-
@lines = input.split
|
74
|
+
@lines = input.gsub(/\(\n.*?\)/m, "\n").split(/\n/) # eat multiline comments
|
68
75
|
end
|
69
76
|
|
70
77
|
def parse
|
@@ -73,7 +80,8 @@ module KaiserRuby
|
|
73
80
|
@tree.extend(Hashie::Extensions::DeepLocate)
|
74
81
|
@function_temp = []
|
75
82
|
@nesting = 0
|
76
|
-
@
|
83
|
+
@nesting_has_else = false
|
84
|
+
@current_scope = [nil]
|
77
85
|
@lnum = 0
|
78
86
|
|
79
87
|
# parse through lines to get the general structure (statements/flow control/functions/etc) out of it
|
@@ -94,14 +102,18 @@ module KaiserRuby
|
|
94
102
|
end
|
95
103
|
|
96
104
|
def parse_line(line)
|
97
|
-
|
105
|
+
# consume comments and extra spaces
|
106
|
+
line = line.gsub(/\(.*?\)/, '').strip
|
98
107
|
|
99
108
|
if line.empty?
|
100
|
-
if @nesting
|
109
|
+
if @nesting.positive?
|
110
|
+
@current_scope.pop unless @current_scope[@nesting].nil?
|
111
|
+
@nesting_has_else = false
|
101
112
|
@nesting -= 1
|
102
|
-
@nesting_start_line = nil
|
103
113
|
end
|
104
114
|
|
115
|
+
@current_scope.pop if @nesting.zero?
|
116
|
+
|
105
117
|
add_to_tree(parse_empty_line)
|
106
118
|
else
|
107
119
|
obj = parse_line_content(line)
|
@@ -111,14 +123,18 @@ module KaiserRuby
|
|
111
123
|
end
|
112
124
|
|
113
125
|
def update_nesting(object)
|
114
|
-
if %i
|
126
|
+
if %i[if function until while].include? object.keys.first
|
115
127
|
@nesting += 1
|
116
|
-
@
|
128
|
+
@nesting_has_else = false
|
129
|
+
end
|
130
|
+
|
131
|
+
if object.keys.first == :function
|
132
|
+
@current_scope.push object.deep_locate(:function_name).first.dig(:function_name)
|
117
133
|
end
|
118
134
|
end
|
119
135
|
|
120
136
|
def parse_line_content(line)
|
121
|
-
words = line.split
|
137
|
+
words = line.split(/\s/)
|
122
138
|
|
123
139
|
if matches_first?(words, IF_KEYWORDS)
|
124
140
|
return parse_if(line)
|
@@ -130,6 +146,8 @@ module KaiserRuby
|
|
130
146
|
return parse_until(line)
|
131
147
|
elsif matches_separate?(words, ASSIGNMENT_FIRST_KEYWORDS, ASSIGNMENT_SECOND_KEYWORDS)
|
132
148
|
return parse_assignment(line)
|
149
|
+
elsif matches_separate?(words, LET_ASSIGNMENT_FIRST_KEYWORDS, LET_ASSIGNMENT_SECOND_KEYWORDS)
|
150
|
+
return parse_let_assignment(line)
|
133
151
|
elsif matches_several_first?(line, RETURN_KEYWORDS)
|
134
152
|
return parse_return(line)
|
135
153
|
elsif matches_first?(words, PRINT_KEYWORDS)
|
@@ -146,21 +164,13 @@ module KaiserRuby
|
|
146
164
|
return(parse_continue) if matches_several_first?(line, CONTINUE_KEYWORDS)
|
147
165
|
return(parse_function_call(line)) if matches_any?(words, FUNCTION_CALL_KEYWORDS)
|
148
166
|
|
149
|
-
if matches_separate?(words, INCREMENT_FIRST_KEYWORDS, INCREMENT_SECOND_KEYWORDS)
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
if matches_separate?(words, DECREMENT_FIRST_KEYWORDS, DECREMENT_SECOND_KEYWORDS)
|
154
|
-
return parse_decrement(line)
|
155
|
-
end
|
156
|
-
|
157
|
-
if matches_any?(words, FUNCTION_KEYWORDS)
|
158
|
-
return(parse_function(line))
|
159
|
-
end
|
167
|
+
return parse_increment(line) if matches_separate?(words, INCREMENT_FIRST_KEYWORDS, INCREMENT_SECOND_KEYWORDS)
|
168
|
+
return parse_decrement(line) if matches_separate?(words, DECREMENT_FIRST_KEYWORDS, DECREMENT_SECOND_KEYWORDS)
|
169
|
+
return(parse_function(line)) if matches_any?(words, FUNCTION_KEYWORDS)
|
160
170
|
end
|
161
171
|
end
|
162
172
|
|
163
|
-
raise KaiserRuby::RockstarSyntaxError, "couldn't parse line: #{line}:#{@lnum+1}"
|
173
|
+
raise KaiserRuby::RockstarSyntaxError, "couldn't parse line: #{line}:#{@lnum + 1}"
|
164
174
|
end
|
165
175
|
|
166
176
|
# statements
|
@@ -176,6 +186,7 @@ module KaiserRuby
|
|
176
186
|
words = line.split prepared_regexp(LISTEN_TO_KEYWORDS)
|
177
187
|
arg = parse_variables(words.last.strip)
|
178
188
|
arg[:type] = :assignment
|
189
|
+
|
179
190
|
{ listen_to: arg }
|
180
191
|
end
|
181
192
|
|
@@ -187,6 +198,7 @@ module KaiserRuby
|
|
187
198
|
words = line.split prepared_regexp(RETURN_KEYWORDS)
|
188
199
|
arg = consume_function_calls(words.last.strip)
|
189
200
|
argument = parse_argument(arg)
|
201
|
+
|
190
202
|
{ return: argument }
|
191
203
|
end
|
192
204
|
|
@@ -194,8 +206,8 @@ module KaiserRuby
|
|
194
206
|
match_rxp = prepared_capture(INCREMENT_FIRST_KEYWORDS, INCREMENT_SECOND_KEYWORDS)
|
195
207
|
var = line.match(match_rxp).captures.first.strip
|
196
208
|
capture = parse_variables(var)
|
197
|
-
|
198
209
|
capture[:amount] = line.split(var).last.scan(/\bup\b/i).count
|
210
|
+
|
199
211
|
{ increment: capture }
|
200
212
|
end
|
201
213
|
|
@@ -203,12 +215,20 @@ module KaiserRuby
|
|
203
215
|
match_rxp = prepared_capture(DECREMENT_FIRST_KEYWORDS, DECREMENT_SECOND_KEYWORDS)
|
204
216
|
var = line.match(match_rxp).captures.first.strip
|
205
217
|
capture = parse_variables(var)
|
206
|
-
|
207
218
|
capture[:amount] = line.split(var).last.scan(/\bdown\b/i).count
|
219
|
+
|
208
220
|
{ decrement: capture }
|
209
221
|
end
|
210
222
|
|
211
223
|
def parse_else
|
224
|
+
if @nesting_has_else
|
225
|
+
@nesting -= 1
|
226
|
+
@nesting_has_else = false
|
227
|
+
add_to_tree parse_empty_line
|
228
|
+
else
|
229
|
+
@nesting_has_else = true
|
230
|
+
end
|
231
|
+
|
212
232
|
{ else: nil }
|
213
233
|
end
|
214
234
|
|
@@ -247,15 +267,14 @@ module KaiserRuby
|
|
247
267
|
end
|
248
268
|
|
249
269
|
def parse_function_call(line)
|
250
|
-
words = line.split
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
end
|
270
|
+
words = line.split(/\s/)
|
271
|
+
return false unless matches_any?(words, FUNCTION_CALL_KEYWORDS)
|
272
|
+
|
273
|
+
words = line.split prepared_regexp(FUNCTION_CALL_KEYWORDS)
|
274
|
+
left = parse_function_name(words.first.strip)
|
275
|
+
right = parse_function_call_arguments(words.last.strip)
|
276
|
+
|
277
|
+
{ function_call: { left: left, right: right } }
|
259
278
|
end
|
260
279
|
|
261
280
|
def parse_poetic_string(line)
|
@@ -263,6 +282,7 @@ module KaiserRuby
|
|
263
282
|
left = parse_variables(words.first.strip)
|
264
283
|
right = { string: "\"#{words.last.strip}\"" }
|
265
284
|
left[:type] = :assignment
|
285
|
+
|
266
286
|
{ poetic_string: { left: left, right: right } }
|
267
287
|
end
|
268
288
|
|
@@ -271,23 +291,28 @@ module KaiserRuby
|
|
271
291
|
left = parse_variables(words.first.strip)
|
272
292
|
right = parse_type_value(words.last.strip)
|
273
293
|
left[:type] = :assignment
|
294
|
+
|
274
295
|
{ poetic_type: { left: left, right: right } }
|
275
296
|
end
|
276
297
|
|
277
298
|
def parse_type_value(string)
|
278
|
-
words = string.split
|
299
|
+
words = string.split(/\s/)
|
279
300
|
|
280
|
-
if matches_first?(words,
|
281
|
-
raise KaiserRuby::RockstarSyntaxError, "extra words are not allowed after literal type keyword: #{string}:#{@lnum+1}" if words.count > 1
|
282
|
-
|
301
|
+
if matches_first?(words, MYSTERIOUS_TYPE)
|
302
|
+
raise KaiserRuby::RockstarSyntaxError, "extra words are not allowed after literal type keyword: #{string}:#{@lnum + 1}" if words.count > 1
|
303
|
+
|
304
|
+
{ type: 'mysterious' }
|
283
305
|
elsif matches_first?(words, NULL_TYPE)
|
284
|
-
raise KaiserRuby::RockstarSyntaxError, "extra words are not allowed after literal type keyword: #{string}:#{@lnum+1}" if words.count > 1
|
306
|
+
raise KaiserRuby::RockstarSyntaxError, "extra words are not allowed after literal type keyword: #{string}:#{@lnum + 1}" if words.count > 1
|
307
|
+
|
285
308
|
{ type: 'null' }
|
286
309
|
elsif matches_first?(words, TRUE_TYPE)
|
287
|
-
raise KaiserRuby::RockstarSyntaxError, "extra words are not allowed after literal type keyword: #{string}:#{@lnum+1}" if words.count > 1
|
310
|
+
raise KaiserRuby::RockstarSyntaxError, "extra words are not allowed after literal type keyword: #{string}:#{@lnum + 1}" if words.count > 1
|
311
|
+
|
288
312
|
{ type: 'true' }
|
289
313
|
elsif matches_first?(words, FALSE_TYPE)
|
290
|
-
raise KaiserRuby::RockstarSyntaxError, "extra words are not allowed after literal type keyword: #{string}:#{@lnum+1}" if words.count > 1
|
314
|
+
raise KaiserRuby::RockstarSyntaxError, "extra words are not allowed after literal type keyword: #{string}:#{@lnum + 1}" if words.count > 1
|
315
|
+
|
291
316
|
{ type: 'false' }
|
292
317
|
elsif string.strip.start_with?('"') && string.strip.end_with?('"')
|
293
318
|
parse_literal_string(string)
|
@@ -297,11 +322,11 @@ module KaiserRuby
|
|
297
322
|
end
|
298
323
|
|
299
324
|
def parse_type_literal(string)
|
300
|
-
words = string.split
|
301
|
-
raise SyntaxError, "too many words in poetic type literal: #{string}:#{@lnum+1}" if words.size > 1
|
325
|
+
words = string.split(/\s/)
|
326
|
+
raise SyntaxError, "too many words in poetic type literal: #{string}:#{@lnum + 1}" if words.size > 1
|
302
327
|
|
303
|
-
if matches_first?(words,
|
304
|
-
{ type: '
|
328
|
+
if matches_first?(words, MYSTERIOUS_TYPE)
|
329
|
+
{ type: 'mysterious' }
|
305
330
|
elsif matches_first?(words, NULL_TYPE)
|
306
331
|
{ type: 'null' }
|
307
332
|
elsif matches_first?(words, TRUE_TYPE)
|
@@ -309,7 +334,7 @@ module KaiserRuby
|
|
309
334
|
elsif matches_first?(words, FALSE_TYPE)
|
310
335
|
{ type: 'false' }
|
311
336
|
else
|
312
|
-
raise SyntaxError, "unknown poetic type literal: #{string}:#{@lnum+1}"
|
337
|
+
raise SyntaxError, "unknown poetic type literal: #{string}:#{@lnum + 1}"
|
313
338
|
end
|
314
339
|
end
|
315
340
|
|
@@ -318,14 +343,33 @@ module KaiserRuby
|
|
318
343
|
right = parse_argument(line.match(match_rxp).captures.first.strip)
|
319
344
|
left = parse_variables(line.match(match_rxp).captures.last.strip)
|
320
345
|
left[:type] = :assignment
|
346
|
+
|
347
|
+
{ assignment: { left: left, right: right } }
|
348
|
+
end
|
349
|
+
|
350
|
+
def parse_let_assignment(line)
|
351
|
+
match_rxp = prepared_capture(LET_ASSIGNMENT_FIRST_KEYWORDS, LET_ASSIGNMENT_SECOND_KEYWORDS)
|
352
|
+
right = parse_argument(line.match(match_rxp).captures.last.strip)
|
353
|
+
left = parse_variables(line.match(match_rxp).captures.first.strip)
|
354
|
+
left[:type] = :assignment
|
355
|
+
|
356
|
+
# if the right is an expression and its variable name is empty
|
357
|
+
# then it's a compound assignment which we translate back to an explicit one
|
358
|
+
right.extend(Hashie::Extensions::DeepLocate)
|
359
|
+
unless right.deep_locate(:variable_name).empty?
|
360
|
+
if right.deep_locate(:variable_name).first[:variable_name].empty?
|
361
|
+
right.deep_locate(:variable_name).first[:variable_name] = left[:variable_name]
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
321
365
|
{ assignment: { left: left, right: right } }
|
322
366
|
end
|
323
367
|
|
324
368
|
def parse_if(line)
|
325
369
|
words = line.split prepared_regexp(IF_KEYWORDS)
|
326
|
-
|
327
370
|
arg = consume_function_calls(words.last.strip)
|
328
371
|
argument = parse_argument(arg)
|
372
|
+
|
329
373
|
{ if: { argument: argument } }
|
330
374
|
end
|
331
375
|
|
@@ -333,6 +377,7 @@ module KaiserRuby
|
|
333
377
|
words = line.split prepared_regexp(UNTIL_KEYWORDS)
|
334
378
|
arg = consume_function_calls(words.last.strip)
|
335
379
|
argument = parse_argument(arg)
|
380
|
+
|
336
381
|
{ until: { argument: argument } }
|
337
382
|
end
|
338
383
|
|
@@ -340,6 +385,7 @@ module KaiserRuby
|
|
340
385
|
words = line.split prepared_regexp(WHILE_KEYWORDS)
|
341
386
|
arg = consume_function_calls(words.last.strip)
|
342
387
|
argument = parse_argument(arg)
|
388
|
+
|
343
389
|
{ while: { argument: argument } }
|
344
390
|
end
|
345
391
|
|
@@ -347,13 +393,14 @@ module KaiserRuby
|
|
347
393
|
words = line.split prepared_regexp(FUNCTION_KEYWORDS)
|
348
394
|
funcname = parse_function_name(words.first.strip)
|
349
395
|
argument = parse_function_definition_arguments(words.last.strip)
|
396
|
+
|
350
397
|
{ function: { name: funcname, argument: argument } }
|
351
398
|
end
|
352
399
|
|
353
400
|
def consume_function_calls(string)
|
354
401
|
if string =~ prepared_regexp(FUNCTION_CALL_KEYWORDS)
|
355
402
|
words = string.split prepared_regexp(FUNCTION_RESTRICTED_KEYWORDS)
|
356
|
-
found_string = words.select { |w| w =~
|
403
|
+
found_string = words.select { |w| w =~ /\btaking\b/ }.first
|
357
404
|
@function_temp << found_string
|
358
405
|
string = string.gsub(found_string, " func_#{@function_temp.count - 1} ")
|
359
406
|
end
|
@@ -362,11 +409,9 @@ module KaiserRuby
|
|
362
409
|
end
|
363
410
|
|
364
411
|
def pass_function_calls(string)
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
return false
|
369
|
-
end
|
412
|
+
return false unless string.strip =~ /func_\d+\Z/
|
413
|
+
|
414
|
+
{ passed_function_call: string }
|
370
415
|
end
|
371
416
|
|
372
417
|
def parse_argument(string)
|
@@ -411,11 +456,9 @@ module KaiserRuby
|
|
411
456
|
|
412
457
|
def parse_poetic_number_value(string)
|
413
458
|
num = parse_literal_number(string)
|
414
|
-
if num
|
415
|
-
|
416
|
-
|
417
|
-
return { number_literal: string.strip }
|
418
|
-
end
|
459
|
+
return num if num
|
460
|
+
|
461
|
+
{ number_literal: string.strip }
|
419
462
|
end
|
420
463
|
|
421
464
|
def parse_logic_operation(string)
|
@@ -430,7 +473,7 @@ module KaiserRuby
|
|
430
473
|
return parse_nor(string)
|
431
474
|
end
|
432
475
|
|
433
|
-
|
476
|
+
false
|
434
477
|
end
|
435
478
|
|
436
479
|
def parse_and(string)
|
@@ -461,6 +504,7 @@ module KaiserRuby
|
|
461
504
|
|
462
505
|
def parse_not(string)
|
463
506
|
return false if string !~ /(?<!is )\bnot\b/i
|
507
|
+
|
464
508
|
words = string.split prepared_regexp(NOT_KEYWORDS)
|
465
509
|
argument = parse_argument(words.last.strip)
|
466
510
|
|
@@ -469,7 +513,6 @@ module KaiserRuby
|
|
469
513
|
|
470
514
|
def parse_comparison(string)
|
471
515
|
return false if string.strip.start_with?('"') && string.strip.strip.end_with?('"') && string.count('"') == 2
|
472
|
-
words = string.split(/\s/)
|
473
516
|
|
474
517
|
if string =~ prepared_regexp(GT_KEYWORDS)
|
475
518
|
return parse_gt(string)
|
@@ -485,7 +528,7 @@ module KaiserRuby
|
|
485
528
|
return parse_equality(string)
|
486
529
|
end
|
487
530
|
|
488
|
-
|
531
|
+
false
|
489
532
|
end
|
490
533
|
|
491
534
|
def parse_equality(string)
|
@@ -537,7 +580,7 @@ module KaiserRuby
|
|
537
580
|
end
|
538
581
|
|
539
582
|
def parse_variables(string)
|
540
|
-
words = string.split
|
583
|
+
words = string.split(/\s/)
|
541
584
|
words = words.map { |e| e.chars.select { |c| c =~ /[[:alnum:]]|\./ }.join }
|
542
585
|
string = words.join(' ')
|
543
586
|
|
@@ -547,9 +590,11 @@ module KaiserRuby
|
|
547
590
|
return parse_common_variable(string)
|
548
591
|
elsif matches_all?(words, /\A[[:upper:]]/) && string !~ prepared_regexp(RESERVED_KEYWORDS)
|
549
592
|
return parse_proper_variable(string)
|
593
|
+
elsif words.count == 1 && string !~ prepared_regexp(RESERVED_KEYWORDS)
|
594
|
+
return prase_simple_variable(string)
|
550
595
|
end
|
551
596
|
|
552
|
-
|
597
|
+
false
|
553
598
|
end
|
554
599
|
|
555
600
|
def parse_function_name(string)
|
@@ -561,14 +606,8 @@ module KaiserRuby
|
|
561
606
|
def parse_common_variable(string)
|
562
607
|
words = string.split(/\s/)
|
563
608
|
|
564
|
-
copied = words.dup
|
565
|
-
copied.shift
|
566
|
-
copied.each do |w|
|
567
|
-
raise SyntaxError, "invalid common variable name: #{string}:#{@lnum+1}" if w =~ /[[:upper:]]/
|
568
|
-
end
|
569
|
-
|
570
609
|
words = words.map { |e| e.chars.select { |c| c =~ /[[:alpha:]]/ }.join }
|
571
|
-
{ variable_name: words.map
|
610
|
+
{ variable_name: words.map(&:downcase).join('_') }
|
572
611
|
end
|
573
612
|
|
574
613
|
def parse_proper_variable(string)
|
@@ -577,19 +616,24 @@ module KaiserRuby
|
|
577
616
|
copied = words.dup
|
578
617
|
copied.shift
|
579
618
|
copied.each do |w|
|
580
|
-
raise SyntaxError, "invalid proper variable name: #{string}:#{@lnum+1}" unless w =~ /\A[[:upper:]]/
|
619
|
+
raise SyntaxError, "invalid proper variable name: #{string}:#{@lnum + 1}" unless w =~ /\A[[:upper:]]/
|
581
620
|
end
|
582
621
|
|
583
622
|
words = words.map { |e| e.chars.select { |c| c =~ /[[:alpha:]]/ }.join }
|
584
|
-
{ variable_name: words.map
|
623
|
+
{ variable_name: words.map(&:downcase).join('_') }
|
585
624
|
end
|
586
625
|
|
587
626
|
def parse_pronoun
|
588
627
|
{ pronoun: nil }
|
589
628
|
end
|
590
629
|
|
630
|
+
def prase_simple_variable(string)
|
631
|
+
{ variable_name: string }
|
632
|
+
end
|
633
|
+
|
591
634
|
def parse_math_operations(string)
|
592
635
|
return false if string.strip.start_with?('"') && string.strip.end_with?('"') && string.count('"') == 2
|
636
|
+
|
593
637
|
words = string.split(/\s/)
|
594
638
|
|
595
639
|
if matches_any?(words, MULTIPLICATION_KEYWORDS)
|
@@ -602,7 +646,7 @@ module KaiserRuby
|
|
602
646
|
return parse_subtraction(string)
|
603
647
|
end
|
604
648
|
|
605
|
-
|
649
|
+
false
|
606
650
|
end
|
607
651
|
|
608
652
|
def parse_addition(string)
|
@@ -638,42 +682,37 @@ module KaiserRuby
|
|
638
682
|
end
|
639
683
|
|
640
684
|
def parse_literal_string(string)
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
return false
|
645
|
-
end
|
685
|
+
return false unless string.strip.start_with?('"') && string.strip.end_with?('"') && string.count('"') == 2
|
686
|
+
|
687
|
+
{ string: string }
|
646
688
|
end
|
647
689
|
|
648
690
|
def parse_literal_number(string)
|
649
691
|
num = Float(string) rescue string
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
return false
|
654
|
-
end
|
692
|
+
return false unless num.is_a?(Float)
|
693
|
+
|
694
|
+
{ number: num }
|
655
695
|
end
|
656
696
|
|
657
|
-
#private
|
697
|
+
# private
|
658
698
|
|
659
699
|
def add_to_tree(object)
|
660
700
|
object.extend(Hashie::Extensions::DeepLocate)
|
661
701
|
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
end
|
702
|
+
object[:current_scope] = @current_scope.last
|
703
|
+
object[:nesting] = @nesting
|
704
|
+
|
666
705
|
@tree << object
|
667
706
|
end
|
668
707
|
|
669
708
|
def prepared_regexp(array)
|
670
|
-
rxp = array.map { |a|
|
709
|
+
rxp = array.map { |a| tokenize_word(a) }.join('|')
|
671
710
|
Regexp.new(rxp, Regexp::IGNORECASE)
|
672
711
|
end
|
673
712
|
|
674
713
|
def prepared_capture(farr, sarr)
|
675
|
-
frxp = farr.map { |a|
|
676
|
-
srxp = sarr.map { |a|
|
714
|
+
frxp = farr.map { |a| tokenize_word(a) }.join('|')
|
715
|
+
srxp = sarr.map { |a| tokenize_word(a) }.join('|')
|
677
716
|
Regexp.new(frxp + '(.*?)' + srxp + '(.*)', Regexp::IGNORECASE)
|
678
717
|
end
|
679
718
|
|
@@ -691,22 +730,28 @@ module KaiserRuby
|
|
691
730
|
first_idx = words.index { |w| w =~ prepared_regexp(first_rxp) }
|
692
731
|
second_idx = words.index { |w| w =~ prepared_regexp(second_rxp) }
|
693
732
|
|
694
|
-
second_idx
|
733
|
+
!second_idx.nil? && !first_idx.nil? && second_idx.to_i - first_idx.to_i == 1
|
695
734
|
end
|
696
735
|
|
697
736
|
def matches_first?(words, rxp)
|
698
|
-
words.index { |w| w =~ prepared_regexp(rxp) }
|
737
|
+
words.index { |w| w =~ prepared_regexp(rxp) }&.zero?
|
699
738
|
end
|
700
739
|
|
701
740
|
def matches_several_first?(line, rxp)
|
702
|
-
(line =~ prepared_regexp(rxp))
|
741
|
+
(line =~ prepared_regexp(rxp))&.zero?
|
703
742
|
end
|
704
743
|
|
705
744
|
def matches_separate?(words, first_rxp, second_rxp)
|
706
745
|
first_idx = words.index { |w| w =~ prepared_regexp(first_rxp) }
|
707
746
|
second_idx = words.index { |w| w =~ prepared_regexp(second_rxp) }
|
708
747
|
|
709
|
-
second_idx
|
748
|
+
!second_idx.nil? && !first_idx.nil? && second_idx.to_i > first_idx.to_i
|
749
|
+
end
|
750
|
+
|
751
|
+
def tokenize_word(word)
|
752
|
+
return '\B' + Regexp.escape(word) + '\B' if MATH_TOKENS.include?(word) # apparently ' + ' is not a word so word boundaries don't work
|
753
|
+
|
754
|
+
'\b' + word + '\b'
|
710
755
|
end
|
711
756
|
end
|
712
|
-
end
|
757
|
+
end
|