keisan 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
![alt text](screenshots/repl.png "Keisan built-in REPL")
|
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
|
|