keisan 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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