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