keisan 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +3 -5
- data/README.md +112 -8
- data/bin/keisan +7 -0
- data/lib/keisan.rb +0 -1
- data/lib/keisan/ast.rb +24 -0
- data/lib/keisan/ast/assignment.rb +36 -10
- data/lib/keisan/ast/builder.rb +7 -1
- data/lib/keisan/ast/constant_literal.rb +6 -1
- data/lib/keisan/ast/exponent.rb +1 -1
- data/lib/keisan/ast/function.rb +7 -3
- data/lib/keisan/ast/list.rb +1 -1
- data/lib/keisan/ast/node.rb +1 -1
- data/lib/keisan/ast/parent.rb +1 -1
- data/lib/keisan/ast/plus.rb +11 -1
- data/lib/keisan/ast/string.rb +8 -0
- data/lib/keisan/ast/unary_operator.rb +1 -1
- data/lib/keisan/ast/variable.rb +8 -2
- data/lib/keisan/calculator.rb +16 -0
- data/lib/keisan/context.rb +39 -8
- data/lib/keisan/evaluator.rb +11 -1
- data/lib/keisan/function.rb +4 -0
- data/lib/keisan/functions/abs.rb +9 -0
- data/lib/keisan/functions/cbrt.rb +15 -0
- data/lib/keisan/functions/cmath_function.rb +11 -0
- data/lib/keisan/functions/cos.rb +15 -0
- data/lib/keisan/functions/cosh.rb +15 -0
- data/lib/keisan/functions/cot.rb +15 -0
- data/lib/keisan/functions/coth.rb +15 -0
- data/lib/keisan/functions/csc.rb +15 -0
- data/lib/keisan/functions/csch.rb +15 -0
- data/lib/keisan/functions/default_registry.rb +95 -1
- data/lib/keisan/functions/diff.rb +36 -14
- data/lib/keisan/functions/exp.rb +15 -0
- data/lib/keisan/functions/expression_function.rb +65 -15
- data/lib/keisan/functions/filter.rb +64 -0
- data/lib/keisan/functions/if.rb +15 -12
- data/lib/keisan/functions/imag.rb +9 -0
- data/lib/keisan/functions/log.rb +15 -0
- data/lib/keisan/functions/map.rb +57 -0
- data/lib/keisan/functions/math_function.rb +34 -0
- data/lib/keisan/functions/proc_function.rb +5 -3
- data/lib/keisan/functions/real.rb +9 -0
- data/lib/keisan/functions/reduce.rb +65 -0
- data/lib/keisan/functions/registry.rb +5 -1
- data/lib/keisan/functions/sec.rb +15 -0
- data/lib/keisan/functions/sech.rb +15 -0
- data/lib/keisan/functions/sin.rb +15 -0
- data/lib/keisan/functions/sinh.rb +15 -0
- data/lib/keisan/functions/sqrt.rb +15 -0
- data/lib/keisan/functions/tan.rb +15 -0
- data/lib/keisan/functions/tanh.rb +15 -0
- data/lib/keisan/repl.rb +105 -0
- data/lib/keisan/tokenizer.rb +5 -2
- data/lib/keisan/tokens/string.rb +1 -1
- data/lib/keisan/variables/registry.rb +14 -3
- data/lib/keisan/version.rb +1 -1
- data/screenshots/repl.png +0 -0
- metadata +30 -6
- data/lib/keisan/ast/functions/diff.rb +0 -57
- data/lib/keisan/ast/functions/if.rb +0 -47
- data/lib/keisan/function_definition_context.rb +0 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1e9c5a4aead65a954cbf6f2dc06d4ccb579e0edf
|
4
|
+
data.tar.gz: 2bbd7406b68ac4bd56f589fab026c303cf474edb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f71593bb29b2f3c088eb1f2ea759c80cdf9afc8a93f2f6f4ed30e9b706a3e528d07f57930d0397c1f4e8c27a927d058fc2fbb9f3961ff2abf3c5a33aac7770fb
|
7
|
+
data.tar.gz: a2239cf26bb7d53c650308b2110b6f097801dc90a57bb5b69d1348a4b32902e74f6a2fb275eebcbd64d64f24865685bf2f442288cd976df3359996642a83aaae
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -11,7 +11,7 @@ Keisan ([計算, to calculate](https://en.wiktionary.org/wiki/%E8%A8%88%E7%AE%97
|
|
11
11
|
|
12
12
|
Add this line to your application's Gemfile:
|
13
13
|
|
14
|
-
```
|
14
|
+
```
|
15
15
|
gem 'keisan'
|
16
16
|
```
|
17
17
|
|
@@ -25,14 +25,47 @@ Or install it yourself as:
|
|
25
25
|
|
26
26
|
## Usage
|
27
27
|
|
28
|
+
### REPL
|
29
|
+
|
30
|
+
The command `bin/keisan` will open up an interactive REPL. The commands you type in to this REPL are relayed to an internal `Keisan::Calculator` class and displayed back to you.
|
31
|
+
|
32
|
+

|
33
|
+
|
28
34
|
### Calculator class
|
29
35
|
|
30
|
-
The functionality of `keisan` can be demonstrated by using the `Keisan::Calculator` class. The `evaluate` method evaluates an expression by parsing it into an abstract syntax tree (AST), then evaluating any member functions/variables given.
|
36
|
+
The functionality of `keisan` can be demonstrated by using the `Keisan::Calculator` class. The `evaluate` method evaluates an expression by parsing it into an abstract syntax tree (AST), then evaluating any member functions/variables given. There is also a `simplify` method that allows undefined variables and functions to exist, and will just return the simplified AST.
|
31
37
|
|
32
38
|
```ruby
|
33
39
|
calculator = Keisan::Calculator.new
|
34
40
|
calculator.evaluate("15 + 2 * (1 + 3)")
|
35
41
|
#=> 23
|
42
|
+
calculator.simplify("1*(0*2+x*g(t))").to_s
|
43
|
+
#=> "x*g(t)"
|
44
|
+
```
|
45
|
+
|
46
|
+
For users who want access to the parsed abstract syntax tree, you can use the `ast` method to parse any given expression.
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
calculator = Keisan::Calculator.new
|
50
|
+
ast = calculator.ast("x**2+1")
|
51
|
+
ast.to_s
|
52
|
+
#=> "(x**2)+1"
|
53
|
+
ast.class
|
54
|
+
#=> Keisan::AST::Plus
|
55
|
+
ast.children[0].class
|
56
|
+
#=> Keisan::AST::Exponent
|
57
|
+
ast.children[0].children[0].class
|
58
|
+
#=> Keisan::AST::Variable
|
59
|
+
ast.children[0].children[0].name
|
60
|
+
#=> "x"
|
61
|
+
ast.children[0].children[1].class
|
62
|
+
#=> Keisan::AST::Number
|
63
|
+
ast.children[0].children[1].value
|
64
|
+
#=> 2
|
65
|
+
ast.children[1].class
|
66
|
+
#=> Keisan::AST::Number
|
67
|
+
ast.children[1].value
|
68
|
+
#=> 1
|
36
69
|
```
|
37
70
|
|
38
71
|
##### Specifying variables
|
@@ -40,6 +73,7 @@ calculator.evaluate("15 + 2 * (1 + 3)")
|
|
40
73
|
Passing in a hash of variable (`name`, `value`) pairs to the `evaluate` method defines variables
|
41
74
|
|
42
75
|
```ruby
|
76
|
+
calculator = Keisan::Calculator.new
|
43
77
|
calculator.evaluate("3*x + y**2", x: -2.5, y: 3)
|
44
78
|
#=> 1.5
|
45
79
|
```
|
@@ -47,6 +81,7 @@ calculator.evaluate("3*x + y**2", x: -2.5, y: 3)
|
|
47
81
|
It will raise an error if an variable is not defined
|
48
82
|
|
49
83
|
```ruby
|
84
|
+
calculator = Keisan::Calculator.new
|
50
85
|
calculator.evaluate("x + 1")
|
51
86
|
#=> Keisan::Exceptions::UndefinedVariableError: x
|
52
87
|
```
|
@@ -54,6 +89,7 @@ calculator.evaluate("x + 1")
|
|
54
89
|
It is also possible to define variables in the string expression itself
|
55
90
|
|
56
91
|
```ruby
|
92
|
+
calculator = Keisan::Calculator.new
|
57
93
|
calculator.evaluate("x = 10*n", n: 2)
|
58
94
|
calculator.evaluate("3*x + 1")
|
59
95
|
#=> 61
|
@@ -64,6 +100,7 @@ calculator.evaluate("3*x + 1")
|
|
64
100
|
Just like variables, functions can be defined by passing a `Proc` object as follows
|
65
101
|
|
66
102
|
```ruby
|
103
|
+
calculator = Keisan::Calculator.new
|
67
104
|
calculator.evaluate("2*f(1+2) + 4", f: Proc.new {|x| x**2})
|
68
105
|
#=> 22
|
69
106
|
```
|
@@ -71,6 +108,7 @@ calculator.evaluate("2*f(1+2) + 4", f: Proc.new {|x| x**2})
|
|
71
108
|
It will raise an error if a function is not defined
|
72
109
|
|
73
110
|
```ruby
|
111
|
+
calculator = Keisan::Calculator.new
|
74
112
|
calculator.evaluate("f(2) + 1")
|
75
113
|
#=> Keisan::Exceptions::UndefinedFunctionError: f
|
76
114
|
```
|
@@ -78,6 +116,7 @@ calculator.evaluate("f(2) + 1")
|
|
78
116
|
Note that functions work in both regular (`f(x)`) and postfix (`x.f()`) notation. The postfix notation requires the function to take at least one argument. In the case of `a.f(b,c)`, this is translated internally to `f(a,b,c)`. If there is only a single argument to the function, the braces can be left off: `x.f`.
|
79
117
|
|
80
118
|
```ruby
|
119
|
+
calculator = Keisan::Calculator.new
|
81
120
|
calculator.evaluate("[1,3,5,7].size()")
|
82
121
|
#=> 4
|
83
122
|
calculator.evaluate("[1,3,5,7].size")
|
@@ -87,6 +126,7 @@ calculator.evaluate("[1,3,5,7].size")
|
|
87
126
|
It is even possible to do more complicated things like follows
|
88
127
|
|
89
128
|
```ruby
|
129
|
+
calculator = Keisan::Calculator.new
|
90
130
|
calculator.define_function!("f", Proc.new {|x| [[x-1,x+1], [x-2,x,x+2]]})
|
91
131
|
calculator.evaluate("4.f")
|
92
132
|
#=> [[3,5], [2,4,6]]
|
@@ -103,6 +143,7 @@ calculator.evaluate("4.f[1].size")
|
|
103
143
|
Like variables, it is also possible to define functions in the string expression itself.
|
104
144
|
|
105
145
|
```ruby
|
146
|
+
calculator = Keisan::Calculator.new
|
106
147
|
calculator.evaluate("f(x) = n*x", n: 10) # n is local to this definition only
|
107
148
|
calculator.evaluate("f(3)")
|
108
149
|
#=> 30
|
@@ -115,6 +156,7 @@ calculator.evaluate("n") # n only exists in the definition of f(x)
|
|
115
156
|
This form even supports recursion, but you must explicitly allow it.
|
116
157
|
|
117
158
|
```ruby
|
159
|
+
calculator = Keisan::Calculator.new
|
118
160
|
calculator = Keisan::Calculator.new(allow_recursive: false)
|
119
161
|
calculator.evaluate("my_fact(n) = if (n > 1, n*my_fact(n-1), 1)")
|
120
162
|
#=> Keisan::Exceptions::InvalidExpression: Unbound function definitions are not allowed by current context
|
@@ -136,6 +178,7 @@ calculator.evaluate("my_fact(5)")
|
|
136
178
|
Just like in Ruby, lists can be defined using square brackets, and indexed using square brackets
|
137
179
|
|
138
180
|
```ruby
|
181
|
+
calculator = Keisan::Calculator.new
|
139
182
|
calculator.evaluate("[2, 3, 5, 8]")
|
140
183
|
#=> [2, 3, 5, 8]
|
141
184
|
calculator.evaluate("[[1,2,3],[4,5,6],[7,8,9]][1][2]")
|
@@ -145,15 +188,55 @@ calculator.evaluate("[[1,2,3],[4,5,6],[7,8,9]][1][2]")
|
|
145
188
|
They can also be concatenated using the `+` operator
|
146
189
|
|
147
190
|
```ruby
|
191
|
+
calculator = Keisan::Calculator.new
|
148
192
|
calculator.evaluate("[3, 5] + [x, x+1]", x: 10)
|
149
193
|
#=> [3, 5, 10, 11]
|
150
194
|
```
|
151
195
|
|
196
|
+
Keisan also supports the following useful list methods,
|
197
|
+
|
198
|
+
```ruby
|
199
|
+
calculator = Keisan::Calculator.new
|
200
|
+
calculator.evaluate("[1,3,5].size")
|
201
|
+
#=> 3
|
202
|
+
calculator.evaluate("[1,3,5].max")
|
203
|
+
#=> 5
|
204
|
+
calculator.evaluate("[1,3,5].min")
|
205
|
+
#=> 1
|
206
|
+
calculator.evaluate("[1,3,5].reverse")
|
207
|
+
#=> [5,3,1]
|
208
|
+
calculator.evaluate("[[1,2],[3,4]].flatten")
|
209
|
+
#=> [1,2,3,4]
|
210
|
+
calculator.evaluate("range(5)")
|
211
|
+
#=> [0,1,2,3,4]
|
212
|
+
calculator.evaluate("range(5,10)")
|
213
|
+
#=> [5,6,7,8,9]
|
214
|
+
calculator.evaluate("range(0,10,2)")
|
215
|
+
#=> [0,2,4,6,8]
|
216
|
+
```
|
217
|
+
|
218
|
+
Keisan also supports the basic functional programming operators `map` (or `collect`), `filter` (or `select`), and `reduce` (or `inject`).
|
219
|
+
|
220
|
+
```ruby
|
221
|
+
calculator = Keisan::Calculator.new
|
222
|
+
calculator.evaluate("map([1,3,5], x, 2*x)")
|
223
|
+
#=> [2,6,10]
|
224
|
+
calculator.simplify("[1,3,5].map(x, y*x**2)").to_s
|
225
|
+
#=> "[y,9*y,25*y]"
|
226
|
+
calculator.evaluate("select([1,2,3,4], x, x % 2 == 0)")
|
227
|
+
#=> [2,4]
|
228
|
+
calculator.evaluate("[-2,-1,0,1,2].filter(x, x > 0)")
|
229
|
+
#=> [1,2]
|
230
|
+
calculator.evaluate("[1,2,3,4,5].inject(1, total, x, total*x)")
|
231
|
+
#=> 120
|
232
|
+
```
|
233
|
+
|
152
234
|
##### Logical operations
|
153
235
|
|
154
236
|
`keisan` understands basic boolean logic operators, like `<`, `<=`, `>`, `>=`, `&&`, `||`, `!`, so calculations like the following are possible
|
155
237
|
|
156
238
|
```ruby
|
239
|
+
calculator = Keisan::Calculator.new
|
157
240
|
calculator.evaluate("1 > 0")
|
158
241
|
#=> true
|
159
242
|
calculator.evaluate("!!!true")
|
@@ -165,6 +248,7 @@ calculator.evaluate("x >= 0 && x < 10", x: 5)
|
|
165
248
|
There is also a useful ternary `if` function defined
|
166
249
|
|
167
250
|
```ruby
|
251
|
+
calculator = Keisan::Calculator.new
|
168
252
|
calculator.evaluate("2 + if(1 > 0, 10, 29)")
|
169
253
|
#=> 12
|
170
254
|
```
|
@@ -174,6 +258,7 @@ calculator.evaluate("2 + if(1 > 0, 10, 29)")
|
|
174
258
|
The basic bitwise operations, NOT `~`, OR `|`, XOR `^`, and AND `&` are also available for use
|
175
259
|
|
176
260
|
```ruby
|
261
|
+
calculator = Keisan::Calculator.new
|
177
262
|
calculator.evaluate("2 + 12 & 7")
|
178
263
|
#=> 6
|
179
264
|
```
|
@@ -183,6 +268,7 @@ calculator.evaluate("2 + 12 & 7")
|
|
183
268
|
`keisan` also can parse in strings, and access the characters by index
|
184
269
|
|
185
270
|
```ruby
|
271
|
+
calculator = Keisan::Calculator.new
|
186
272
|
calculator.evaluate("'hello'[1]")
|
187
273
|
#=> "e"
|
188
274
|
```
|
@@ -192,6 +278,7 @@ calculator.evaluate("'hello'[1]")
|
|
192
278
|
Using the prefixes `0b`, `0o`, and `0x` (standard in Ruby) indicates binary, octal, and hexadecimal numbers respectively.
|
193
279
|
|
194
280
|
```ruby
|
281
|
+
calculator = Keisan::Calculator.new
|
195
282
|
calculator.evaluate("0b1100")
|
196
283
|
#=> 12
|
197
284
|
calculator.evaluate("0o775")
|
@@ -205,10 +292,11 @@ calculator.evaluate("0x1f0")
|
|
205
292
|
`keisan` has a couple methods for doing random operations, `rand` and `sample`. For example,
|
206
293
|
|
207
294
|
```ruby
|
208
|
-
calculator.
|
209
|
-
|
210
|
-
|
211
|
-
|
295
|
+
calculator = Keisan::Calculator.new
|
296
|
+
(0...10).include? calculator.evaluate("rand(10)")
|
297
|
+
#=> true
|
298
|
+
[2,4,6,8].include? calculator.evaluate("sample([2, 4, 6, 8])")
|
299
|
+
#=> true
|
212
300
|
```
|
213
301
|
|
214
302
|
If you want reproducibility, you can pass in your own `Random` object to the calculator's context.
|
@@ -227,6 +315,7 @@ calculator2 = Keisan::Calculator.new(context: Keisan::Context.new(random: Random
|
|
227
315
|
`keisan` includes all standard methods given by the Ruby `Math` class.
|
228
316
|
|
229
317
|
```ruby
|
318
|
+
calculator = Keisan::Calculator.new
|
230
319
|
calculator.evaluate("log10(1000)")
|
231
320
|
#=> 3.0
|
232
321
|
```
|
@@ -234,6 +323,7 @@ calculator.evaluate("log10(1000)")
|
|
234
323
|
Furthermore, the following builtin constants are defined
|
235
324
|
|
236
325
|
```ruby
|
326
|
+
calculator = Keisan::Calculator.new
|
237
327
|
calculator.evaluate("PI")
|
238
328
|
#=> 3.141592653589793
|
239
329
|
calculator.evaluate("E")
|
@@ -245,13 +335,15 @@ calculator.evaluate("I")
|
|
245
335
|
This allows for simple calculations like
|
246
336
|
|
247
337
|
```ruby
|
338
|
+
calculator = Keisan::Calculator.new
|
248
339
|
calculator.evaluate("E**(I*PI)+1")
|
249
|
-
|
340
|
+
#=> (0.0+0.0i)
|
250
341
|
```
|
251
342
|
|
252
343
|
There is a `replace` method that can replace instances of a variable in an expression with another expression. The form is `replace(original_expression, variable_to_replace, replacement_expression)`. Before the replacement is carried out, the `original_expression` and `replacement_expression` are `evaluate`d, then instances in the original expression of the given variable are replaced by the replacement expression.
|
253
344
|
|
254
345
|
```ruby
|
346
|
+
calculator = Keisan::Calculator.new
|
255
347
|
calculator.evaluate("replace(x**2, x, 3)")
|
256
348
|
#=> 9
|
257
349
|
```
|
@@ -269,17 +361,29 @@ The derivative operation is also builtin to Keisan as the `diff` function.
|
|
269
361
|
```ruby
|
270
362
|
calculator = Keisan::Calculator.new
|
271
363
|
calculator.evaluate("diff(4*x, x)")
|
364
|
+
#=> 4
|
272
365
|
calculator.evaluate("replace(diff(4*x**2, x), x, 3)")
|
273
366
|
#=> 24
|
274
367
|
```
|
275
368
|
|
369
|
+
This also works intelligently with user defined functions.
|
370
|
+
|
371
|
+
```ruby
|
372
|
+
calculator = Keisan::Calculator.new
|
373
|
+
calculator.evaluate("f(x, y) = x**2 + y")
|
374
|
+
calculator.simplify("diff(f(2*t, t+1), t)").to_s
|
375
|
+
#=> "1+(8*t)"
|
376
|
+
calculator.evaluate("replace(diff(f(2*t, t+1), t), t, 3)")
|
377
|
+
#=> 1+8*3
|
378
|
+
```
|
379
|
+
|
276
380
|
### Adding custom variables and functions
|
277
381
|
|
278
382
|
The `Keisan::Calculator` class has a single `Keisan::Context` object in its `context` attribute. This class is used to store local variables and functions. These can be stored using either the `define_variable!` or `define_function!` methods, or by using the assignment operator `=` in an expression that is evaluated. As an example of pre-defining some variables and functions, see the following
|
279
383
|
|
280
384
|
```ruby
|
385
|
+
calculator = Keisan::Calculator.new
|
281
386
|
calculator.define_variable!("x", 5)
|
282
|
-
#=> 5
|
283
387
|
calculator.evaluate("x + 1")
|
284
388
|
#=> 6
|
285
389
|
calculator.evaluate("x + 1", x: 10)
|
data/bin/keisan
ADDED
data/lib/keisan.rb
CHANGED
@@ -58,7 +58,6 @@ require "keisan/functions/default_registry"
|
|
58
58
|
require "keisan/variables/registry"
|
59
59
|
require "keisan/variables/default_registry"
|
60
60
|
require "keisan/context"
|
61
|
-
require "keisan/function_definition_context"
|
62
61
|
|
63
62
|
require "keisan/token"
|
64
63
|
require "keisan/tokens/comma"
|
data/lib/keisan/ast.rb
CHANGED
@@ -10,36 +10,60 @@ module KeisanNumeric
|
|
10
10
|
def to_node
|
11
11
|
Keisan::AST::Number.new(self)
|
12
12
|
end
|
13
|
+
|
14
|
+
def value(context = nil)
|
15
|
+
self
|
16
|
+
end
|
13
17
|
end
|
14
18
|
|
15
19
|
module KeisanString
|
16
20
|
def to_node
|
17
21
|
Keisan::AST::String.new(self)
|
18
22
|
end
|
23
|
+
|
24
|
+
def value(context = nil)
|
25
|
+
self
|
26
|
+
end
|
19
27
|
end
|
20
28
|
|
21
29
|
module KeisanTrueClass
|
22
30
|
def to_node
|
23
31
|
Keisan::AST::Boolean.new(true)
|
24
32
|
end
|
33
|
+
|
34
|
+
def value(context = nil)
|
35
|
+
self
|
36
|
+
end
|
25
37
|
end
|
26
38
|
|
27
39
|
module KeisanFalseClass
|
28
40
|
def to_node
|
29
41
|
Keisan::AST::Boolean.new(false)
|
30
42
|
end
|
43
|
+
|
44
|
+
def value(context = nil)
|
45
|
+
self
|
46
|
+
end
|
31
47
|
end
|
32
48
|
|
33
49
|
module KeisanNilClass
|
34
50
|
def to_node
|
35
51
|
Keisan::AST::Null.new
|
36
52
|
end
|
53
|
+
|
54
|
+
def value(context = nil)
|
55
|
+
self
|
56
|
+
end
|
37
57
|
end
|
38
58
|
|
39
59
|
module KeisanArray
|
40
60
|
def to_node
|
41
61
|
Keisan::AST::List.new(map {|n| n.to_node})
|
42
62
|
end
|
63
|
+
|
64
|
+
def value(context = nil)
|
65
|
+
self
|
66
|
+
end
|
43
67
|
end
|
44
68
|
|
45
69
|
class Numeric; prepend KeisanNumeric; end
|
@@ -11,16 +11,45 @@ module Keisan
|
|
11
11
|
lhs = children.first
|
12
12
|
rhs = children.last
|
13
13
|
|
14
|
-
|
15
|
-
when Keisan::AST::Variable
|
14
|
+
if is_variable_definition?
|
16
15
|
evaluate_variable(context, lhs, rhs)
|
17
|
-
|
16
|
+
elsif is_function_definition?
|
18
17
|
evaluate_function(context, lhs, rhs)
|
19
18
|
else
|
20
19
|
raise Keisan::Exceptions::InvalidExpression.new("Unhandled left hand side #{lhs} in assignment")
|
21
20
|
end
|
22
21
|
end
|
23
22
|
|
23
|
+
def simplify(context = nil)
|
24
|
+
evaluate(context)
|
25
|
+
end
|
26
|
+
|
27
|
+
def unbound_variables(context = nil)
|
28
|
+
variables = super(context)
|
29
|
+
if is_variable_definition?
|
30
|
+
variables.delete(children.first.name)
|
31
|
+
else
|
32
|
+
variables
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def unbound_functions(context = nil)
|
37
|
+
functions = super(context)
|
38
|
+
if is_function_definition?
|
39
|
+
functions.delete(children.first.name)
|
40
|
+
else
|
41
|
+
functions
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def is_variable_definition?
|
46
|
+
children.first.is_a?(Keisan::AST::Variable)
|
47
|
+
end
|
48
|
+
|
49
|
+
def is_function_definition?
|
50
|
+
children.first.is_a?(Keisan::AST::Function)
|
51
|
+
end
|
52
|
+
|
24
53
|
private
|
25
54
|
|
26
55
|
def evaluate_variable(context, lhs, rhs)
|
@@ -32,6 +61,7 @@ module Keisan
|
|
32
61
|
|
33
62
|
rhs_value = rhs.value(context)
|
34
63
|
context.register_variable!(lhs.name, rhs_value)
|
64
|
+
# Return the variable assigned value
|
35
65
|
rhs
|
36
66
|
end
|
37
67
|
|
@@ -41,11 +71,7 @@ module Keisan
|
|
41
71
|
end
|
42
72
|
|
43
73
|
argument_names = lhs.children.map(&:name)
|
44
|
-
function_definition_context =
|
45
|
-
parent: context,
|
46
|
-
arguments: argument_names
|
47
|
-
)
|
48
|
-
rhs = rhs.evaluate(function_definition_context)
|
74
|
+
function_definition_context = context.spawn_child(shadowed: argument_names, transient: true)
|
49
75
|
|
50
76
|
unless rhs.unbound_variables(context) <= Set.new(argument_names)
|
51
77
|
raise Keisan::Exceptions::InvalidExpression.new("Unbound variables found in function definition")
|
@@ -60,8 +86,8 @@ module Keisan
|
|
60
86
|
Keisan::Functions::ExpressionFunction.new(
|
61
87
|
lhs.name,
|
62
88
|
argument_names,
|
63
|
-
rhs,
|
64
|
-
|
89
|
+
rhs.simplify(function_definition_context),
|
90
|
+
context.transient_definitions
|
65
91
|
)
|
66
92
|
)
|
67
93
|
|