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
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
|