keisan 0.5.0 → 0.6.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/README.md +49 -1
- data/keisan.gemspec +1 -0
- data/lib/keisan.rb +30 -0
- data/lib/keisan/ast/assignment.rb +44 -17
- data/lib/keisan/ast/block.rb +60 -0
- data/lib/keisan/ast/boolean.rb +5 -5
- data/lib/keisan/ast/builder.rb +10 -207
- data/lib/keisan/ast/cell.rb +60 -0
- data/lib/keisan/ast/constant_literal.rb +9 -0
- data/lib/keisan/ast/exponent.rb +6 -6
- data/lib/keisan/ast/function.rb +12 -8
- data/lib/keisan/ast/indexing.rb +25 -15
- data/lib/keisan/ast/line_builder.rb +230 -0
- data/lib/keisan/ast/list.rb +28 -1
- data/lib/keisan/ast/literal.rb +0 -8
- data/lib/keisan/ast/logical_and.rb +1 -1
- data/lib/keisan/ast/logical_or.rb +1 -1
- data/lib/keisan/ast/multi_line.rb +28 -0
- data/lib/keisan/ast/node.rb +32 -24
- data/lib/keisan/ast/number.rb +31 -31
- data/lib/keisan/ast/operator.rb +12 -4
- data/lib/keisan/ast/parent.rb +4 -4
- data/lib/keisan/ast/plus.rb +10 -10
- data/lib/keisan/ast/string.rb +3 -3
- data/lib/keisan/ast/times.rb +8 -8
- data/lib/keisan/ast/unary_identity.rb +1 -1
- data/lib/keisan/ast/unary_inverse.rb +7 -7
- data/lib/keisan/ast/unary_minus.rb +5 -5
- data/lib/keisan/ast/unary_operator.rb +2 -2
- data/lib/keisan/ast/unary_plus.rb +2 -2
- data/lib/keisan/ast/variable.rb +26 -10
- data/lib/keisan/context.rb +5 -5
- data/lib/keisan/evaluator.rb +15 -8
- data/lib/keisan/function.rb +24 -6
- data/lib/keisan/functions/cbrt.rb +1 -1
- data/lib/keisan/functions/cos.rb +1 -1
- data/lib/keisan/functions/cosh.rb +1 -1
- data/lib/keisan/functions/cot.rb +1 -1
- data/lib/keisan/functions/coth.rb +1 -1
- data/lib/keisan/functions/csc.rb +1 -1
- data/lib/keisan/functions/csch.rb +1 -1
- data/lib/keisan/functions/default_registry.rb +53 -74
- data/lib/keisan/functions/diff.rb +18 -14
- data/lib/keisan/functions/erf.rb +15 -0
- data/lib/keisan/functions/exp.rb +1 -1
- data/lib/keisan/functions/expression_function.rb +15 -21
- data/lib/keisan/functions/filter.rb +13 -15
- data/lib/keisan/functions/if.rb +14 -20
- data/lib/keisan/functions/let.rb +36 -0
- data/lib/keisan/functions/map.rb +11 -13
- data/lib/keisan/functions/math_function.rb +2 -2
- data/lib/keisan/functions/proc_function.rb +10 -6
- data/lib/keisan/functions/rand.rb +2 -1
- data/lib/keisan/functions/range.rb +74 -0
- data/lib/keisan/functions/reduce.rb +12 -14
- data/lib/keisan/functions/registry.rb +7 -7
- data/lib/keisan/functions/replace.rb +8 -8
- data/lib/keisan/functions/sample.rb +2 -1
- data/lib/keisan/functions/sec.rb +1 -1
- data/lib/keisan/functions/sech.rb +1 -1
- data/lib/keisan/functions/sin.rb +1 -1
- data/lib/keisan/functions/sinh.rb +1 -1
- data/lib/keisan/functions/sqrt.rb +1 -1
- data/lib/keisan/functions/tan.rb +1 -1
- data/lib/keisan/functions/tanh.rb +1 -1
- data/lib/keisan/functions/while.rb +46 -0
- data/lib/keisan/parser.rb +121 -79
- data/lib/keisan/parsing/assignment.rb +1 -1
- data/lib/keisan/parsing/bitwise_and.rb +1 -1
- data/lib/keisan/parsing/bitwise_not.rb +1 -1
- data/lib/keisan/parsing/bitwise_not_not.rb +1 -1
- data/lib/keisan/parsing/bitwise_or.rb +1 -1
- data/lib/keisan/parsing/bitwise_xor.rb +1 -1
- data/lib/keisan/parsing/curly_group.rb +6 -0
- data/lib/keisan/parsing/divide.rb +1 -1
- data/lib/keisan/parsing/exponent.rb +1 -1
- data/lib/keisan/parsing/function.rb +1 -1
- data/lib/keisan/parsing/group.rb +1 -1
- data/lib/keisan/parsing/indexing.rb +1 -1
- data/lib/keisan/parsing/line_separator.rb +6 -0
- data/lib/keisan/parsing/logical_and.rb +1 -1
- data/lib/keisan/parsing/logical_equal.rb +1 -1
- data/lib/keisan/parsing/logical_greater_than.rb +1 -1
- data/lib/keisan/parsing/logical_greater_than_or_equal_to.rb +1 -1
- data/lib/keisan/parsing/logical_less_than.rb +1 -1
- data/lib/keisan/parsing/logical_less_than_or_equal_to.rb +1 -1
- data/lib/keisan/parsing/logical_not.rb +1 -1
- data/lib/keisan/parsing/logical_not_equal.rb +1 -1
- data/lib/keisan/parsing/logical_not_not.rb +1 -1
- data/lib/keisan/parsing/logical_or.rb +1 -1
- data/lib/keisan/parsing/minus.rb +1 -1
- data/lib/keisan/parsing/modulo.rb +1 -1
- data/lib/keisan/parsing/operator.rb +1 -1
- data/lib/keisan/parsing/plus.rb +1 -1
- data/lib/keisan/parsing/times.rb +1 -1
- data/lib/keisan/parsing/unary_minus.rb +1 -1
- data/lib/keisan/parsing/unary_operator.rb +1 -1
- data/lib/keisan/parsing/unary_plus.rb +1 -1
- data/lib/keisan/repl.rb +1 -1
- data/lib/keisan/tokenizer.rb +4 -9
- data/lib/keisan/tokens/group.rb +3 -1
- data/lib/keisan/tokens/line_separator.rb +11 -0
- data/lib/keisan/variables/default_registry.rb +0 -5
- data/lib/keisan/variables/registry.rb +7 -7
- data/lib/keisan/version.rb +1 -1
- metadata +27 -2
data/lib/keisan/ast/parent.rb
CHANGED
@@ -6,20 +6,20 @@ module Keisan
|
|
6
6
|
def initialize(children = [])
|
7
7
|
children = Array.wrap(children).map(&:to_node)
|
8
8
|
unless children.is_a?(Array) && children.all? {|children| children.is_a?(Node)}
|
9
|
-
raise
|
9
|
+
raise Exceptions::InternalError.new
|
10
10
|
end
|
11
11
|
@children = children
|
12
12
|
end
|
13
13
|
|
14
14
|
def unbound_variables(context = nil)
|
15
|
-
context ||=
|
15
|
+
context ||= Context.new
|
16
16
|
children.inject(Set.new) do |vars, child|
|
17
17
|
vars | child.unbound_variables(context)
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
21
|
def unbound_functions(context = nil)
|
22
|
-
context ||=
|
22
|
+
context ||= Context.new
|
23
23
|
children.inject(Set.new) do |fns, child|
|
24
24
|
fns | child.unbound_functions(context)
|
25
25
|
end
|
@@ -45,7 +45,7 @@ module Keisan
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def evaluate(context = nil)
|
48
|
-
context ||=
|
48
|
+
context ||= Context.new
|
49
49
|
@children = children.map {|child| child.evaluate(context)}
|
50
50
|
self
|
51
51
|
end
|
data/lib/keisan/ast/plus.rb
CHANGED
@@ -39,25 +39,25 @@ module Keisan
|
|
39
39
|
# Commutative, so pull in operands of any `Plus` operators
|
40
40
|
@children = children.inject([]) do |new_children, cur_child|
|
41
41
|
case cur_child
|
42
|
-
when
|
42
|
+
when Plus
|
43
43
|
new_children + cur_child.children
|
44
44
|
else
|
45
45
|
new_children << cur_child
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
if children.all? {|child| child.is_a?(
|
50
|
-
return
|
49
|
+
if children.all? {|child| child.is_a?(String)}
|
50
|
+
return String.new(children.inject("") do |result, child|
|
51
51
|
result + child.value
|
52
52
|
end)
|
53
|
-
elsif children.all? {|child| child.is_a?(
|
54
|
-
return
|
53
|
+
elsif children.all? {|child| child.is_a?(List)}
|
54
|
+
return List.new(children.inject([]) do |result, child|
|
55
55
|
result + child.value
|
56
56
|
end)
|
57
57
|
end
|
58
58
|
|
59
|
-
constants, non_constants = *children.partition {|child| child.is_a?(
|
60
|
-
constant = constants.inject(
|
59
|
+
constants, non_constants = *children.partition {|child| child.is_a?(Number)}
|
60
|
+
constant = constants.inject(Number.new(0), &:+).simplify(context)
|
61
61
|
|
62
62
|
if non_constants.empty?
|
63
63
|
constant
|
@@ -72,15 +72,15 @@ module Keisan
|
|
72
72
|
end
|
73
73
|
|
74
74
|
def differentiate(variable, context = nil)
|
75
|
-
|
75
|
+
Plus.new(children.map {|child| child.differentiate(variable, context)}).simplify(context)
|
76
76
|
end
|
77
77
|
|
78
78
|
private
|
79
79
|
|
80
80
|
def convert_minus_to_plus!
|
81
81
|
@parsing_operators.each.with_index do |parsing_operator, index|
|
82
|
-
if parsing_operator.is_a?(
|
83
|
-
@children[index+1] =
|
82
|
+
if parsing_operator.is_a?(Parsing::Minus)
|
83
|
+
@children[index+1] = UnaryMinus.new(@children[index+1])
|
84
84
|
end
|
85
85
|
end
|
86
86
|
end
|
data/lib/keisan/ast/string.rb
CHANGED
@@ -13,10 +13,10 @@ module Keisan
|
|
13
13
|
|
14
14
|
def +(other)
|
15
15
|
case other
|
16
|
-
when
|
17
|
-
|
16
|
+
when String
|
17
|
+
String.new(value + other.value)
|
18
18
|
else
|
19
|
-
raise
|
19
|
+
raise Exceptions::TypeError.new("#{other}'s type is invalid, #{other.class}")
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
data/lib/keisan/ast/times.rb
CHANGED
@@ -26,17 +26,17 @@ module Keisan
|
|
26
26
|
# Commutative, so pull in operands of any `Times` operators
|
27
27
|
@children = children.inject([]) do |new_children, cur_child|
|
28
28
|
case cur_child
|
29
|
-
when
|
29
|
+
when Times
|
30
30
|
new_children + cur_child.children
|
31
31
|
else
|
32
32
|
new_children << cur_child
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
constants, non_constants = *children.partition {|child| child.is_a?(
|
37
|
-
constant = constants.inject(
|
36
|
+
constants, non_constants = *children.partition {|child| child.is_a?(Number)}
|
37
|
+
constant = constants.inject(Number.new(1), &:*).simplify(context)
|
38
38
|
|
39
|
-
return
|
39
|
+
return Number.new(0) if constant.value(context) == 0
|
40
40
|
|
41
41
|
if non_constants.empty?
|
42
42
|
constant
|
@@ -52,9 +52,9 @@ module Keisan
|
|
52
52
|
|
53
53
|
def differentiate(variable, context = nil)
|
54
54
|
# Product rule
|
55
|
-
|
55
|
+
Plus.new(
|
56
56
|
children.map.with_index do |child,i|
|
57
|
-
|
57
|
+
Times.new(
|
58
58
|
children.slice(0,i) + [child.differentiate(variable, context)] + children.slice(i+1,children.size)
|
59
59
|
)
|
60
60
|
end
|
@@ -65,8 +65,8 @@ module Keisan
|
|
65
65
|
|
66
66
|
def convert_divide_to_inverse!
|
67
67
|
@parsing_operators.each.with_index do |parsing_operator, index|
|
68
|
-
if parsing_operator.is_a?(
|
69
|
-
@children[index+1] =
|
68
|
+
if parsing_operator.is_a?(Parsing::Divide)
|
69
|
+
@children[index+1] = UnaryInverse.new(@children[index+1])
|
70
70
|
end
|
71
71
|
end
|
72
72
|
end
|
@@ -18,8 +18,8 @@ module Keisan
|
|
18
18
|
|
19
19
|
@children = [child.simplify(context)]
|
20
20
|
case child
|
21
|
-
when
|
22
|
-
|
21
|
+
when Number
|
22
|
+
Number.new(Rational(1,child.value(context))).simplify(context)
|
23
23
|
else
|
24
24
|
(child ** -1).simplify(context)
|
25
25
|
end
|
@@ -27,12 +27,12 @@ module Keisan
|
|
27
27
|
|
28
28
|
def differentiate(variable, context = nil)
|
29
29
|
context ||= Context.new
|
30
|
-
|
30
|
+
Times.new(
|
31
31
|
[
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
child.deep_dup,
|
32
|
+
UnaryMinus.new(child.differentiate(variable, context)),
|
33
|
+
UnaryInverse.new(
|
34
|
+
Exponent.new([
|
35
|
+
child.deep_dup, Number.new(2)
|
36
36
|
])
|
37
37
|
)
|
38
38
|
]
|
@@ -17,11 +17,11 @@ module Keisan
|
|
17
17
|
context ||= Context.new
|
18
18
|
|
19
19
|
case child
|
20
|
-
when
|
21
|
-
|
20
|
+
when Number
|
21
|
+
Number.new(-child.value(context)).simplify(context)
|
22
22
|
else
|
23
|
-
|
24
|
-
|
23
|
+
Times.new([
|
24
|
+
Number.new(-1),
|
25
25
|
child
|
26
26
|
]).simplify(context)
|
27
27
|
end
|
@@ -29,7 +29,7 @@ module Keisan
|
|
29
29
|
|
30
30
|
def differentiate(variable, context = nil)
|
31
31
|
context ||= Context.new
|
32
|
-
|
32
|
+
Times.new([
|
33
33
|
-1.to_node,
|
34
34
|
child.differentiate(variable, context)
|
35
35
|
]).simplify(context)
|
@@ -5,7 +5,7 @@ module Keisan
|
|
5
5
|
children = Array.wrap(children)
|
6
6
|
super(children)
|
7
7
|
if children.count != 1
|
8
|
-
raise
|
8
|
+
raise Exceptions::ASTError.new("Unary operator takes has a single child")
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
@@ -26,7 +26,7 @@ module Keisan
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def to_s
|
29
|
-
if child.is_a?(
|
29
|
+
if child.is_a?(Operator)
|
30
30
|
"#{symbol.to_s}(#{child.to_s})"
|
31
31
|
else
|
32
32
|
"#{symbol.to_s}#{child.to_s}"
|
data/lib/keisan/ast/variable.rb
CHANGED
@@ -8,17 +8,22 @@ module Keisan
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def value(context = nil)
|
11
|
-
context
|
12
|
-
context
|
11
|
+
context ||= Context.new
|
12
|
+
variable_node_from_context(context).value(context)
|
13
13
|
end
|
14
14
|
|
15
15
|
def unbound_variables(context = nil)
|
16
|
-
context ||=
|
16
|
+
context ||= Context.new
|
17
17
|
context.has_variable?(name) ? Set.new : Set.new([name])
|
18
18
|
end
|
19
19
|
|
20
20
|
def ==(other)
|
21
|
-
|
21
|
+
case other
|
22
|
+
when Variable
|
23
|
+
name == other.name
|
24
|
+
else
|
25
|
+
false
|
26
|
+
end
|
22
27
|
end
|
23
28
|
|
24
29
|
def to_s
|
@@ -26,12 +31,13 @@ module Keisan
|
|
26
31
|
end
|
27
32
|
|
28
33
|
def evaluate(context = nil)
|
29
|
-
context ||=
|
34
|
+
context ||= Context.new
|
30
35
|
if context.has_variable?(name)
|
31
|
-
variable = context
|
36
|
+
variable = variable_node_from_context(context)
|
37
|
+
|
32
38
|
# The variable might just be a variable, i.e. probably in function definition
|
33
|
-
if variable.is_a?(
|
34
|
-
variable.is_a?(
|
39
|
+
if variable.is_a?(Node)
|
40
|
+
variable.is_a?(Variable) ? variable : variable.evaluate(context)
|
35
41
|
else
|
36
42
|
variable
|
37
43
|
end
|
@@ -41,7 +47,7 @@ module Keisan
|
|
41
47
|
end
|
42
48
|
|
43
49
|
def simplify(context = nil)
|
44
|
-
context ||=
|
50
|
+
context ||= Context.new
|
45
51
|
if context.has_variable?(name)
|
46
52
|
context.variable(name).to_node.simplify(context)
|
47
53
|
else
|
@@ -58,7 +64,7 @@ module Keisan
|
|
58
64
|
end
|
59
65
|
|
60
66
|
def differentiate(variable, context = nil)
|
61
|
-
context ||=
|
67
|
+
context ||= Context.new
|
62
68
|
|
63
69
|
if name == variable.name && !context.has_variable?(name)
|
64
70
|
1.to_node
|
@@ -66,6 +72,16 @@ module Keisan
|
|
66
72
|
0.to_node
|
67
73
|
end
|
68
74
|
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def variable_node_from_context(context)
|
79
|
+
variable = context.variable(name)
|
80
|
+
if variable.is_a?(Cell)
|
81
|
+
variable = variable.node
|
82
|
+
end
|
83
|
+
variable
|
84
|
+
end
|
69
85
|
end
|
70
86
|
end
|
71
87
|
end
|
data/lib/keisan/context.rb
CHANGED
@@ -26,7 +26,7 @@ module Keisan
|
|
26
26
|
case value
|
27
27
|
when Proc
|
28
28
|
child.register_function!(name, value)
|
29
|
-
when
|
29
|
+
when Functions::ProcFunction
|
30
30
|
child.register_function!(name, value.function_proc)
|
31
31
|
else
|
32
32
|
child.register_variable!(name, value)
|
@@ -61,8 +61,8 @@ module Keisan
|
|
61
61
|
@variable_registry.has?(name)
|
62
62
|
end
|
63
63
|
|
64
|
-
def register_variable!(name, value)
|
65
|
-
if !@variable_registry.shadowed.member?(name) && transient?
|
64
|
+
def register_variable!(name, value, local: false)
|
65
|
+
if !@variable_registry.shadowed.member?(name) && (transient? || !local && @parent&.has_variable?(name))
|
66
66
|
@parent.register_variable!(name, value)
|
67
67
|
else
|
68
68
|
@variable_registry.register!(name, value)
|
@@ -77,8 +77,8 @@ module Keisan
|
|
77
77
|
@function_registry.has?(name)
|
78
78
|
end
|
79
79
|
|
80
|
-
def register_function!(name, function)
|
81
|
-
if transient?
|
80
|
+
def register_function!(name, function, local: false)
|
81
|
+
if transient? || !local && @parent&.has_function?(name)
|
82
82
|
@parent.register_function!(name, function)
|
83
83
|
else
|
84
84
|
@function_registry.register!(name.to_s, function)
|
data/lib/keisan/evaluator.rb
CHANGED
@@ -8,13 +8,14 @@ module Keisan
|
|
8
8
|
|
9
9
|
def evaluate(expression, definitions = {})
|
10
10
|
context = calculator.context.spawn_child(definitions: definitions, transient: true)
|
11
|
-
ast =
|
12
|
-
|
11
|
+
ast = ast(expression)
|
12
|
+
last_line = last_line(ast)
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
evaluation = ast.evaluated(context)
|
15
|
+
|
16
|
+
if last_line.is_a?(AST::Assignment)
|
17
|
+
if last_line.children.first.is_a?(AST::Variable)
|
18
|
+
context.variable(last_line.children.first.name).value(context)
|
18
19
|
end
|
19
20
|
else
|
20
21
|
evaluation.value(context)
|
@@ -23,12 +24,18 @@ module Keisan
|
|
23
24
|
|
24
25
|
def simplify(expression, definitions = {})
|
25
26
|
context = calculator.context.spawn_child(definitions: definitions, transient: true)
|
26
|
-
ast =
|
27
|
+
ast = AST.parse(expression)
|
27
28
|
ast.simplify(context)
|
28
29
|
end
|
29
30
|
|
30
31
|
def ast(expression)
|
31
|
-
|
32
|
+
AST.parse(expression)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def last_line(ast)
|
38
|
+
ast.is_a?(AST::MultiLine) ? ast.children.last : ast
|
32
39
|
end
|
33
40
|
end
|
34
41
|
end
|
data/lib/keisan/function.rb
CHANGED
@@ -1,25 +1,43 @@
|
|
1
1
|
module Keisan
|
2
2
|
class Function
|
3
|
-
attr_reader :name
|
3
|
+
attr_reader :name, :arity
|
4
4
|
|
5
|
-
def initialize(name)
|
5
|
+
def initialize(name, arity = 1)
|
6
6
|
@name = name
|
7
|
+
@arity = arity
|
7
8
|
end
|
8
9
|
|
9
10
|
def value(ast_function, context = nil)
|
10
|
-
raise
|
11
|
+
raise Exceptions::NotImplementedError.new
|
11
12
|
end
|
12
13
|
|
13
14
|
def evaluate(ast_function, context = nil)
|
14
|
-
raise
|
15
|
+
raise Exceptions::NotImplementedError.new
|
15
16
|
end
|
16
17
|
|
17
18
|
def simplify(ast_function, context = nil)
|
18
|
-
raise
|
19
|
+
raise Exceptions::NotImplementedError.new
|
19
20
|
end
|
20
21
|
|
21
22
|
def differentiate(ast_function, variable, context = nil)
|
22
|
-
raise
|
23
|
+
raise Exceptions::NotImplementedError.new
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def validate_arguments!(count)
|
29
|
+
case arity
|
30
|
+
when Integer
|
31
|
+
if arity < 0 && count < arity.abs || arity >= 0 && count != arity
|
32
|
+
raise Keisan::Exceptions::InvalidFunctionError.new("Require #{arity} arguments to #{name}")
|
33
|
+
end
|
34
|
+
when Range
|
35
|
+
unless arity.include? count
|
36
|
+
raise Keisan::Exceptions::InvalidFunctionError.new("Require #{arity} arguments to #{name}")
|
37
|
+
end
|
38
|
+
else
|
39
|
+
raise Keisan::Exceptions::InternalError.new("Invalid arity: #{arity}")
|
40
|
+
end
|
23
41
|
end
|
24
42
|
end
|
25
43
|
end
|