keisan 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +49 -1
- data/keisan.gemspec +1 -0
- data/lib/keisan.rb +30 -0
- data/lib/keisan/ast/assignment.rb +44 -17
- data/lib/keisan/ast/block.rb +60 -0
- data/lib/keisan/ast/boolean.rb +5 -5
- data/lib/keisan/ast/builder.rb +10 -207
- data/lib/keisan/ast/cell.rb +60 -0
- data/lib/keisan/ast/constant_literal.rb +9 -0
- data/lib/keisan/ast/exponent.rb +6 -6
- data/lib/keisan/ast/function.rb +12 -8
- data/lib/keisan/ast/indexing.rb +25 -15
- data/lib/keisan/ast/line_builder.rb +230 -0
- data/lib/keisan/ast/list.rb +28 -1
- data/lib/keisan/ast/literal.rb +0 -8
- data/lib/keisan/ast/logical_and.rb +1 -1
- data/lib/keisan/ast/logical_or.rb +1 -1
- data/lib/keisan/ast/multi_line.rb +28 -0
- data/lib/keisan/ast/node.rb +32 -24
- data/lib/keisan/ast/number.rb +31 -31
- data/lib/keisan/ast/operator.rb +12 -4
- data/lib/keisan/ast/parent.rb +4 -4
- data/lib/keisan/ast/plus.rb +10 -10
- data/lib/keisan/ast/string.rb +3 -3
- data/lib/keisan/ast/times.rb +8 -8
- data/lib/keisan/ast/unary_identity.rb +1 -1
- data/lib/keisan/ast/unary_inverse.rb +7 -7
- data/lib/keisan/ast/unary_minus.rb +5 -5
- data/lib/keisan/ast/unary_operator.rb +2 -2
- data/lib/keisan/ast/unary_plus.rb +2 -2
- data/lib/keisan/ast/variable.rb +26 -10
- data/lib/keisan/context.rb +5 -5
- data/lib/keisan/evaluator.rb +15 -8
- data/lib/keisan/function.rb +24 -6
- data/lib/keisan/functions/cbrt.rb +1 -1
- data/lib/keisan/functions/cos.rb +1 -1
- data/lib/keisan/functions/cosh.rb +1 -1
- data/lib/keisan/functions/cot.rb +1 -1
- data/lib/keisan/functions/coth.rb +1 -1
- data/lib/keisan/functions/csc.rb +1 -1
- data/lib/keisan/functions/csch.rb +1 -1
- data/lib/keisan/functions/default_registry.rb +53 -74
- data/lib/keisan/functions/diff.rb +18 -14
- data/lib/keisan/functions/erf.rb +15 -0
- data/lib/keisan/functions/exp.rb +1 -1
- data/lib/keisan/functions/expression_function.rb +15 -21
- data/lib/keisan/functions/filter.rb +13 -15
- data/lib/keisan/functions/if.rb +14 -20
- data/lib/keisan/functions/let.rb +36 -0
- data/lib/keisan/functions/map.rb +11 -13
- data/lib/keisan/functions/math_function.rb +2 -2
- data/lib/keisan/functions/proc_function.rb +10 -6
- data/lib/keisan/functions/rand.rb +2 -1
- data/lib/keisan/functions/range.rb +74 -0
- data/lib/keisan/functions/reduce.rb +12 -14
- data/lib/keisan/functions/registry.rb +7 -7
- data/lib/keisan/functions/replace.rb +8 -8
- data/lib/keisan/functions/sample.rb +2 -1
- data/lib/keisan/functions/sec.rb +1 -1
- data/lib/keisan/functions/sech.rb +1 -1
- data/lib/keisan/functions/sin.rb +1 -1
- data/lib/keisan/functions/sinh.rb +1 -1
- data/lib/keisan/functions/sqrt.rb +1 -1
- data/lib/keisan/functions/tan.rb +1 -1
- data/lib/keisan/functions/tanh.rb +1 -1
- data/lib/keisan/functions/while.rb +46 -0
- data/lib/keisan/parser.rb +121 -79
- data/lib/keisan/parsing/assignment.rb +1 -1
- data/lib/keisan/parsing/bitwise_and.rb +1 -1
- data/lib/keisan/parsing/bitwise_not.rb +1 -1
- data/lib/keisan/parsing/bitwise_not_not.rb +1 -1
- data/lib/keisan/parsing/bitwise_or.rb +1 -1
- data/lib/keisan/parsing/bitwise_xor.rb +1 -1
- data/lib/keisan/parsing/curly_group.rb +6 -0
- data/lib/keisan/parsing/divide.rb +1 -1
- data/lib/keisan/parsing/exponent.rb +1 -1
- data/lib/keisan/parsing/function.rb +1 -1
- data/lib/keisan/parsing/group.rb +1 -1
- data/lib/keisan/parsing/indexing.rb +1 -1
- data/lib/keisan/parsing/line_separator.rb +6 -0
- data/lib/keisan/parsing/logical_and.rb +1 -1
- data/lib/keisan/parsing/logical_equal.rb +1 -1
- data/lib/keisan/parsing/logical_greater_than.rb +1 -1
- data/lib/keisan/parsing/logical_greater_than_or_equal_to.rb +1 -1
- data/lib/keisan/parsing/logical_less_than.rb +1 -1
- data/lib/keisan/parsing/logical_less_than_or_equal_to.rb +1 -1
- data/lib/keisan/parsing/logical_not.rb +1 -1
- data/lib/keisan/parsing/logical_not_equal.rb +1 -1
- data/lib/keisan/parsing/logical_not_not.rb +1 -1
- data/lib/keisan/parsing/logical_or.rb +1 -1
- data/lib/keisan/parsing/minus.rb +1 -1
- data/lib/keisan/parsing/modulo.rb +1 -1
- data/lib/keisan/parsing/operator.rb +1 -1
- data/lib/keisan/parsing/plus.rb +1 -1
- data/lib/keisan/parsing/times.rb +1 -1
- data/lib/keisan/parsing/unary_minus.rb +1 -1
- data/lib/keisan/parsing/unary_operator.rb +1 -1
- data/lib/keisan/parsing/unary_plus.rb +1 -1
- data/lib/keisan/repl.rb +1 -1
- data/lib/keisan/tokenizer.rb +4 -9
- data/lib/keisan/tokens/group.rb +3 -1
- data/lib/keisan/tokens/line_separator.rb +11 -0
- data/lib/keisan/variables/default_registry.rb +0 -5
- data/lib/keisan/variables/registry.rb +7 -7
- data/lib/keisan/version.rb +1 -1
- metadata +27 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3b56feddfbcabbb14a37f3b278a234d1c110bdc4
|
4
|
+
data.tar.gz: 78ce88cb37946bc9356a78056a6ae2bbaa7ea788
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/keisan.gemspec
CHANGED
@@ -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"
|
data/lib/keisan.rb
CHANGED
@@ -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 ||=
|
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
|
-
|
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?(
|
58
|
+
children.first.is_a?(Variable)
|
47
59
|
end
|
48
60
|
|
49
61
|
def is_function_definition?
|
50
|
-
children.first.is_a?(
|
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
|
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?(
|
70
|
-
raise
|
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
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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
|
-
|
112
|
+
Functions::ExpressionFunction.new(
|
87
113
|
lhs.name,
|
88
114
|
argument_names,
|
89
|
-
rhs.
|
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
|
data/lib/keisan/ast/boolean.rb
CHANGED
@@ -12,14 +12,14 @@ module Keisan
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def !
|
15
|
-
|
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
|
22
|
-
|
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
|
32
|
-
|
31
|
+
when Boolean
|
32
|
+
Boolean.new(bool || other.bool)
|
33
33
|
else
|
34
34
|
super
|
35
35
|
end
|
data/lib/keisan/ast/builder.rb
CHANGED
@@ -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
|
7
|
+
raise Exceptions::InternalError.new("Require one of string, parser or components")
|
8
8
|
end
|
9
9
|
|
10
10
|
if !string.nil?
|
11
|
-
@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
|
-
@
|
18
|
+
@lines = @components.split {|component|
|
19
|
+
component.is_a?(Parsing::LineSeparator)
|
20
|
+
}.reject(&:empty?)
|
19
21
|
|
20
|
-
|
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
|
-
|
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
|
-
|
27
|
+
@node = MultiLine.new(@line_builders.map(&:ast))
|
33
28
|
end
|
34
29
|
end
|
35
30
|
|
36
31
|
def node
|
37
|
-
@
|
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
|