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
@@ -0,0 +1,15 @@
|
|
1
|
+
module Keisan
|
2
|
+
module Functions
|
3
|
+
class Csch < CMathFunction
|
4
|
+
def initialize
|
5
|
+
super("csch", Proc.new {|arg| 1 / CMath::sinh(arg)})
|
6
|
+
end
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
def self.derivative(argument)
|
11
|
+
-Keisan::AST::Function.new([argument], "cosh") * Keisan::AST::Exponent.new([Keisan::AST::Function.new([argument], "sinh"), -2])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -1,8 +1,32 @@
|
|
1
1
|
require_relative "if"
|
2
2
|
require_relative "diff"
|
3
3
|
require_relative "replace"
|
4
|
+
require_relative "map"
|
5
|
+
require_relative "filter"
|
6
|
+
require_relative "reduce"
|
4
7
|
require_relative "rand"
|
5
8
|
require_relative "sample"
|
9
|
+
require_relative "math_function"
|
10
|
+
require_relative "cmath_function"
|
11
|
+
require_relative "exp"
|
12
|
+
require_relative "log"
|
13
|
+
require_relative "sin"
|
14
|
+
require_relative "cos"
|
15
|
+
require_relative "sec"
|
16
|
+
require_relative "tan"
|
17
|
+
require_relative "cot"
|
18
|
+
require_relative "csc"
|
19
|
+
require_relative "sinh"
|
20
|
+
require_relative "cosh"
|
21
|
+
require_relative "sech"
|
22
|
+
require_relative "tanh"
|
23
|
+
require_relative "coth"
|
24
|
+
require_relative "csch"
|
25
|
+
require_relative "sqrt"
|
26
|
+
require_relative "cbrt"
|
27
|
+
require_relative "abs"
|
28
|
+
require_relative "real"
|
29
|
+
require_relative "imag"
|
6
30
|
|
7
31
|
module Keisan
|
8
32
|
module Functions
|
@@ -19,10 +43,40 @@ module Keisan
|
|
19
43
|
registry.register!(:if, Keisan::Functions::If.new, force: true)
|
20
44
|
registry.register!(:diff, Keisan::Functions::Diff.new, force: true)
|
21
45
|
registry.register!(:replace, Keisan::Functions::Replace.new, force: true)
|
46
|
+
registry.register!(:map, Keisan::Functions::Map.new, force: true)
|
47
|
+
registry.register!(:collect, Keisan::Functions::Map.new, force: true)
|
48
|
+
registry.register!(:filter, Keisan::Functions::Filter.new, force: true)
|
49
|
+
registry.register!(:select, Keisan::Functions::Filter.new, force: true)
|
50
|
+
registry.register!(:reduce, Keisan::Functions::Reduce.new, force: true)
|
51
|
+
registry.register!(:inject, Keisan::Functions::Reduce.new, force: true)
|
22
52
|
|
23
53
|
register_builtin_math!(registry)
|
24
54
|
register_array_methods!(registry)
|
25
55
|
register_random_methods!(registry)
|
56
|
+
|
57
|
+
registry.register!(:exp, Keisan::Functions::Exp.new, force: true)
|
58
|
+
registry.register!(:log, Keisan::Functions::Log.new, force: true)
|
59
|
+
|
60
|
+
registry.register!(:sin, Keisan::Functions::Sin.new, force: true)
|
61
|
+
registry.register!(:cos, Keisan::Functions::Cos.new, force: true)
|
62
|
+
registry.register!(:tan, Keisan::Functions::Tan.new, force: true)
|
63
|
+
registry.register!(:cot, Keisan::Functions::Cot.new, force: true)
|
64
|
+
registry.register!(:sec, Keisan::Functions::Sec.new, force: true)
|
65
|
+
registry.register!(:csc, Keisan::Functions::Csc.new, force: true)
|
66
|
+
|
67
|
+
registry.register!(:sinh, Keisan::Functions::Sinh.new, force: true)
|
68
|
+
registry.register!(:cosh, Keisan::Functions::Cosh.new, force: true)
|
69
|
+
registry.register!(:tanh, Keisan::Functions::Tanh.new, force: true)
|
70
|
+
registry.register!(:coth, Keisan::Functions::Coth.new, force: true)
|
71
|
+
registry.register!(:sech, Keisan::Functions::Sech.new, force: true)
|
72
|
+
registry.register!(:csch, Keisan::Functions::Csch.new, force: true)
|
73
|
+
|
74
|
+
registry.register!(:sqrt, Keisan::Functions::Sqrt.new, force: true)
|
75
|
+
registry.register!(:cbrt, Keisan::Functions::Cbrt.new, force: true)
|
76
|
+
|
77
|
+
registry.register!(:abs, Keisan::Functions::Abs.new, force: true)
|
78
|
+
registry.register!(:real, Keisan::Functions::Real.new, force: true)
|
79
|
+
registry.register!(:imag, Keisan::Functions::Imag.new, force: true)
|
26
80
|
end
|
27
81
|
|
28
82
|
def self.register_builtin_math!(registry)
|
@@ -30,6 +84,7 @@ module Keisan
|
|
30
84
|
registry.register!(
|
31
85
|
method,
|
32
86
|
Proc.new {|*args|
|
87
|
+
args = args.map(&:value)
|
33
88
|
Math.send(method, *args)
|
34
89
|
},
|
35
90
|
force: true
|
@@ -38,9 +93,48 @@ module Keisan
|
|
38
93
|
end
|
39
94
|
|
40
95
|
def self.register_array_methods!(registry)
|
41
|
-
%i(min max size).each do |method|
|
96
|
+
%i(min max size flatten reverse).each do |method|
|
42
97
|
registry.register!(method, Proc.new {|a| a.send(method)}, force: true)
|
43
98
|
end
|
99
|
+
|
100
|
+
# range(10) => Integers from 0 inclusive to 10 exclusively
|
101
|
+
# range(5, 15) => Integers from 5 inclusive to 15 exclusive
|
102
|
+
# range(10, -1, -2) => Integers from 10 inclusive to -1 exclusive, decreasing by twos
|
103
|
+
# i.e.: [10, 8, 6, 4, 2, 0]
|
104
|
+
registry.register!("range", Proc.new {|*args|
|
105
|
+
case args.count
|
106
|
+
when 1
|
107
|
+
(0...args[0]).to_a
|
108
|
+
when 2
|
109
|
+
(args[0]...args[1]).to_a
|
110
|
+
when 3
|
111
|
+
current = args[0]
|
112
|
+
final = args[1]
|
113
|
+
shift = args[2]
|
114
|
+
|
115
|
+
if shift == 0 or !shift.is_a?(Integer)
|
116
|
+
raise Keisan::Exceptions::InvalidFunctionError.new("range's 3rd argument must be non-zero integer")
|
117
|
+
end
|
118
|
+
|
119
|
+
result = []
|
120
|
+
|
121
|
+
if shift > 0
|
122
|
+
while current < final
|
123
|
+
result << current
|
124
|
+
current += shift
|
125
|
+
end
|
126
|
+
else
|
127
|
+
while current > final
|
128
|
+
result << current
|
129
|
+
current += shift
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
result
|
134
|
+
else
|
135
|
+
raise Keisan::Exceptions::InvalidFunctionError.new("range takes 1 to 3 arguments")
|
136
|
+
end
|
137
|
+
}, force: true)
|
44
138
|
end
|
45
139
|
|
46
140
|
def self.register_random_methods!(registry)
|
@@ -17,29 +17,46 @@ module Keisan
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def evaluate(ast_function, context = nil)
|
20
|
-
context ||= Context.new
|
21
|
-
function,
|
20
|
+
context ||= Keisan::Context.new
|
21
|
+
function, variables = function_and_variables(ast_function)
|
22
|
+
local = context_from(variables, context)
|
22
23
|
|
23
|
-
|
24
|
-
result = differentiate(result, variable,
|
24
|
+
result = variables.inject(function.evaluate(local)) do |result, variable|
|
25
|
+
result = differentiate(result, variable, local)
|
25
26
|
if !is_ast_derivative?(result)
|
26
|
-
result = result.evaluate(
|
27
|
+
result = result.evaluate(local)
|
27
28
|
end
|
28
29
|
result
|
29
30
|
end
|
31
|
+
|
32
|
+
case result
|
33
|
+
when Keisan::AST::Function
|
34
|
+
result.name == "diff" ? result : result.simplify(context)
|
35
|
+
else
|
36
|
+
result.simplify(context)
|
37
|
+
end
|
30
38
|
end
|
31
39
|
|
32
40
|
def simplify(ast_function, context = nil)
|
33
|
-
|
34
|
-
function,
|
41
|
+
raise Keisan::Exceptions::InternalError.new("received non-diff function") unless ast_function.name == "diff"
|
42
|
+
function, variables = function_and_variables(ast_function)
|
43
|
+
context ||= Keisan::Context.new
|
44
|
+
local = context_from(variables, context)
|
35
45
|
|
36
|
-
|
37
|
-
result = differentiate(result, variable,
|
46
|
+
result = variables.inject(function.simplify(local)) do |result, variable|
|
47
|
+
result = differentiate(result, variable, local)
|
38
48
|
if !is_ast_derivative?(result)
|
39
|
-
result = result.simplify(
|
49
|
+
result = result.simplify(local)
|
40
50
|
end
|
41
51
|
result
|
42
52
|
end
|
53
|
+
|
54
|
+
case result
|
55
|
+
when Keisan::AST::Function
|
56
|
+
result.name == "diff" ? result : result.simplify(context)
|
57
|
+
else
|
58
|
+
result.simplify(context)
|
59
|
+
end
|
43
60
|
end
|
44
61
|
|
45
62
|
private
|
@@ -61,22 +78,27 @@ module Keisan
|
|
61
78
|
)
|
62
79
|
end
|
63
80
|
|
64
|
-
def
|
81
|
+
def function_and_variables(ast_function)
|
65
82
|
unless ast_function.is_a?(Keisan::AST::Function) && ast_function.name == name
|
66
83
|
raise Keisan::Exceptions::InvalidFunctionError.new("Must receive diff function")
|
67
84
|
end
|
68
85
|
|
69
|
-
|
86
|
+
variables = ast_function.children[1..-1]
|
70
87
|
|
71
|
-
unless
|
88
|
+
unless variables.all? {|var| var.is_a?(AST::Variable)}
|
72
89
|
raise Keisan::Exceptions::InvalidFunctionError.new("Diff must differentiate with respect to variables")
|
73
90
|
end
|
74
91
|
|
75
92
|
[
|
76
93
|
ast_function.children.first,
|
77
|
-
|
94
|
+
variables
|
78
95
|
]
|
79
96
|
end
|
97
|
+
|
98
|
+
def context_from(variables, context = nil)
|
99
|
+
context ||= Keisan::Context.new(shadowed: variables.map(&:name))
|
100
|
+
context.spawn_child(shadowed: variables.map(&:name))
|
101
|
+
end
|
80
102
|
end
|
81
103
|
end
|
82
104
|
end
|
@@ -3,19 +3,17 @@ module Keisan
|
|
3
3
|
class ExpressionFunction < Keisan::Function
|
4
4
|
attr_reader :arguments, :expression
|
5
5
|
|
6
|
-
def initialize(name, arguments, expression,
|
6
|
+
def initialize(name, arguments, expression, transient_definitions)
|
7
7
|
super(name)
|
8
8
|
@expression = expression.deep_dup
|
9
9
|
@arguments = arguments
|
10
|
-
@
|
10
|
+
@transient_definitions = transient_definitions
|
11
11
|
end
|
12
12
|
|
13
13
|
def call(context, *args)
|
14
|
-
|
15
|
-
raise Keisan::Exceptions::InvalidFunctionError.new("Invalid number of arguments for #{name} function")
|
16
|
-
end
|
14
|
+
verify_argument_size!(args.count)
|
17
15
|
|
18
|
-
local =
|
16
|
+
local = local_context_for(context)
|
19
17
|
arguments.each.with_index do |arg_name, i|
|
20
18
|
local.register_variable!(arg_name, args[i])
|
21
19
|
end
|
@@ -24,28 +22,30 @@ module Keisan
|
|
24
22
|
end
|
25
23
|
|
26
24
|
def value(ast_function, context = nil)
|
25
|
+
verify_argument_size!(ast_function.children.count)
|
26
|
+
|
27
27
|
context ||= Keisan::Context.new
|
28
28
|
argument_values = ast_function.children.map {|child| child.value(context)}
|
29
29
|
call(context, *argument_values)
|
30
30
|
end
|
31
31
|
|
32
32
|
def evaluate(ast_function, context = nil)
|
33
|
+
verify_argument_size!(ast_function.children.count)
|
34
|
+
|
33
35
|
context ||= Keisan::Context.new
|
36
|
+
local = local_context_for(context)
|
34
37
|
|
35
|
-
ast_function.
|
36
|
-
:@children,
|
37
|
-
ast_function.children.map {|child| child.evaluate(context)}
|
38
|
-
)
|
38
|
+
argument_values = ast_function.children.map {|child| child.evaluate(context)}
|
39
39
|
|
40
|
-
|
41
|
-
|
42
|
-
else
|
43
|
-
ast_function
|
40
|
+
arguments.each.with_index do |arg_name, i|
|
41
|
+
local.register_variable!(arg_name, argument_values[i].evaluate(context))
|
44
42
|
end
|
43
|
+
|
44
|
+
expression.evaluate(local)
|
45
45
|
end
|
46
46
|
|
47
47
|
def simplify(ast_function, context = nil)
|
48
|
-
|
48
|
+
verify_argument_size!(ast_function.children.count)
|
49
49
|
|
50
50
|
ast_function.instance_variable_set(
|
51
51
|
:@children,
|
@@ -58,6 +58,56 @@ module Keisan
|
|
58
58
|
ast_function
|
59
59
|
end
|
60
60
|
end
|
61
|
+
|
62
|
+
# Multi-argument functions work as follows:
|
63
|
+
# Given f(x, y), in general we will take the derivative with respect to t,
|
64
|
+
# and x = x(t), y = y(t). For instance d/dt f(2*t, t+1).
|
65
|
+
# In this case, chain rule gives derivative:
|
66
|
+
# dx(t)/dt * f_x(x(t), y(t)) + dy(t)/dt * f_y(x(t), y(t)),
|
67
|
+
# where f_x and f_y are the x and y partial derivatives respectively.
|
68
|
+
def differentiate(ast_function, variable, context = nil)
|
69
|
+
verify_argument_size!(ast_function.children.count)
|
70
|
+
|
71
|
+
local = local_context_for(context)
|
72
|
+
|
73
|
+
# expression.differentiate(variable, context)
|
74
|
+
|
75
|
+
argument_values = ast_function.children.map {|child| child.evaluate(local)}
|
76
|
+
|
77
|
+
argument_derivatives = ast_function.children.map do |child|
|
78
|
+
child.differentiate(variable, context)
|
79
|
+
end
|
80
|
+
|
81
|
+
Keisan::AST::Plus.new(
|
82
|
+
argument_derivatives.map.with_index {|argument_derivative, i|
|
83
|
+
partial_derivative = partial_derivatives[i].replace(argument_variables[i], argument_values[i])
|
84
|
+
Keisan::AST::Times.new([argument_derivative, partial_derivative])
|
85
|
+
}
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def argument_variables
|
92
|
+
@argument_variables ||= arguments.map {|argument| Keisan::AST::Variable.new(argument)}
|
93
|
+
end
|
94
|
+
|
95
|
+
def partial_derivatives
|
96
|
+
@partial_derivatives ||= argument_variables.map.with_index do |variable, i|
|
97
|
+
partial_derivative = expression.differentiate(variable)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def verify_argument_size!(argument_size)
|
102
|
+
unless @arguments.count == argument_size
|
103
|
+
raise Keisan::Exceptions::InvalidFunctionError.new("Invalid number of arguments for #{name} function")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def local_context_for(context = nil)
|
108
|
+
context ||= Keisan::Context.new
|
109
|
+
context.spawn_child(definitions: @transient_definitions, shadowed: @arguments, transient: true)
|
110
|
+
end
|
61
111
|
end
|
62
112
|
end
|
63
113
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Keisan
|
2
|
+
module Functions
|
3
|
+
class Filter < Keisan::Function
|
4
|
+
# Filters (list, variable, expression)
|
5
|
+
# e.g. filter([1,2,3,4], x, x % 2 == 0)
|
6
|
+
# should give [2,4]
|
7
|
+
def initialize
|
8
|
+
@name = "filter"
|
9
|
+
end
|
10
|
+
|
11
|
+
def value(ast_function, context = nil)
|
12
|
+
evaluate(ast_function, context = nil)
|
13
|
+
end
|
14
|
+
|
15
|
+
def evaluate(ast_function, context = nil)
|
16
|
+
context ||= Context.new
|
17
|
+
simplify(ast_function, context).evaluate(context)
|
18
|
+
end
|
19
|
+
|
20
|
+
def simplify(ast_function, context = nil)
|
21
|
+
list, variable, expression = list_variable_expression_for(ast_function, context)
|
22
|
+
|
23
|
+
context ||= Keisan::Context.new
|
24
|
+
local = context.spawn_child(transient: false, shadowed: [variable.name])
|
25
|
+
|
26
|
+
Keisan::AST::List.new(
|
27
|
+
list.children.select do |element|
|
28
|
+
local.register_variable!(variable, element)
|
29
|
+
result = expression.evaluate(local)
|
30
|
+
|
31
|
+
case result
|
32
|
+
when Keisan::AST::Boolean
|
33
|
+
result.value
|
34
|
+
else
|
35
|
+
raise Keisan::Exceptions::InvalidFunctionError.new("Filter requires expression to be a logical expression")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def list_variable_expression_for(ast_function, context)
|
44
|
+
unless ast_function.children.size == 3
|
45
|
+
raise Keisan::Exceptions::InvalidFunctionError.new("Require 3 arguments to filter")
|
46
|
+
end
|
47
|
+
|
48
|
+
list = ast_function.children[0].simplify(context)
|
49
|
+
variable = ast_function.children[1]
|
50
|
+
expression = ast_function.children[2]
|
51
|
+
|
52
|
+
unless list.is_a?(Keisan::AST::List)
|
53
|
+
raise Keisan::Exceptions::InvalidFunctionError.new("First argument to filter must be a list")
|
54
|
+
end
|
55
|
+
|
56
|
+
unless variable.is_a?(Keisan::AST::Variable)
|
57
|
+
raise Keisan::Exceptions::InvalidFunctionError.new("Second argument to filter must be a variable")
|
58
|
+
end
|
59
|
+
|
60
|
+
[list, variable, expression]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/keisan/functions/if.rb
CHANGED
@@ -27,17 +27,8 @@ module Keisan
|
|
27
27
|
bool = ast_function.children[0].evaluate(context)
|
28
28
|
|
29
29
|
if bool.is_a?(Keisan::AST::Boolean)
|
30
|
-
|
31
|
-
|
32
|
-
else
|
33
|
-
ast_function.children[2].to_node.evaluate(context)
|
34
|
-
end
|
35
|
-
else
|
36
|
-
ast_function
|
37
|
-
end
|
38
|
-
|
39
|
-
if ast_function.children.all? {|child| child.well_defined?(context)}
|
40
|
-
value(ast_function, context).to_node.evaluate(context)
|
30
|
+
node = bool.value ? ast_function.children[1] : ast_function.children[2]
|
31
|
+
node.to_node.evaluate(context)
|
41
32
|
else
|
42
33
|
ast_function
|
43
34
|
end
|
@@ -49,7 +40,7 @@ module Keisan
|
|
49
40
|
|
50
41
|
if bool.is_a?(Keisan::AST::Boolean)
|
51
42
|
if bool.value
|
52
|
-
ast_function.children[1].simplify(context)
|
43
|
+
ast_function.children[1].to_node.simplify(context)
|
53
44
|
else
|
54
45
|
ast_function.children[2].to_node.simplify(context)
|
55
46
|
end
|
@@ -57,6 +48,18 @@ module Keisan
|
|
57
48
|
ast_function
|
58
49
|
end
|
59
50
|
end
|
51
|
+
|
52
|
+
def differentiate(ast_function, variable, context = nil)
|
53
|
+
context ||= Context.new
|
54
|
+
Keisan::AST::Function.new(
|
55
|
+
[
|
56
|
+
ast_function.children[0],
|
57
|
+
ast_function.children[1].differentiate(variable, context),
|
58
|
+
ast_function.children[2].differentiate(variable, context)
|
59
|
+
],
|
60
|
+
@name
|
61
|
+
)
|
62
|
+
end
|
60
63
|
end
|
61
64
|
end
|
62
65
|
end
|