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
data/lib/keisan/ast/builder.rb
CHANGED
@@ -22,7 +22,13 @@ module Keisan
|
|
22
22
|
|
23
23
|
consume_operators!
|
24
24
|
|
25
|
-
|
25
|
+
case @nodes.count
|
26
|
+
when 0
|
27
|
+
# Empty string, set to just Null
|
28
|
+
@nodes = [Keisan::AST::Null.new]
|
29
|
+
when 1
|
30
|
+
# Good
|
31
|
+
else
|
26
32
|
raise Keisan::Exceptions::ASTError.new("Should end up with a single node")
|
27
33
|
end
|
28
34
|
end
|
data/lib/keisan/ast/exponent.rb
CHANGED
@@ -58,7 +58,7 @@ module Keisan
|
|
58
58
|
|
59
59
|
# Special simple case where exponent is a pure number
|
60
60
|
if exponent.is_a?(AST::Number)
|
61
|
-
return (exponent * base ** (exponent -1)).simplify(context)
|
61
|
+
return (exponent * base.differentiate(variable, context) * base ** (exponent -1)).simplify(context)
|
62
62
|
end
|
63
63
|
|
64
64
|
base_diff = base.differentiate(variable, context)
|
data/lib/keisan/ast/function.rb
CHANGED
@@ -47,7 +47,7 @@ module Keisan
|
|
47
47
|
if function_defined?(context)
|
48
48
|
function_from_context(context).evaluate(self, context)
|
49
49
|
else
|
50
|
-
@children = children.map {|child| child.evaluate(context)}
|
50
|
+
@children = children.map {|child| child.evaluate(context).to_node}
|
51
51
|
self
|
52
52
|
end
|
53
53
|
end
|
@@ -58,7 +58,7 @@ module Keisan
|
|
58
58
|
if function_defined?(context)
|
59
59
|
function_from_context(context).simplify(self, context)
|
60
60
|
else
|
61
|
-
@children = children.map {|child| child.simplify(context)}
|
61
|
+
@children = children.map {|child| child.simplify(context).to_node}
|
62
62
|
self
|
63
63
|
end
|
64
64
|
end
|
@@ -68,10 +68,14 @@ module Keisan
|
|
68
68
|
end
|
69
69
|
|
70
70
|
def differentiate(variable, context = nil)
|
71
|
+
function = function_from_context(context)
|
72
|
+
function.differentiate(self, variable, context)
|
73
|
+
|
74
|
+
rescue Keisan::Exceptions::UndefinedFunctionError, Keisan::Exceptions::NotImplementedError
|
71
75
|
unless unbound_variables(context).include?(variable.name)
|
72
76
|
return AST::Number.new(0)
|
73
77
|
end
|
74
|
-
|
78
|
+
|
75
79
|
AST::Function.new([self, variable], "diff")
|
76
80
|
end
|
77
81
|
end
|
data/lib/keisan/ast/list.rb
CHANGED
data/lib/keisan/ast/node.rb
CHANGED
data/lib/keisan/ast/parent.rb
CHANGED
@@ -4,7 +4,7 @@ module Keisan
|
|
4
4
|
attr_reader :children
|
5
5
|
|
6
6
|
def initialize(children = [])
|
7
|
-
children = Array.wrap(children)
|
7
|
+
children = Array.wrap(children).map(&:to_node)
|
8
8
|
unless children.is_a?(Array) && children.all? {|children| children.is_a?(Node)}
|
9
9
|
raise Keisan::Exceptions::InternalError.new
|
10
10
|
end
|
data/lib/keisan/ast/plus.rb
CHANGED
@@ -24,7 +24,7 @@ module Keisan
|
|
24
24
|
children_values.inject([], &:+)
|
25
25
|
else
|
26
26
|
children_values.inject(0, &:+)
|
27
|
-
end
|
27
|
+
end.to_node.value(context)
|
28
28
|
end
|
29
29
|
|
30
30
|
def evaluate(context = nil)
|
@@ -46,6 +46,16 @@ module Keisan
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
+
if children.all? {|child| child.is_a?(Keisan::AST::String)}
|
50
|
+
return Keisan::AST::String.new(children.inject("") do |result, child|
|
51
|
+
result + child.value
|
52
|
+
end)
|
53
|
+
elsif children.all? {|child| child.is_a?(Keisan::AST::List)}
|
54
|
+
return Keisan::AST::List.new(children.inject([]) do |result, child|
|
55
|
+
result + child.value
|
56
|
+
end)
|
57
|
+
end
|
58
|
+
|
49
59
|
constants, non_constants = *children.partition {|child| child.is_a?(AST::Number)}
|
50
60
|
constant = constants.inject(AST::Number.new(0), &:+).simplify(context)
|
51
61
|
|
data/lib/keisan/ast/string.rb
CHANGED
@@ -19,6 +19,14 @@ module Keisan
|
|
19
19
|
raise Keisan::Exceptions::TypeError.new("#{other}'s type is invalid, #{other.class}")
|
20
20
|
end
|
21
21
|
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
if value =~ /\"/
|
25
|
+
"\"#{value.gsub("\"", "\\\"")}\""
|
26
|
+
else
|
27
|
+
"\"#{value}\""
|
28
|
+
end
|
29
|
+
end
|
22
30
|
end
|
23
31
|
end
|
24
32
|
end
|
data/lib/keisan/ast/variable.rb
CHANGED
@@ -9,7 +9,7 @@ module Keisan
|
|
9
9
|
|
10
10
|
def value(context = nil)
|
11
11
|
context = Keisan::Context.new if context.nil?
|
12
|
-
context.variable(name)
|
12
|
+
context.variable(name).value(context)
|
13
13
|
end
|
14
14
|
|
15
15
|
def unbound_variables(context = nil)
|
@@ -28,7 +28,13 @@ module Keisan
|
|
28
28
|
def evaluate(context = nil)
|
29
29
|
context ||= Keisan::Context.new
|
30
30
|
if context.has_variable?(name)
|
31
|
-
context.variable(name)
|
31
|
+
variable = context.variable(name)
|
32
|
+
# The variable might just be a variable, i.e. probably in function definition
|
33
|
+
if variable.is_a?(AST::Node)
|
34
|
+
variable.is_a?(AST::Variable) ? variable : variable.evaluate(context)
|
35
|
+
else
|
36
|
+
variable
|
37
|
+
end
|
32
38
|
else
|
33
39
|
self
|
34
40
|
end
|
data/lib/keisan/calculator.rb
CHANGED
@@ -6,10 +6,26 @@ module Keisan
|
|
6
6
|
@context = context || Context.new(allow_recursive: allow_recursive)
|
7
7
|
end
|
8
8
|
|
9
|
+
def allow_recursive
|
10
|
+
context.allow_recursive
|
11
|
+
end
|
12
|
+
|
13
|
+
def allow_recursive!
|
14
|
+
context.allow_recursive!
|
15
|
+
end
|
16
|
+
|
9
17
|
def evaluate(expression, definitions = {})
|
10
18
|
Evaluator.new(self).evaluate(expression, definitions)
|
11
19
|
end
|
12
20
|
|
21
|
+
def simplify(expression, definitions = {})
|
22
|
+
Evaluator.new(self).simplify(expression, definitions)
|
23
|
+
end
|
24
|
+
|
25
|
+
def ast(expression)
|
26
|
+
Evaluator.new(self).ast(expression)
|
27
|
+
end
|
28
|
+
|
13
29
|
def define_variable!(name, value)
|
14
30
|
context.register_variable!(name, value)
|
15
31
|
end
|
data/lib/keisan/context.rb
CHANGED
@@ -2,30 +2,57 @@ module Keisan
|
|
2
2
|
class Context
|
3
3
|
attr_reader :function_registry, :variable_registry, :allow_recursive
|
4
4
|
|
5
|
-
def initialize(parent: nil, random: nil, allow_recursive: false)
|
5
|
+
def initialize(parent: nil, random: nil, allow_recursive: false, shadowed: [])
|
6
6
|
@parent = parent
|
7
7
|
@function_registry = Functions::Registry.new(parent: @parent.try(:function_registry))
|
8
|
-
@variable_registry = Variables::Registry.new(parent: @parent.try(:variable_registry))
|
8
|
+
@variable_registry = Variables::Registry.new(parent: @parent.try(:variable_registry), shadowed: shadowed)
|
9
9
|
@random = random
|
10
10
|
@allow_recursive = allow_recursive
|
11
11
|
end
|
12
12
|
|
13
|
-
def
|
14
|
-
|
13
|
+
def allow_recursive!
|
14
|
+
@allow_recursive = true
|
15
|
+
end
|
16
|
+
|
17
|
+
# A transient context does not persist variables and functions in this context, but
|
18
|
+
# rather store them one level higher in the parent context. When evaluating a string,
|
19
|
+
# the entire operation is done in a transient context that is unique from the calculators
|
20
|
+
# current context, but such that variable/function definitions can be persisted in
|
21
|
+
# the calculator.
|
22
|
+
def spawn_child(definitions: {}, shadowed: [], transient: nil)
|
23
|
+
child = pure_child(shadowed: shadowed)
|
15
24
|
|
16
25
|
definitions.each do |name, value|
|
17
26
|
case value
|
18
27
|
when Proc
|
19
28
|
child.register_function!(name, value)
|
29
|
+
when Keisan::Functions::ProcFunction
|
30
|
+
child.register_function!(name, value.function_proc)
|
20
31
|
else
|
21
32
|
child.register_variable!(name, value)
|
22
33
|
end
|
23
34
|
end
|
24
35
|
|
25
|
-
|
36
|
+
if transient.nil? && self.transient? || transient == true
|
37
|
+
child.set_transient!
|
38
|
+
end
|
26
39
|
child
|
27
40
|
end
|
28
41
|
|
42
|
+
def transient_definitions
|
43
|
+
return {} unless @transient
|
44
|
+
parent_definitions = @parent.present? ? @parent.transient_definitions : {}
|
45
|
+
parent_definitions.merge(
|
46
|
+
@variable_registry.locals
|
47
|
+
).merge(
|
48
|
+
@function_registry.locals
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
def transient?
|
53
|
+
!!@transient
|
54
|
+
end
|
55
|
+
|
29
56
|
def variable(name)
|
30
57
|
@variable_registry[name.to_s]
|
31
58
|
end
|
@@ -35,10 +62,10 @@ module Keisan
|
|
35
62
|
end
|
36
63
|
|
37
64
|
def register_variable!(name, value)
|
38
|
-
if
|
65
|
+
if !@variable_registry.shadowed.member?(name) && transient?
|
39
66
|
@parent.register_variable!(name, value)
|
40
67
|
else
|
41
|
-
@variable_registry.register!(name
|
68
|
+
@variable_registry.register!(name, value)
|
42
69
|
end
|
43
70
|
end
|
44
71
|
|
@@ -51,7 +78,7 @@ module Keisan
|
|
51
78
|
end
|
52
79
|
|
53
80
|
def register_function!(name, function)
|
54
|
-
if
|
81
|
+
if transient?
|
55
82
|
@parent.register_function!(name, function)
|
56
83
|
else
|
57
84
|
@function_registry.register!(name.to_s, function)
|
@@ -67,5 +94,9 @@ module Keisan
|
|
67
94
|
def set_transient!
|
68
95
|
@transient = true
|
69
96
|
end
|
97
|
+
|
98
|
+
def pure_child(shadowed: [])
|
99
|
+
self.class.new(parent: self, shadowed: shadowed, allow_recursive: allow_recursive)
|
100
|
+
end
|
70
101
|
end
|
71
102
|
end
|
data/lib/keisan/evaluator.rb
CHANGED
@@ -14,11 +14,21 @@ module Keisan
|
|
14
14
|
case ast
|
15
15
|
when Keisan::AST::Assignment
|
16
16
|
if ast.children.first.is_a?(Keisan::AST::Variable)
|
17
|
-
context.variable(ast.children.first.name)
|
17
|
+
context.variable(ast.children.first.name).value(context)
|
18
18
|
end
|
19
19
|
else
|
20
20
|
evaluation.value(context)
|
21
21
|
end
|
22
22
|
end
|
23
|
+
|
24
|
+
def simplify(expression, definitions = {})
|
25
|
+
context = calculator.context.spawn_child(definitions: definitions, transient: true)
|
26
|
+
ast = Keisan::AST.parse(expression)
|
27
|
+
ast.simplify(context)
|
28
|
+
end
|
29
|
+
|
30
|
+
def ast(expression)
|
31
|
+
Keisan::AST.parse(expression)
|
32
|
+
end
|
23
33
|
end
|
24
34
|
end
|
data/lib/keisan/function.rb
CHANGED
@@ -0,0 +1,15 @@
|
|
1
|
+
module Keisan
|
2
|
+
module Functions
|
3
|
+
class Cot < CMathFunction
|
4
|
+
def initialize
|
5
|
+
super("cot", Proc.new {|arg| 1 / CMath::tan(arg)})
|
6
|
+
end
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
def self.derivative(argument)
|
11
|
+
-Keisan::AST::Exponent.new([Keisan::AST::Function.new([argument], "sin"), -2])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Keisan
|
2
|
+
module Functions
|
3
|
+
class Coth < CMathFunction
|
4
|
+
def initialize
|
5
|
+
super("coth", Proc.new {|arg| 1 / CMath::tanh(arg)})
|
6
|
+
end
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
def self.derivative(argument)
|
11
|
+
-Keisan::AST::Exponent.new([Keisan::AST::Function.new([argument], "sinh"), -2])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Keisan
|
2
|
+
module Functions
|
3
|
+
class Csc < CMathFunction
|
4
|
+
def initialize
|
5
|
+
super("csc", Proc.new {|arg| 1 / CMath::sin(arg)})
|
6
|
+
end
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
def self.derivative(argument)
|
11
|
+
-Keisan::AST::Function.new([argument], "cos") * Keisan::AST::Exponent.new([Keisan::AST::Function.new([argument], "sin"), -2])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|