kaiser-ruby 0.7.1 → 0.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d86f7b4d4eeb3c6c63aee4afd51fafb2e1676fe6d743a8df48a10a77b7e761e8
4
- data.tar.gz: 1090782c7e4bb4684ffbc3162f4d5ae6529c0e4cfd3d29d227938f1a930d8519
3
+ metadata.gz: 040ea9398c1f1cb0499f3a5d21038b9ee52434a9370145ec8efd5e1217264431
4
+ data.tar.gz: b7eb871f93fb4f68570685d9376c75698ca23e16b12752e6157cd046acacc6ff
5
5
  SHA512:
6
- metadata.gz: 9cf3a4423bc2d0da323ac5dcf575cdacf373eac7d3aaccee48c23781a85589342941a225a909b53f7e342baa611e0fe8ad8997c37386f44ffc306bff96160839
7
- data.tar.gz: 100adddde94add35ad05b3a2d58d8a99993bc88b55f8d71a452f0356468d8f4a1be669a6bb3531d0ca596d8230b73098ee447b28b65d86bfefe40c245fb26d22
6
+ metadata.gz: bdd9abbab639f244d3a97799a25dc8c0ef5b5dfc582011a6b1d3cc02df804c7ca2d3ca8746214ba5b7df101af8967021e9fe20a010e86c8121bd122b68ea98f0
7
+ data.tar.gz: 3c04422053c184d14c59740bceb57212b4f8b1d3483c916ee8c131128af2315cf30a2040dbf0c431dd31c27100372b858580b3180739975f593a639e6f336945
@@ -0,0 +1,11 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.3
3
+
4
+ Metrics:
5
+ Enabled: false
6
+
7
+ Style/RescueModifier:
8
+ Enabled: false
9
+
10
+ Lint/InterpolationCheck:
11
+ Enabled: false
@@ -1 +1 @@
1
- 2.5.1
1
+ 2.6.2
@@ -1,7 +1,8 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- - 2.3.7
5
- - 2.4.4
6
- - 2.5.1
7
- before_install: gem install bundler -v 1.16.1
4
+ - 2.3.8
5
+ - 2.4.5
6
+ - 2.5.5
7
+ - 2.6.2
8
+ before_install: gem install bundler
@@ -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 Implementation
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 stuff
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 Implementation:
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 stuff:
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 Implementation:
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 stuff:
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
- source "https://rubygems.org"
1
+ # frozen_string_literal: true
2
2
 
3
- git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
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
@@ -1,45 +1,60 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- kaiser-ruby (0.7.1)
4
+ kaiser-ruby (0.8)
5
5
  hashie
6
- thor (~> 0.20)
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
- method_source (0.9.0)
15
- pry (0.11.3)
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
- rake (10.5.0)
19
- rspec (3.7.0)
20
- rspec-core (~> 3.7.0)
21
- rspec-expectations (~> 3.7.0)
22
- rspec-mocks (~> 3.7.0)
23
- rspec-core (3.7.1)
24
- rspec-support (~> 3.7.0)
25
- rspec-expectations (3.7.0)
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.7.0)
28
- rspec-mocks (3.7.0)
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.7.0)
31
- rspec-support (3.7.1)
32
- thor (0.20.0)
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.16)
55
+ bundler (~> 1.17.0)
39
56
  kaiser-ruby!
40
- pry (~> 0.11)
41
- rake (~> 10.0)
42
- rspec (~> 3.0)
43
-
44
- BUNDLED WITH
45
- 1.16.2
57
+ pry
58
+ rake
59
+ rspec
60
+ rubocop
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
3
5
 
4
6
  RSpec::Core::RakeTask.new(:spec)
5
7
 
6
- task :default => :spec
8
+ task default: :spec
@@ -1,6 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'kaiser_ruby'
6
+ require 'pry'
7
+
8
+ using KaiserRuby::Refinements
2
9
 
3
- require "bundler/setup"
4
- require "kaiser_ruby"
5
- require "pry"
6
10
  Pry.start
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
- require "rubygems"
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
- KaiserRuby::CLI.start(ARGV)
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)
@@ -1,30 +1,33 @@
1
- lib = File.expand_path("../lib", __FILE__)
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 "kaiser_ruby/version"
5
+ require 'kaiser_ruby/version'
4
6
 
5
7
  Gem::Specification.new do |spec|
6
- spec.name = "kaiser-ruby"
8
+ spec.name = 'kaiser-ruby'
7
9
  spec.version = KaiserRuby::VERSION
8
- spec.authors = ["Marcin Ruszkiewicz"]
9
- spec.email = ["marcin.ruszkiewicz@polcode.net"]
10
+ spec.authors = ['Marcin Ruszkiewicz']
11
+ spec.email = ['marcin.ruszkiewicz@polcode.net']
10
12
 
11
- spec.summary = %q{Transpiler of Rockstar language to Ruby}
12
- spec.homepage = "https://github.com/marcinruszkiewicz/kaiser-ruby"
13
- spec.license = "MIT"
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 = "exe"
20
+ spec.bindir = 'exe'
19
21
  spec.executables = ['kaiser-ruby']
20
- spec.require_paths = ["lib"]
22
+ spec.require_paths = ['lib']
21
23
 
22
24
  spec.required_ruby_version = '>= 2.3'
23
25
 
24
- spec.add_development_dependency "bundler", "~> 1.16"
25
- spec.add_development_dependency "rake", "~> 10.0"
26
- spec.add_development_dependency "rspec", "~> 3.0"
27
- spec.add_development_dependency "pry", "~> 0.11"
28
- spec.add_dependency "thor", "~> 0.20"
29
- spec.add_dependency "hashie"
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
@@ -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
@@ -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 "transpile FILE", "transpile a .rock FILE and output the result"
9
- option 'show-source'.to_sym, type: :boolean, desc: "prints out the source file along with the transpiled output"
10
- option :save, desc: "saves the transpiled output in SAVE"
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 "-" * 40, :green
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 "execute FILE", "transpiles and runs a .rock FILE"
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 "rock", "opens an interactive console that accepts and evaluates Rockstar code"
47
- option :debug, type: :boolean, desc: "also shows transpiled code"
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
 
@@ -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(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)
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
- 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']
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 = %w(and)
58
- OR_KEYWORDS = %w(or)
59
- NOR_KEYWORDS = %w(nor)
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 /\n/
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
- @nesting_start_line = 0
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
- line = line.strip
105
+ # consume comments and extra spaces
106
+ line = line.gsub(/\(.*?\)/, '').strip
98
107
 
99
108
  if line.empty?
100
- if @nesting > 0
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(if function until while).include? object.keys.first
126
+ if %i[if function until while].include? object.keys.first
115
127
  @nesting += 1
116
- @nesting_start_line = @lnum
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 /\s/
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
- return parse_increment(line)
151
- end
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 /\s/
251
- if matches_any?(words, FUNCTION_CALL_KEYWORDS)
252
- words = line.split prepared_regexp(FUNCTION_CALL_KEYWORDS)
253
- left = parse_function_name(words.first.strip)
254
- right = parse_function_call_arguments(words.last.strip)
255
- { function_call: { left: left, right: right } }
256
- else
257
- return false
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 /\s/
299
+ words = string.split(/\s/)
279
300
 
280
- if matches_first?(words, NIL_TYPE)
281
- raise KaiserRuby::RockstarSyntaxError, "extra words are not allowed after literal type keyword: #{string}:#{@lnum+1}" if words.count > 1
282
- { type: 'nil' }
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 /\s/
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, NIL_TYPE)
304
- { type: 'nil' }
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 =~ (/\btaking\b/) }.first
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
- if string.strip =~ /func_\d+\Z/
366
- { passed_function_call: string }
367
- else
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
- return num
416
- else
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
- return false
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
- return false
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 /\s/
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
- return false
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 { |w| w.downcase }.join('_') }
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 { |w| w.downcase }.join('_') }
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
- return false
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
- if string.strip.start_with?('"') && string.strip.end_with?('"') && string.count('"') == 2
642
- { string: string }
643
- else
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
- if num.is_a?(Float)
651
- { number: num }
652
- else
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
- if @nesting > 0
663
- object[:nesting_start_line] = @nesting_start_line
664
- object[:nesting] = @nesting
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| '\b' + a + '\b' }.join('|')
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| '\b' + a + '\b' }.join('|')
676
- srxp = sarr.map { |a| '\b' + a + '\b' }.join('|')
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 != nil && first_idx != nil && second_idx.to_i - first_idx.to_i == 1
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) } == 0
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)) == 0
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 != nil && first_idx != nil && second_idx.to_i > first_idx.to_i
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