keisan 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +49 -1
  3. data/keisan.gemspec +1 -0
  4. data/lib/keisan.rb +30 -0
  5. data/lib/keisan/ast/assignment.rb +44 -17
  6. data/lib/keisan/ast/block.rb +60 -0
  7. data/lib/keisan/ast/boolean.rb +5 -5
  8. data/lib/keisan/ast/builder.rb +10 -207
  9. data/lib/keisan/ast/cell.rb +60 -0
  10. data/lib/keisan/ast/constant_literal.rb +9 -0
  11. data/lib/keisan/ast/exponent.rb +6 -6
  12. data/lib/keisan/ast/function.rb +12 -8
  13. data/lib/keisan/ast/indexing.rb +25 -15
  14. data/lib/keisan/ast/line_builder.rb +230 -0
  15. data/lib/keisan/ast/list.rb +28 -1
  16. data/lib/keisan/ast/literal.rb +0 -8
  17. data/lib/keisan/ast/logical_and.rb +1 -1
  18. data/lib/keisan/ast/logical_or.rb +1 -1
  19. data/lib/keisan/ast/multi_line.rb +28 -0
  20. data/lib/keisan/ast/node.rb +32 -24
  21. data/lib/keisan/ast/number.rb +31 -31
  22. data/lib/keisan/ast/operator.rb +12 -4
  23. data/lib/keisan/ast/parent.rb +4 -4
  24. data/lib/keisan/ast/plus.rb +10 -10
  25. data/lib/keisan/ast/string.rb +3 -3
  26. data/lib/keisan/ast/times.rb +8 -8
  27. data/lib/keisan/ast/unary_identity.rb +1 -1
  28. data/lib/keisan/ast/unary_inverse.rb +7 -7
  29. data/lib/keisan/ast/unary_minus.rb +5 -5
  30. data/lib/keisan/ast/unary_operator.rb +2 -2
  31. data/lib/keisan/ast/unary_plus.rb +2 -2
  32. data/lib/keisan/ast/variable.rb +26 -10
  33. data/lib/keisan/context.rb +5 -5
  34. data/lib/keisan/evaluator.rb +15 -8
  35. data/lib/keisan/function.rb +24 -6
  36. data/lib/keisan/functions/cbrt.rb +1 -1
  37. data/lib/keisan/functions/cos.rb +1 -1
  38. data/lib/keisan/functions/cosh.rb +1 -1
  39. data/lib/keisan/functions/cot.rb +1 -1
  40. data/lib/keisan/functions/coth.rb +1 -1
  41. data/lib/keisan/functions/csc.rb +1 -1
  42. data/lib/keisan/functions/csch.rb +1 -1
  43. data/lib/keisan/functions/default_registry.rb +53 -74
  44. data/lib/keisan/functions/diff.rb +18 -14
  45. data/lib/keisan/functions/erf.rb +15 -0
  46. data/lib/keisan/functions/exp.rb +1 -1
  47. data/lib/keisan/functions/expression_function.rb +15 -21
  48. data/lib/keisan/functions/filter.rb +13 -15
  49. data/lib/keisan/functions/if.rb +14 -20
  50. data/lib/keisan/functions/let.rb +36 -0
  51. data/lib/keisan/functions/map.rb +11 -13
  52. data/lib/keisan/functions/math_function.rb +2 -2
  53. data/lib/keisan/functions/proc_function.rb +10 -6
  54. data/lib/keisan/functions/rand.rb +2 -1
  55. data/lib/keisan/functions/range.rb +74 -0
  56. data/lib/keisan/functions/reduce.rb +12 -14
  57. data/lib/keisan/functions/registry.rb +7 -7
  58. data/lib/keisan/functions/replace.rb +8 -8
  59. data/lib/keisan/functions/sample.rb +2 -1
  60. data/lib/keisan/functions/sec.rb +1 -1
  61. data/lib/keisan/functions/sech.rb +1 -1
  62. data/lib/keisan/functions/sin.rb +1 -1
  63. data/lib/keisan/functions/sinh.rb +1 -1
  64. data/lib/keisan/functions/sqrt.rb +1 -1
  65. data/lib/keisan/functions/tan.rb +1 -1
  66. data/lib/keisan/functions/tanh.rb +1 -1
  67. data/lib/keisan/functions/while.rb +46 -0
  68. data/lib/keisan/parser.rb +121 -79
  69. data/lib/keisan/parsing/assignment.rb +1 -1
  70. data/lib/keisan/parsing/bitwise_and.rb +1 -1
  71. data/lib/keisan/parsing/bitwise_not.rb +1 -1
  72. data/lib/keisan/parsing/bitwise_not_not.rb +1 -1
  73. data/lib/keisan/parsing/bitwise_or.rb +1 -1
  74. data/lib/keisan/parsing/bitwise_xor.rb +1 -1
  75. data/lib/keisan/parsing/curly_group.rb +6 -0
  76. data/lib/keisan/parsing/divide.rb +1 -1
  77. data/lib/keisan/parsing/exponent.rb +1 -1
  78. data/lib/keisan/parsing/function.rb +1 -1
  79. data/lib/keisan/parsing/group.rb +1 -1
  80. data/lib/keisan/parsing/indexing.rb +1 -1
  81. data/lib/keisan/parsing/line_separator.rb +6 -0
  82. data/lib/keisan/parsing/logical_and.rb +1 -1
  83. data/lib/keisan/parsing/logical_equal.rb +1 -1
  84. data/lib/keisan/parsing/logical_greater_than.rb +1 -1
  85. data/lib/keisan/parsing/logical_greater_than_or_equal_to.rb +1 -1
  86. data/lib/keisan/parsing/logical_less_than.rb +1 -1
  87. data/lib/keisan/parsing/logical_less_than_or_equal_to.rb +1 -1
  88. data/lib/keisan/parsing/logical_not.rb +1 -1
  89. data/lib/keisan/parsing/logical_not_equal.rb +1 -1
  90. data/lib/keisan/parsing/logical_not_not.rb +1 -1
  91. data/lib/keisan/parsing/logical_or.rb +1 -1
  92. data/lib/keisan/parsing/minus.rb +1 -1
  93. data/lib/keisan/parsing/modulo.rb +1 -1
  94. data/lib/keisan/parsing/operator.rb +1 -1
  95. data/lib/keisan/parsing/plus.rb +1 -1
  96. data/lib/keisan/parsing/times.rb +1 -1
  97. data/lib/keisan/parsing/unary_minus.rb +1 -1
  98. data/lib/keisan/parsing/unary_operator.rb +1 -1
  99. data/lib/keisan/parsing/unary_plus.rb +1 -1
  100. data/lib/keisan/repl.rb +1 -1
  101. data/lib/keisan/tokenizer.rb +4 -9
  102. data/lib/keisan/tokens/group.rb +3 -1
  103. data/lib/keisan/tokens/line_separator.rb +11 -0
  104. data/lib/keisan/variables/default_registry.rb +0 -5
  105. data/lib/keisan/variables/registry.rb +7 -7
  106. data/lib/keisan/version.rb +1 -1
  107. metadata +27 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1e9c5a4aead65a954cbf6f2dc06d4ccb579e0edf
4
- data.tar.gz: 2bbd7406b68ac4bd56f589fab026c303cf474edb
3
+ metadata.gz: 3b56feddfbcabbb14a37f3b278a234d1c110bdc4
4
+ data.tar.gz: 78ce88cb37946bc9356a78056a6ae2bbaa7ea788
5
5
  SHA512:
6
- metadata.gz: f71593bb29b2f3c088eb1f2ea759c80cdf9afc8a93f2f6f4ed30e9b706a3e528d07f57930d0397c1f4e8c27a927d058fc2fbb9f3961ff2abf3c5a33aac7770fb
7
- data.tar.gz: a2239cf26bb7d53c650308b2110b6f097801dc90a57bb5b69d1348a4b32902e74f6a2fb275eebcbd64d64f24865685bf2f442288cd976df3359996642a83aaae
6
+ metadata.gz: 4fe9d5949164c273f8b7634595630758e12905be56adb2df12c3fd1ea0491ccb063ff7a1348ab27d88a1f1b6286d2a17e1c65565d192a1d8c0682c4cd4e75a3a
7
+ data.tar.gz: 020dc6b00c5a8b24b98cbca79121a24321cea76067b31acca979a2f5f799f144b9ea0fb12f77fba474a5ce10f96aedc6375f065c0489e52f5c39112bff8922aa
data/README.md CHANGED
@@ -4,8 +4,10 @@
4
4
  [![Build Status](https://travis-ci.org/project-eutopia/keisan.png?branch=master)](https://travis-ci.org/project-eutopia/keisan)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
6
6
  [![Hakiri](https://hakiri.io/github/project-eutopia/keisan/master.svg)](https://hakiri.io/github/project-eutopia/keisan)
7
+ [![Maintainability](https://api.codeclimate.com/v1/badges/760e213d5ea81bca4480/maintainability)](https://codeclimate.com/github/project-eutopia/keisan/maintainability)
8
+ [![Coverage Status](https://coveralls.io/repos/github/project-eutopia/keisan/badge.svg?branch=master)](https://coveralls.io/github/project-eutopia/keisan?branch=master)
7
9
 
8
- Keisan ([計算, to calculate](https://en.wiktionary.org/wiki/%E8%A8%88%E7%AE%97#Japanese)) is a Ruby library for parsing equations into an abstract syntax tree. This allows for safe evaluation of string representations of mathematical/logical expressions.
10
+ Keisan ([計算, to calculate](https://en.wiktionary.org/wiki/%E8%A8%88%E7%AE%97#Japanese)) is a Ruby library for parsing equations into an abstract syntax tree. This allows for safe evaluation of string representations of mathematical/logical expressions. It also has support for variables, functions, conditionals, and loops, making it a Turing complete programming language.
9
11
 
10
12
  ## Installation
11
13
 
@@ -173,6 +175,39 @@ calculator.evaluate("my_fact(5)")
173
175
  #=> 120
174
176
  ```
175
177
 
178
+ ##### Multiple lines and blocks
179
+
180
+ Keisan understands strings which contain multiple lines. It will evaluate each line separately, and the last line will be the the result of the total evaluation. Lines can be separated by newlines or semi-colons.
181
+
182
+ ```ruby
183
+ calculator = Keisan::Calculator.new
184
+ calculator.evaluate("x = 2; y = 5\n x+y")
185
+ #=> 7
186
+ ```
187
+
188
+ The use of curly braces `{}` can be used to create block which has a new closure where variable definitions are local to the block itself. Inside a block, external variables are still visible and re-assignable, but new variable definitions remain local.
189
+
190
+ ```ruby
191
+ calculator = Keisan::Calculator.new
192
+ calculator.evaluate("x = 10; y = 20")
193
+ calculator.evaluate("{a = 100; x = 15; a+x+y}")
194
+ #=> 135
195
+ calculator.evaluate("x")
196
+ #=> 15
197
+ calculator.evaluate("a")
198
+ #=> Keisan::Exceptions::UndefinedVariableError: a
199
+ ```
200
+
201
+ By default assigning to a variable or function will bubble up to the first definition available in the parent scopes. To assign to a local variable, you can use the `let` keyword. The difference is illustrated below.
202
+
203
+ ```ruby
204
+ calculator = Keisan::Calculator.new
205
+ calculator.evaluate("x = 1; {x = 2}; x")
206
+ #=> 2
207
+ calculator.evaluate("x = 11; {let x = 12}; x")
208
+ #=> 11
209
+ ```
210
+
176
211
  ##### Lists
177
212
 
178
213
  Just like in Ruby, lists can be defined using square brackets, and indexed using square brackets
@@ -183,6 +218,10 @@ calculator.evaluate("[2, 3, 5, 8]")
183
218
  #=> [2, 3, 5, 8]
184
219
  calculator.evaluate("[[1,2,3],[4,5,6],[7,8,9]][1][2]")
185
220
  #=> 6
221
+ calculator.evaluate("a = [1,2,3]")
222
+ calculator.evaluate("a[1] = 22")
223
+ calculator.evaluate("a")
224
+ #=> [1, 22, 3]
186
225
  ```
187
226
 
188
227
  They can also be concatenated using the `+` operator
@@ -253,6 +292,15 @@ calculator.evaluate("2 + if(1 > 0, 10, 29)")
253
292
  #=> 12
254
293
  ```
255
294
 
295
+ For looping, you can use the basic `while` loop, which has an expression that evaluates to a boolean as the first argument, and any expression in the second argument. This works just like the normal `while` loop.
296
+
297
+ ```ruby
298
+ calculator = Keisan::Calculator.new
299
+ calculator.evaluate("my_sum(a) = {let i = 0; let total = 0; while(i < a.size, {total = total + a[i]; i = i + 1}); total}")
300
+ calculator.evaluate("my_sum([1,3,5,7,9])")
301
+ #=> 25
302
+ ```
303
+
256
304
  ##### Bitwise operations
257
305
 
258
306
  The basic bitwise operations, NOT `~`, OR `|`, XOR `^`, and AND `&` are also available for use
@@ -23,6 +23,7 @@ Gem::Specification.new do |spec|
23
23
 
24
24
  spec.add_dependency "activesupport", ">= 4.2.2"
25
25
 
26
+ spec.add_development_dependency "coveralls"
26
27
  spec.add_development_dependency "bundler", "~> 1.14"
27
28
  spec.add_development_dependency "rake", "~> 10.0"
28
29
  spec.add_development_dependency "rspec", "~> 3.0"
@@ -5,6 +5,7 @@ require "keisan/version"
5
5
  require "keisan/exceptions"
6
6
 
7
7
  require "keisan/ast/node"
8
+ require "keisan/ast/cell"
8
9
 
9
10
  require "keisan/ast/literal"
10
11
  require "keisan/ast/variable"
@@ -14,9 +15,11 @@ require "keisan/ast/string"
14
15
  require "keisan/ast/null"
15
16
  require "keisan/ast/boolean"
16
17
 
18
+ require "keisan/ast/block"
17
19
  require "keisan/ast/parent"
18
20
  require "keisan/ast/operator"
19
21
  require "keisan/ast/assignment"
22
+ require "keisan/ast/multi_line"
20
23
  require "keisan/ast/unary_operator"
21
24
  require "keisan/ast/unary_identity"
22
25
  require "keisan/ast/unary_plus"
@@ -47,6 +50,7 @@ require "keisan/ast/function"
47
50
  require "keisan/ast/list"
48
51
  require "keisan/ast/indexing"
49
52
 
53
+ require "keisan/ast/line_builder"
50
54
  require "keisan/ast/builder"
51
55
  require "keisan/ast"
52
56
 
@@ -73,6 +77,7 @@ require "keisan/tokens/arithmetic_operator"
73
77
  require "keisan/tokens/logical_operator"
74
78
  require "keisan/tokens/bitwise_operator"
75
79
  require "keisan/tokens/word"
80
+ require "keisan/tokens/line_separator"
76
81
 
77
82
  require "keisan/tokenizer"
78
83
 
@@ -91,9 +96,11 @@ require "keisan/parsing/function"
91
96
  require "keisan/parsing/group"
92
97
  require "keisan/parsing/round_group"
93
98
  require "keisan/parsing/square_group"
99
+ require "keisan/parsing/curly_group"
94
100
  require "keisan/parsing/list"
95
101
  require "keisan/parsing/indexing"
96
102
  require "keisan/parsing/argument"
103
+ require "keisan/parsing/line_separator"
97
104
 
98
105
  require "keisan/parsing/operator"
99
106
 
@@ -134,4 +141,27 @@ require "keisan/calculator"
134
141
  require "keisan/evaluator"
135
142
 
136
143
  module Keisan
144
+ def self.calculator
145
+ @@calculator ||= Calculator.new
146
+ end
147
+
148
+ def self.reset
149
+ @@calculator = nil
150
+ end
151
+
152
+ def self.[](expression)
153
+ simplify(expression)
154
+ end
155
+
156
+ def self.evaluate(expression)
157
+ calculator.evaluate(expression)
158
+ end
159
+
160
+ def self.simplify(expression)
161
+ calculator.simplify(expression)
162
+ end
163
+
164
+ def self.ast(expression)
165
+ calculator.ast(expression)
166
+ end
137
167
  end
@@ -1,12 +1,19 @@
1
1
  module Keisan
2
2
  module AST
3
3
  class Assignment < Operator
4
+ attr_reader :local
5
+
6
+ def initialize(children = [], parsing_operators = [], local: false)
7
+ super(children, parsing_operators)
8
+ @local = local
9
+ end
10
+
4
11
  def self.symbol
5
12
  :"="
6
13
  end
7
14
 
8
15
  def evaluate(context = nil)
9
- context ||= Keisan::Context.new
16
+ context ||= Context.new
10
17
 
11
18
  lhs = children.first
12
19
  rhs = children.last
@@ -16,7 +23,8 @@ module Keisan
16
23
  elsif is_function_definition?
17
24
  evaluate_function(context, lhs, rhs)
18
25
  else
19
- raise Keisan::Exceptions::InvalidExpression.new("Unhandled left hand side #{lhs} in assignment")
26
+ # Try cell assignment
27
+ evaluate_cell_assignment(context, lhs, rhs)
20
28
  end
21
29
  end
22
30
 
@@ -24,6 +32,10 @@ module Keisan
24
32
  evaluate(context)
25
33
  end
26
34
 
35
+ def evaluate_assignments(context = nil)
36
+ evaluate(context)
37
+ end
38
+
27
39
  def unbound_variables(context = nil)
28
40
  variables = super(context)
29
41
  if is_variable_definition?
@@ -43,52 +55,67 @@ module Keisan
43
55
  end
44
56
 
45
57
  def is_variable_definition?
46
- children.first.is_a?(Keisan::AST::Variable)
58
+ children.first.is_a?(Variable)
47
59
  end
48
60
 
49
61
  def is_function_definition?
50
- children.first.is_a?(Keisan::AST::Function)
62
+ children.first.is_a?(Function)
51
63
  end
52
64
 
53
65
  private
54
66
 
67
+ def evaluate_cell_assignment(context, lhs, rhs)
68
+ lhs = lhs.evaluate(context)
69
+ unless lhs.is_a?(Cell)
70
+ raise Exceptions::InvalidExpression.new("Unhandled left hand side #{lhs} in assignment")
71
+ end
72
+
73
+ rhs = rhs.evaluate(context)
74
+
75
+ lhs.node = rhs
76
+ rhs
77
+ end
78
+
55
79
  def evaluate_variable(context, lhs, rhs)
56
80
  rhs = rhs.evaluate(context)
57
81
 
58
82
  unless rhs.well_defined?
59
- raise Keisan::Exceptions::InvalidExpression.new("Right hand side of assignment to variable must be well defined")
83
+ raise Exceptions::InvalidExpression.new("Right hand side of assignment to variable must be well defined")
60
84
  end
61
85
 
62
86
  rhs_value = rhs.value(context)
63
- context.register_variable!(lhs.name, rhs_value)
87
+ context.register_variable!(lhs.name, rhs_value, local: local)
64
88
  # Return the variable assigned value
65
89
  rhs
66
90
  end
67
91
 
68
92
  def evaluate_function(context, lhs, rhs)
69
- unless lhs.children.all? {|arg| arg.is_a?(Keisan::AST::Variable)}
70
- raise Keisan::Exceptions::InvalidExpression.new("Left hand side function must have variables as arguments")
93
+ unless lhs.children.all? {|arg| arg.is_a?(Variable)}
94
+ raise Exceptions::InvalidExpression.new("Left hand side function must have variables as arguments")
71
95
  end
72
96
 
73
97
  argument_names = lhs.children.map(&:name)
74
98
  function_definition_context = context.spawn_child(shadowed: argument_names, transient: true)
75
99
 
76
- unless rhs.unbound_variables(context) <= Set.new(argument_names)
77
- raise Keisan::Exceptions::InvalidExpression.new("Unbound variables found in function definition")
78
- end
79
-
80
- unless context.allow_recursive || rhs.unbound_functions(context).empty?
81
- raise Keisan::Exceptions::InvalidExpression.new("Unbound function definitions are not allowed by current context")
100
+ # Blocks might have local variable/function definitions
101
+ if !rhs.is_a?(Block)
102
+ unless rhs.unbound_variables(context) <= Set.new(argument_names)
103
+ raise Exceptions::InvalidExpression.new("Unbound variables found in function definition")
104
+ end
105
+ unless context.allow_recursive || rhs.unbound_functions(context).empty?
106
+ raise Exceptions::InvalidExpression.new("Unbound function definitions are not allowed by current context")
107
+ end
82
108
  end
83
109
 
84
110
  context.register_function!(
85
111
  lhs.name,
86
- Keisan::Functions::ExpressionFunction.new(
112
+ Functions::ExpressionFunction.new(
87
113
  lhs.name,
88
114
  argument_names,
89
- rhs.simplify(function_definition_context),
115
+ rhs.evaluate_assignments(function_definition_context),
90
116
  context.transient_definitions
91
- )
117
+ ),
118
+ local: local
92
119
  )
93
120
 
94
121
  rhs
@@ -0,0 +1,60 @@
1
+ module Keisan
2
+ module AST
3
+ class Block < Node
4
+ attr_reader :child
5
+
6
+ def initialize(child)
7
+ @child = child
8
+ end
9
+
10
+ def unbound_variables(context = nil)
11
+ local = get_local_context(context)
12
+ child.unbound_variables(local)
13
+ end
14
+
15
+ def unbound_functions(context = nil)
16
+ local = get_local_context(context)
17
+ child.unbound_functions(local)
18
+ end
19
+
20
+ def deep_dup
21
+ dupped = dup
22
+ dupped.instance_variable_set(
23
+ :@child,
24
+ dupped.child.deep_dup
25
+ )
26
+ dupped
27
+ end
28
+
29
+ def value(context = nil)
30
+ local = get_local_context(context)
31
+ child.evaluated(local).value(local)
32
+ end
33
+
34
+ def evaluate(context = nil)
35
+ local = get_local_context(context)
36
+ child.evaluate(local)
37
+ end
38
+
39
+ def simplify(context = nil)
40
+ local = get_local_context(context)
41
+ child.simplify(local)
42
+ end
43
+
44
+ def replace(variable, replacement)
45
+ self
46
+ end
47
+
48
+ def to_s
49
+ "{#{child}}"
50
+ end
51
+
52
+ private
53
+
54
+ def get_local_context(context)
55
+ context ||= Context.new
56
+ context.spawn_child(transient: false)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -12,14 +12,14 @@ module Keisan
12
12
  end
13
13
 
14
14
  def !
15
- AST::Boolean.new(!bool)
15
+ Boolean.new(!bool)
16
16
  end
17
17
 
18
18
  def and(other)
19
19
  other = other.to_node
20
20
  case other
21
- when AST::Boolean
22
- AST::Boolean.new(bool && other.bool)
21
+ when Boolean
22
+ Boolean.new(bool && other.bool)
23
23
  else
24
24
  super
25
25
  end
@@ -28,8 +28,8 @@ module Keisan
28
28
  def or(other)
29
29
  other = other.to_node
30
30
  case other
31
- when AST::Boolean
32
- AST::Boolean.new(bool || other.bool)
31
+ when Boolean
32
+ Boolean.new(bool || other.bool)
33
33
  else
34
34
  super
35
35
  end
@@ -4,234 +4,37 @@ module Keisan
4
4
  # Build from parser
5
5
  def initialize(string: nil, parser: nil, components: nil)
6
6
  if [string, parser, components].select(&:nil?).size != 2
7
- raise Keisan::Exceptions::InternalError.new("Require one of string, parser or components")
7
+ raise Exceptions::InternalError.new("Require one of string, parser or components")
8
8
  end
9
9
 
10
10
  if !string.nil?
11
- @components = Keisan::Parser.new(string: string).components
11
+ @components = Parser.new(string: string).components
12
12
  elsif !parser.nil?
13
13
  @components = parser.components
14
14
  else
15
15
  @components = Array.wrap(components)
16
16
  end
17
17
 
18
- @nodes = components_to_basic_nodes(@components)
18
+ @lines = @components.split {|component|
19
+ component.is_a?(Parsing::LineSeparator)
20
+ }.reject(&:empty?)
19
21
 
20
- # Negative means not an operator
21
- @priorities = @nodes.map {|node| node.is_a?(Keisan::Parsing::Operator) ? node.priority : -1}
22
+ @line_builders = @lines.map {|line| LineBuilder.new(line)}
22
23
 
23
- consume_operators!
24
-
25
- case @nodes.count
26
- when 0
27
- # Empty string, set to just Null
28
- @nodes = [Keisan::AST::Null.new]
29
- when 1
30
- # Good
24
+ if @line_builders.size == 1
25
+ @node = @line_builders.first.ast
31
26
  else
32
- raise Keisan::Exceptions::ASTError.new("Should end up with a single node")
27
+ @node = MultiLine.new(@line_builders.map(&:ast))
33
28
  end
34
29
  end
35
30
 
36
31
  def node
37
- @nodes.first
32
+ @node
38
33
  end
39
34
 
40
35
  def ast
41
36
  node
42
37
  end
43
-
44
- private
45
-
46
- # Array of AST elements, and Parsing operators
47
- def components_to_basic_nodes(components)
48
- nodes_components = []
49
-
50
- components.each do |component|
51
- if nodes_components.empty?
52
- nodes_components << [component]
53
- else
54
- is_operator = [nodes_components.last.last.is_a?(Keisan::Parsing::Operator), component.is_a?(Keisan::Parsing::Operator)]
55
-
56
- if is_operator.first == is_operator.last
57
- nodes_components.last << component
58
- else
59
- nodes_components << [component]
60
- end
61
- end
62
- end
63
-
64
- nodes_components.inject([]) do |nodes, node_or_component_group|
65
- if node_or_component_group.first.is_a?(Keisan::Parsing::Operator)
66
- node_or_component_group.each do |component|
67
- nodes << component
68
- end
69
- else
70
- nodes << node_from_components(node_or_component_group)
71
- end
72
-
73
- nodes
74
- end
75
- end
76
-
77
- def node_from_components(components)
78
- node, postfix_components = *node_postfixes(components)
79
- # Apply postfix operators
80
- postfix_components.each do |postfix_component|
81
- node = apply_postfix_component_to_node(postfix_component, node)
82
- end
83
-
84
- node
85
- end
86
-
87
- def apply_postfix_component_to_node(postfix_component, node)
88
- case postfix_component
89
- when Keisan::Parsing::Indexing
90
- postfix_component.node_class.new(
91
- node,
92
- postfix_component.arguments.map {|parsing_argument|
93
- Builder.new(components: parsing_argument.components).node
94
- }
95
- )
96
- when Keisan::Parsing::DotWord
97
- Keisan::AST::Function.new(
98
- [node],
99
- postfix_component.name
100
- )
101
- when Keisan::Parsing::DotOperator
102
- Keisan::AST::Function.new(
103
- [node] + postfix_component.arguments.map {|parsing_argument|
104
- Builder.new(components: parsing_argument.components).node
105
- },
106
- postfix_component.name
107
- )
108
- else
109
- raise Keisan::Exceptions::ASTError.new("Invalid postfix component #{postfix_component}")
110
- end
111
- end
112
-
113
- # Returns an array of the form
114
- # [node, postfix_operators]
115
- # middle_node is the main node which will be modified by prefix and postfix operators
116
- # postfix_operators is an array of Keisan::Parsing::Indexing, DotWord, and DotOperator objects
117
- def node_postfixes(components)
118
- index_of_postfix_components = components.map.with_index {|c,i| [c,i]}.select {|c,i|
119
- c.is_a?(Keisan::Parsing::Indexing) || c.is_a?(Keisan::Parsing::DotWord) || c.is_a?(Keisan::Parsing::DotOperator)
120
- }.map(&:last)
121
- unless index_of_postfix_components.reverse.map.with_index.all? {|i,j| i + j == components.size - 1 }
122
- raise Keisan::Exceptions::ASTError.new("postfix components must be in back")
123
- end
124
-
125
- num_postfix = index_of_postfix_components.size
126
-
127
- unless num_postfix + 1 == components.size
128
- raise Keisan::Exceptions::ASTError.new("have too many components")
129
- end
130
-
131
- [
132
- node_of_component(components[0]),
133
- index_of_postfix_components.map {|i| components[i]}
134
- ]
135
- end
136
-
137
- def node_of_component(component)
138
- case component
139
- when Keisan::Parsing::Number
140
- Keisan::AST::Number.new(component.value)
141
- when Keisan::Parsing::String
142
- Keisan::AST::String.new(component.value)
143
- when Keisan::Parsing::Null
144
- Keisan::AST::Null.new
145
- when Keisan::Parsing::Variable
146
- Keisan::AST::Variable.new(component.name)
147
- when Keisan::Parsing::Boolean
148
- Keisan::AST::Boolean.new(component.value)
149
- when Keisan::Parsing::List
150
- Keisan::AST::List.new(
151
- component.arguments.map {|parsing_argument|
152
- Builder.new(components: parsing_argument.components).node
153
- }
154
- )
155
- when Keisan::Parsing::Group
156
- Builder.new(components: component.components).node
157
- when Keisan::Parsing::Function
158
- Keisan::AST::Function.new(
159
- component.arguments.map {|parsing_argument|
160
- Builder.new(components: parsing_argument.components).node
161
- },
162
- component.name
163
- )
164
- when Keisan::Parsing::DotWord
165
- Keisan::AST::Function.new(
166
- [node_of_component(component.target)],
167
- component.name
168
- )
169
- when Keisan::Parsing::DotOperator
170
- Keisan::AST::Function.new(
171
- [node_of_component(component.target)] + component.arguments.map {|parsing_argument|
172
- Builder.new(components: parsing_argument.components).node
173
- },
174
- component.name
175
- )
176
- else
177
- raise Keisan::Exceptions::ASTError.new("Unhandled component, #{component}")
178
- end
179
- end
180
-
181
- def consume_operators!
182
- loop do
183
- break if @priorities.empty?
184
- max_priority = @priorities.max
185
- break if max_priority < 0
186
-
187
- consume_operators_with_priority!(max_priority)
188
- end
189
- end
190
-
191
- def consume_operators_with_priority!(priority)
192
- p_indexes = @priorities.map.with_index.select {|p,index| p == priority}
193
- # :left, :right, or :none
194
- associativity = AST::Operator.associativity_of_priority(priority)
195
-
196
- if associativity == :right
197
- index = p_indexes[-1][1]
198
- else
199
- index = p_indexes[0][1]
200
- end
201
-
202
- operator = @nodes[index]
203
-
204
- # If has unary operators after, must process those first
205
- if @nodes[index+1].is_a?(Keisan::Parsing::UnaryOperator)
206
- loop do
207
- break if !@nodes[index+1].is_a?(Keisan::Parsing::UnaryOperator)
208
- index += 1
209
- end
210
- operator = @nodes[index]
211
- end
212
-
213
- # operator is the current operator to process, and index is its index
214
- if operator.is_a?(Keisan::Parsing::UnaryOperator)
215
- replacement_node = operator.node_class.new(
216
- children = [@nodes[index+1]]
217
- )
218
- @nodes.delete_if.with_index {|node, i| i >= index && i <= index+1}
219
- @priorities.delete_if.with_index {|node, i| i >= index && i <= index+1}
220
- @nodes.insert(index, replacement_node)
221
- @priorities.insert(index, -1)
222
- elsif operator.is_a?(Keisan::Parsing::Operator)
223
- replacement_node = operator.node_class.new(
224
- children = [@nodes[index-1],@nodes[index+1]],
225
- parsing_operators = [operator]
226
- )
227
- @nodes.delete_if.with_index {|node, i| i >= index-1 && i <= index+1}
228
- @priorities.delete_if.with_index {|node, i| i >= index-1 && i <= index+1}
229
- @nodes.insert(index-1, replacement_node)
230
- @priorities.insert(index-1, -1)
231
- else
232
- raise Keisan::Exceptions::ASTError.new("Can only consume operators")
233
- end
234
- end
235
38
  end
236
39
  end
237
40
  end