keisan 0.3.0 → 0.4.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 +57 -9
- data/lib/keisan.rb +21 -12
- data/lib/keisan/ast.rb +50 -0
- data/lib/keisan/ast/arithmetic_operator.rb +0 -3
- data/lib/keisan/ast/assignment.rb +72 -0
- data/lib/keisan/ast/bitwise_and.rb +4 -4
- data/lib/keisan/ast/bitwise_operator.rb +0 -3
- data/lib/keisan/ast/bitwise_or.rb +4 -4
- data/lib/keisan/ast/bitwise_xor.rb +4 -4
- data/lib/keisan/ast/boolean.rb +25 -1
- data/lib/keisan/ast/builder.rb +98 -63
- data/lib/keisan/ast/constant_literal.rb +13 -0
- data/lib/keisan/ast/exponent.rb +62 -8
- data/lib/keisan/ast/function.rb +37 -26
- data/lib/keisan/ast/functions/diff.rb +57 -0
- data/lib/keisan/ast/functions/if.rb +47 -0
- data/lib/keisan/ast/indexing.rb +44 -4
- data/lib/keisan/ast/list.rb +4 -0
- data/lib/keisan/ast/literal.rb +8 -0
- data/lib/keisan/ast/logical_and.rb +9 -4
- data/lib/keisan/ast/logical_equal.rb +4 -4
- data/lib/keisan/ast/logical_greater_than.rb +4 -4
- data/lib/keisan/ast/logical_greater_than_or_equal_to.rb +4 -4
- data/lib/keisan/ast/logical_less_than.rb +4 -4
- data/lib/keisan/ast/logical_less_than_or_equal_to.rb +4 -4
- data/lib/keisan/ast/logical_not_equal.rb +4 -4
- data/lib/keisan/ast/logical_operator.rb +0 -3
- data/lib/keisan/ast/logical_or.rb +10 -5
- data/lib/keisan/ast/modulo.rb +4 -4
- data/lib/keisan/ast/node.rb +132 -20
- data/lib/keisan/ast/null.rb +1 -1
- data/lib/keisan/ast/number.rb +172 -1
- data/lib/keisan/ast/operator.rb +66 -8
- data/lib/keisan/ast/parent.rb +50 -0
- data/lib/keisan/ast/plus.rb +38 -4
- data/lib/keisan/ast/string.rb +10 -1
- data/lib/keisan/ast/times.rb +47 -4
- data/lib/keisan/ast/unary_bitwise_not.rb +9 -1
- data/lib/keisan/ast/unary_identity.rb +18 -1
- data/lib/keisan/ast/unary_inverse.rb +35 -1
- data/lib/keisan/ast/unary_logical_not.rb +5 -1
- data/lib/keisan/ast/unary_minus.rb +31 -1
- data/lib/keisan/ast/unary_operator.rb +29 -1
- data/lib/keisan/ast/unary_plus.rb +14 -1
- data/lib/keisan/ast/variable.rb +44 -0
- data/lib/keisan/calculator.rb +2 -2
- data/lib/keisan/context.rb +32 -16
- data/lib/keisan/evaluator.rb +10 -65
- data/lib/keisan/exceptions.rb +2 -0
- data/lib/keisan/function.rb +11 -5
- data/lib/keisan/function_definition_context.rb +34 -0
- data/lib/keisan/functions/default_registry.rb +13 -5
- data/lib/keisan/functions/diff.rb +82 -0
- data/lib/keisan/functions/expression_function.rb +63 -0
- data/lib/keisan/functions/if.rb +62 -0
- data/lib/keisan/functions/proc_function.rb +52 -0
- data/lib/keisan/functions/rand.rb +1 -1
- data/lib/keisan/functions/registry.rb +20 -10
- data/lib/keisan/functions/replace.rb +49 -0
- data/lib/keisan/functions/sample.rb +1 -1
- data/lib/keisan/parser.rb +13 -1
- data/lib/keisan/parsing/assignment.rb +9 -0
- data/lib/keisan/parsing/operator.rb +8 -0
- data/lib/keisan/parsing/unary_operator.rb +1 -1
- data/lib/keisan/tokenizer.rb +1 -0
- data/lib/keisan/tokens/assignment.rb +15 -0
- data/lib/keisan/variables/default_registry.rb +4 -4
- data/lib/keisan/variables/registry.rb +17 -8
- data/lib/keisan/version.rb +1 -1
- metadata +15 -3
- data/lib/keisan/ast/priorities.rb +0 -27
data/lib/keisan/ast/null.rb
CHANGED
data/lib/keisan/ast/number.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Keisan
|
2
2
|
module AST
|
3
|
-
class Number <
|
3
|
+
class Number < ConstantLiteral
|
4
4
|
attr_reader :number
|
5
5
|
|
6
6
|
def initialize(number)
|
@@ -10,6 +10,177 @@ module Keisan
|
|
10
10
|
def value(context = nil)
|
11
11
|
number
|
12
12
|
end
|
13
|
+
|
14
|
+
def -@
|
15
|
+
AST::Number.new(-value)
|
16
|
+
end
|
17
|
+
|
18
|
+
def +@
|
19
|
+
AST::Number.new(value)
|
20
|
+
end
|
21
|
+
|
22
|
+
def +(other)
|
23
|
+
other = other.to_node
|
24
|
+
case other
|
25
|
+
when AST::Number
|
26
|
+
AST::Number.new(value + other.value)
|
27
|
+
else
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def -(other)
|
33
|
+
self + (-other.to_node)
|
34
|
+
end
|
35
|
+
|
36
|
+
def *(other)
|
37
|
+
other = other.to_node
|
38
|
+
case other
|
39
|
+
when AST::Number
|
40
|
+
AST::Number.new(value * other.value)
|
41
|
+
else
|
42
|
+
super
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def /(other)
|
47
|
+
other = other.to_node
|
48
|
+
case other
|
49
|
+
when AST::Number
|
50
|
+
AST::Number.new(Rational(value, other.value))
|
51
|
+
else
|
52
|
+
super
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def **(other)
|
57
|
+
other = other.to_node
|
58
|
+
case other
|
59
|
+
when AST::Number
|
60
|
+
AST::Number.new(value ** other.value)
|
61
|
+
else
|
62
|
+
super
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def %(other)
|
67
|
+
other = other.to_node
|
68
|
+
case other
|
69
|
+
when AST::Number
|
70
|
+
AST::Number.new(value % other.value)
|
71
|
+
else
|
72
|
+
super
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def &(other)
|
77
|
+
other = other.to_node
|
78
|
+
case other
|
79
|
+
when AST::Number
|
80
|
+
AST::Number.new(value & other.value)
|
81
|
+
else
|
82
|
+
super
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def ~
|
87
|
+
AST::Number.new(~value)
|
88
|
+
end
|
89
|
+
|
90
|
+
def ^(other)
|
91
|
+
other = other.to_node
|
92
|
+
case other
|
93
|
+
when AST::Number
|
94
|
+
AST::Number.new(value ^ other.value)
|
95
|
+
else
|
96
|
+
super
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def |(other)
|
101
|
+
other = other.to_node
|
102
|
+
case other
|
103
|
+
when AST::Number
|
104
|
+
AST::Number.new(value | other.value)
|
105
|
+
else
|
106
|
+
super
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def >(other)
|
111
|
+
other = other.to_node
|
112
|
+
case other
|
113
|
+
when AST::Number
|
114
|
+
AST::Boolean.new(value > other.value)
|
115
|
+
else
|
116
|
+
super
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def >=(other)
|
121
|
+
other = other.to_node
|
122
|
+
case other
|
123
|
+
when AST::Number
|
124
|
+
AST::Boolean.new(value >= other.value)
|
125
|
+
else
|
126
|
+
super
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def <(other)
|
131
|
+
other = other.to_node
|
132
|
+
case other
|
133
|
+
when AST::Number
|
134
|
+
AST::Boolean.new(value < other.value)
|
135
|
+
else
|
136
|
+
super
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def <=(other)
|
141
|
+
other = other.to_node
|
142
|
+
case other
|
143
|
+
when AST::Number
|
144
|
+
AST::Boolean.new(value <= other.value)
|
145
|
+
else
|
146
|
+
super
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def equal(other)
|
151
|
+
other = other.to_node
|
152
|
+
case other
|
153
|
+
when AST::Number
|
154
|
+
AST::Boolean.new(value == other.value)
|
155
|
+
else
|
156
|
+
super
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def not_equal(other)
|
161
|
+
other = other.to_node
|
162
|
+
case other
|
163
|
+
when AST::Number
|
164
|
+
AST::Boolean.new(value != other.value)
|
165
|
+
else
|
166
|
+
super
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def simplify(context = nil)
|
171
|
+
case number
|
172
|
+
when Rational
|
173
|
+
if number.denominator == 1
|
174
|
+
@number = number.numerator
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
self
|
179
|
+
end
|
180
|
+
|
181
|
+
def differentiate(variable, context = nil)
|
182
|
+
0.to_node
|
183
|
+
end
|
13
184
|
end
|
14
185
|
end
|
15
186
|
end
|
data/lib/keisan/ast/operator.rb
CHANGED
@@ -1,23 +1,62 @@
|
|
1
1
|
module Keisan
|
2
2
|
module AST
|
3
3
|
class Operator < Parent
|
4
|
+
# NOTE: operators with same priority must have same associativity
|
5
|
+
ARITY_PRIORITY_ASSOCIATIVITY = {
|
6
|
+
"u!": [1, 100, :right], # Logical not
|
7
|
+
"u~": [1, 100, :right], # Bitwise not
|
8
|
+
"u+": [1, 100, :right], # Unary plus
|
9
|
+
"**": [2, 95, :right], # Exponent
|
10
|
+
"u-": [1, 90, :right], # Unary minus
|
11
|
+
"*": [2, 85, :left], # Times
|
12
|
+
# "/": [2, 85, :left], # Divide
|
13
|
+
"%": [2, 85, :left], # Modulo
|
14
|
+
"+": [2, 80, :left], # Plus
|
15
|
+
# "-": [2, 80, :left], # Minus
|
16
|
+
"&": [2, 70, :left], # Bitwise and
|
17
|
+
"^": [2, 65, :left], # Bitwise xor
|
18
|
+
"|": [2, 65, :left], # Bitwise or
|
19
|
+
">": [2, 60, :left], # Greater than
|
20
|
+
">=": [2, 60, :left], # Greater than or equal to
|
21
|
+
"<": [2, 60, :left], # Less than
|
22
|
+
"<=": [2, 60, :left], # Less than or equal to
|
23
|
+
"==": [2, 55, :none], # Equal
|
24
|
+
"!=": [2, 55, :none], # Not equal
|
25
|
+
"&&": [2, 50, :left], # Logical and
|
26
|
+
"||": [2, 45, :left], # Logical or
|
27
|
+
"=": [2, 40, :right] # TODO: handle and test
|
28
|
+
}.freeze
|
29
|
+
|
30
|
+
ARITIES = Hash[ARITY_PRIORITY_ASSOCIATIVITY.map {|sym, ary| [sym, ary[0]]}].freeze
|
31
|
+
PRIORITIES = Hash[ARITY_PRIORITY_ASSOCIATIVITY.map {|sym, ary| [sym, ary[1]]}].freeze
|
32
|
+
ASSOCIATIVITIES = Hash[ARITY_PRIORITY_ASSOCIATIVITY.map {|sym, ary| [sym, ary[2]]}].freeze
|
33
|
+
|
34
|
+
ASSOCIATIVITY_OF_PRIORITY = ARITY_PRIORITY_ASSOCIATIVITY.inject({}) do |h, (symbol,arity_priority_associativity)|
|
35
|
+
h[arity_priority_associativity[1]] = arity_priority_associativity[2]
|
36
|
+
h
|
37
|
+
end.freeze
|
38
|
+
|
4
39
|
def initialize(children = [], parsing_operators = [])
|
5
|
-
unless children.count == parsing_operators.count + 1
|
40
|
+
unless parsing_operators.empty? || children.count == parsing_operators.count + 1
|
6
41
|
raise Keisan::Exceptions::ASTError.new("Mismatch of children and operators")
|
7
42
|
end
|
8
43
|
|
9
|
-
unless arity.include?(children.count)
|
10
|
-
raise Keisan::Exceptions::ASTError.new("Invalid number of arguments")
|
11
|
-
end
|
12
|
-
|
13
44
|
children = Array.wrap(children)
|
14
45
|
super(children)
|
15
46
|
|
16
47
|
@parsing_operators = parsing_operators
|
17
48
|
end
|
18
49
|
|
50
|
+
def self.associativity_of_priority(priority)
|
51
|
+
ASSOCIATIVITY_OF_PRIORITY[priority]
|
52
|
+
end
|
53
|
+
|
19
54
|
def arity
|
20
|
-
|
55
|
+
self.class.arity
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.arity
|
59
|
+
ARITIES[symbol]
|
21
60
|
end
|
22
61
|
|
23
62
|
def priority
|
@@ -25,17 +64,25 @@ module Keisan
|
|
25
64
|
end
|
26
65
|
|
27
66
|
def self.priority
|
28
|
-
|
67
|
+
PRIORITIES[symbol]
|
29
68
|
end
|
30
69
|
|
31
70
|
def associativity
|
32
|
-
|
71
|
+
self.class.associativity
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.associativity
|
75
|
+
ASSOCIATIVITIES[symbol]
|
33
76
|
end
|
34
77
|
|
35
78
|
def symbol
|
36
79
|
self.class.symbol
|
37
80
|
end
|
38
81
|
|
82
|
+
def self.symbol
|
83
|
+
raise Keisan::Exceptions::NotImplementedError.new
|
84
|
+
end
|
85
|
+
|
39
86
|
def blank_value
|
40
87
|
raise Keisan::Exceptions::NotImplementedError.new
|
41
88
|
end
|
@@ -52,6 +99,17 @@ module Keisan
|
|
52
99
|
end
|
53
100
|
end
|
54
101
|
end
|
102
|
+
|
103
|
+
def to_s
|
104
|
+
children.map do |child|
|
105
|
+
case child
|
106
|
+
when AST::Operator
|
107
|
+
"(#{child.to_s})"
|
108
|
+
else
|
109
|
+
"#{child.to_s}"
|
110
|
+
end
|
111
|
+
end.join(symbol.to_s)
|
112
|
+
end
|
55
113
|
end
|
56
114
|
end
|
57
115
|
end
|
data/lib/keisan/ast/parent.rb
CHANGED
@@ -10,6 +10,56 @@ module Keisan
|
|
10
10
|
end
|
11
11
|
@children = children
|
12
12
|
end
|
13
|
+
|
14
|
+
def unbound_variables(context = nil)
|
15
|
+
context ||= Keisan::Context.new
|
16
|
+
children.inject(Set.new) do |vars, child|
|
17
|
+
vars | child.unbound_variables(context)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def unbound_functions(context = nil)
|
22
|
+
context ||= Keisan::Context.new
|
23
|
+
children.inject(Set.new) do |fns, child|
|
24
|
+
fns | child.unbound_functions(context)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def ==(other)
|
29
|
+
return false unless self.class == other.class
|
30
|
+
|
31
|
+
children.size == other.children.size && children.map.with_index {|_,i|
|
32
|
+
children[i] == other.children[i]
|
33
|
+
}.all? {|bool|
|
34
|
+
bool == true
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
def deep_dup
|
39
|
+
dupped = dup
|
40
|
+
dupped.instance_variable_set(
|
41
|
+
:@children,
|
42
|
+
dupped.children.map(&:deep_dup)
|
43
|
+
)
|
44
|
+
dupped
|
45
|
+
end
|
46
|
+
|
47
|
+
def evaluate(context = nil)
|
48
|
+
context ||= Keisan::Context.new
|
49
|
+
@children = children.map {|child| child.evaluate(context)}
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def simplify(context = nil)
|
54
|
+
context ||= Context.new
|
55
|
+
@children = @children.map {|child| child.simplify(context)}
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
def replace(variable, replacement)
|
60
|
+
@children = children.map {|child| child.replace(variable, replacement)}
|
61
|
+
self
|
62
|
+
end
|
13
63
|
end
|
14
64
|
end
|
15
65
|
end
|
data/lib/keisan/ast/plus.rb
CHANGED
@@ -6,10 +6,6 @@ module Keisan
|
|
6
6
|
convert_minus_to_plus!
|
7
7
|
end
|
8
8
|
|
9
|
-
def arity
|
10
|
-
2..Float::INFINITY
|
11
|
-
end
|
12
|
-
|
13
9
|
def self.symbol
|
14
10
|
:+
|
15
11
|
end
|
@@ -31,6 +27,44 @@ module Keisan
|
|
31
27
|
end
|
32
28
|
end
|
33
29
|
|
30
|
+
def evaluate(context = nil)
|
31
|
+
children[1..-1].inject(children.first.evaluate(context)) {|total, child| total + child.evaluate(context)}
|
32
|
+
end
|
33
|
+
|
34
|
+
def simplify(context = nil)
|
35
|
+
context ||= Context.new
|
36
|
+
|
37
|
+
super(context)
|
38
|
+
|
39
|
+
# Commutative, so pull in operands of any `Plus` operators
|
40
|
+
@children = children.inject([]) do |new_children, cur_child|
|
41
|
+
case cur_child
|
42
|
+
when AST::Plus
|
43
|
+
new_children + cur_child.children
|
44
|
+
else
|
45
|
+
new_children << cur_child
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
constants, non_constants = *children.partition {|child| child.is_a?(AST::Number)}
|
50
|
+
constant = constants.inject(AST::Number.new(0), &:+).simplify(context)
|
51
|
+
|
52
|
+
if non_constants.empty?
|
53
|
+
constant
|
54
|
+
else
|
55
|
+
@children = constant.value(context) == 0 ? [] : [constant]
|
56
|
+
@children += non_constants
|
57
|
+
|
58
|
+
return @children.first.simplify(context) if @children.size == 1
|
59
|
+
|
60
|
+
self
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def differentiate(variable, context = nil)
|
65
|
+
AST::Plus.new(children.map {|child| child.differentiate(variable, context)}).simplify(context)
|
66
|
+
end
|
67
|
+
|
34
68
|
private
|
35
69
|
|
36
70
|
def convert_minus_to_plus!
|
data/lib/keisan/ast/string.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Keisan
|
2
2
|
module AST
|
3
|
-
class String <
|
3
|
+
class String < ConstantLiteral
|
4
4
|
attr_reader :content
|
5
5
|
|
6
6
|
def initialize(content)
|
@@ -10,6 +10,15 @@ module Keisan
|
|
10
10
|
def value(context = nil)
|
11
11
|
content
|
12
12
|
end
|
13
|
+
|
14
|
+
def +(other)
|
15
|
+
case other
|
16
|
+
when AST::String
|
17
|
+
AST::String.new(value + other.value)
|
18
|
+
else
|
19
|
+
raise Keisan::Exceptions::TypeError.new("#{other}'s type is invalid, #{other.class}")
|
20
|
+
end
|
21
|
+
end
|
13
22
|
end
|
14
23
|
end
|
15
24
|
end
|
data/lib/keisan/ast/times.rb
CHANGED
@@ -6,10 +6,6 @@ module Keisan
|
|
6
6
|
convert_divide_to_inverse!
|
7
7
|
end
|
8
8
|
|
9
|
-
def arity
|
10
|
-
2..Float::INFINITY
|
11
|
-
end
|
12
|
-
|
13
9
|
def self.symbol
|
14
10
|
:*
|
15
11
|
end
|
@@ -18,6 +14,53 @@ module Keisan
|
|
18
14
|
1
|
19
15
|
end
|
20
16
|
|
17
|
+
def evaluate(context = nil)
|
18
|
+
children[1..-1].inject(children.first.evaluate(context)) {|total, child| total * child.evaluate(context)}
|
19
|
+
end
|
20
|
+
|
21
|
+
def simplify(context = nil)
|
22
|
+
context ||= Context.new
|
23
|
+
|
24
|
+
super(context)
|
25
|
+
|
26
|
+
# Commutative, so pull in operands of any `Times` operators
|
27
|
+
@children = children.inject([]) do |new_children, cur_child|
|
28
|
+
case cur_child
|
29
|
+
when AST::Times
|
30
|
+
new_children + cur_child.children
|
31
|
+
else
|
32
|
+
new_children << cur_child
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
constants, non_constants = *children.partition {|child| child.is_a?(AST::Number)}
|
37
|
+
constant = constants.inject(AST::Number.new(1), &:*).simplify(context)
|
38
|
+
|
39
|
+
return Keisan::AST::Number.new(0) if constant.value(context) == 0
|
40
|
+
|
41
|
+
if non_constants.empty?
|
42
|
+
constant
|
43
|
+
else
|
44
|
+
@children = constant.value(context) == 1 ? [] : [constant]
|
45
|
+
@children += non_constants
|
46
|
+
|
47
|
+
return @children.first.simplify(context) if @children.size == 1
|
48
|
+
|
49
|
+
self
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def differentiate(variable, context = nil)
|
54
|
+
# Product rule
|
55
|
+
AST::Plus.new(
|
56
|
+
children.map.with_index do |child,i|
|
57
|
+
AST::Times.new(
|
58
|
+
children.slice(0,i) + [child.differentiate(variable, context)] + children.slice(i+1,children.size)
|
59
|
+
)
|
60
|
+
end
|
61
|
+
).simplify(context)
|
62
|
+
end
|
63
|
+
|
21
64
|
private
|
22
65
|
|
23
66
|
def convert_divide_to_inverse!
|