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.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -5
  3. data/README.md +112 -8
  4. data/bin/keisan +7 -0
  5. data/lib/keisan.rb +0 -1
  6. data/lib/keisan/ast.rb +24 -0
  7. data/lib/keisan/ast/assignment.rb +36 -10
  8. data/lib/keisan/ast/builder.rb +7 -1
  9. data/lib/keisan/ast/constant_literal.rb +6 -1
  10. data/lib/keisan/ast/exponent.rb +1 -1
  11. data/lib/keisan/ast/function.rb +7 -3
  12. data/lib/keisan/ast/list.rb +1 -1
  13. data/lib/keisan/ast/node.rb +1 -1
  14. data/lib/keisan/ast/parent.rb +1 -1
  15. data/lib/keisan/ast/plus.rb +11 -1
  16. data/lib/keisan/ast/string.rb +8 -0
  17. data/lib/keisan/ast/unary_operator.rb +1 -1
  18. data/lib/keisan/ast/variable.rb +8 -2
  19. data/lib/keisan/calculator.rb +16 -0
  20. data/lib/keisan/context.rb +39 -8
  21. data/lib/keisan/evaluator.rb +11 -1
  22. data/lib/keisan/function.rb +4 -0
  23. data/lib/keisan/functions/abs.rb +9 -0
  24. data/lib/keisan/functions/cbrt.rb +15 -0
  25. data/lib/keisan/functions/cmath_function.rb +11 -0
  26. data/lib/keisan/functions/cos.rb +15 -0
  27. data/lib/keisan/functions/cosh.rb +15 -0
  28. data/lib/keisan/functions/cot.rb +15 -0
  29. data/lib/keisan/functions/coth.rb +15 -0
  30. data/lib/keisan/functions/csc.rb +15 -0
  31. data/lib/keisan/functions/csch.rb +15 -0
  32. data/lib/keisan/functions/default_registry.rb +95 -1
  33. data/lib/keisan/functions/diff.rb +36 -14
  34. data/lib/keisan/functions/exp.rb +15 -0
  35. data/lib/keisan/functions/expression_function.rb +65 -15
  36. data/lib/keisan/functions/filter.rb +64 -0
  37. data/lib/keisan/functions/if.rb +15 -12
  38. data/lib/keisan/functions/imag.rb +9 -0
  39. data/lib/keisan/functions/log.rb +15 -0
  40. data/lib/keisan/functions/map.rb +57 -0
  41. data/lib/keisan/functions/math_function.rb +34 -0
  42. data/lib/keisan/functions/proc_function.rb +5 -3
  43. data/lib/keisan/functions/real.rb +9 -0
  44. data/lib/keisan/functions/reduce.rb +65 -0
  45. data/lib/keisan/functions/registry.rb +5 -1
  46. data/lib/keisan/functions/sec.rb +15 -0
  47. data/lib/keisan/functions/sech.rb +15 -0
  48. data/lib/keisan/functions/sin.rb +15 -0
  49. data/lib/keisan/functions/sinh.rb +15 -0
  50. data/lib/keisan/functions/sqrt.rb +15 -0
  51. data/lib/keisan/functions/tan.rb +15 -0
  52. data/lib/keisan/functions/tanh.rb +15 -0
  53. data/lib/keisan/repl.rb +105 -0
  54. data/lib/keisan/tokenizer.rb +5 -2
  55. data/lib/keisan/tokens/string.rb +1 -1
  56. data/lib/keisan/variables/registry.rb +14 -3
  57. data/lib/keisan/version.rb +1 -1
  58. data/screenshots/repl.png +0 -0
  59. metadata +30 -6
  60. data/lib/keisan/ast/functions/diff.rb +0 -57
  61. data/lib/keisan/ast/functions/if.rb +0 -47
  62. data/lib/keisan/function_definition_context.rb +0 -34
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 32b53dd19dc29bced6d98d110f7d11c0a7d22ea5
4
- data.tar.gz: d55ec179a6f8b1e597cb322e42bab4ed0b959fed
3
+ metadata.gz: 1e9c5a4aead65a954cbf6f2dc06d4ccb579e0edf
4
+ data.tar.gz: 2bbd7406b68ac4bd56f589fab026c303cf474edb
5
5
  SHA512:
6
- metadata.gz: a4c87fabd551121df563306488410003e9291b1569330ca3da65d833b9e9e97f925dcbc3d07e23cb9c5e05272b8fe74028766388064e0713ff66d625261a5752
7
- data.tar.gz: cdfef6449002d021fe83b10457959e5d1bccf81aa569be908e6ba98026f0adc2df44eeb3a60b6d013126e6d278992cd8a50b4c397c11460656a33574f0234ad7
6
+ metadata.gz: f71593bb29b2f3c088eb1f2ea759c80cdf9afc8a93f2f6f4ed30e9b706a3e528d07f57930d0397c1f4e8c27a927d058fc2fbb9f3961ff2abf3c5a33aac7770fb
7
+ data.tar.gz: a2239cf26bb7d53c650308b2110b6f097801dc90a57bb5b69d1348a4b32902e74f6a2fb275eebcbd64d64f24865685bf2f442288cd976df3359996642a83aaae
@@ -1,8 +1,6 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- - 2.2.6
5
- - 2.3.3
6
- - 2.4.0
7
- - 2.4.1
8
- before_install: gem install bundler -v 1.14.6
4
+ - 2.3.5
5
+ - 2.4.2
6
+ before_install: gem install bundler -v 1.16.0
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
- ```ruby
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.evaluate("rand(10)")
209
- #=> 3
210
- calculator.evaluate("sample([2, 4, 6, 8])")
211
- #=> 8
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
- => (0.0+0.0i)
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)
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "keisan"
5
+ require "keisan/repl"
6
+
7
+ Keisan::Repl.new.start
@@ -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"
@@ -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
- case lhs
15
- when Keisan::AST::Variable
14
+ if is_variable_definition?
16
15
  evaluate_variable(context, lhs, rhs)
17
- when Keisan::AST::Function
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 = Keisan::FunctionDefinitionContext.new(
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
- function_definition_context
89
+ rhs.simplify(function_definition_context),
90
+ context.transient_definitions
65
91
  )
66
92
  )
67
93