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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +57 -9
  3. data/lib/keisan.rb +21 -12
  4. data/lib/keisan/ast.rb +50 -0
  5. data/lib/keisan/ast/arithmetic_operator.rb +0 -3
  6. data/lib/keisan/ast/assignment.rb +72 -0
  7. data/lib/keisan/ast/bitwise_and.rb +4 -4
  8. data/lib/keisan/ast/bitwise_operator.rb +0 -3
  9. data/lib/keisan/ast/bitwise_or.rb +4 -4
  10. data/lib/keisan/ast/bitwise_xor.rb +4 -4
  11. data/lib/keisan/ast/boolean.rb +25 -1
  12. data/lib/keisan/ast/builder.rb +98 -63
  13. data/lib/keisan/ast/constant_literal.rb +13 -0
  14. data/lib/keisan/ast/exponent.rb +62 -8
  15. data/lib/keisan/ast/function.rb +37 -26
  16. data/lib/keisan/ast/functions/diff.rb +57 -0
  17. data/lib/keisan/ast/functions/if.rb +47 -0
  18. data/lib/keisan/ast/indexing.rb +44 -4
  19. data/lib/keisan/ast/list.rb +4 -0
  20. data/lib/keisan/ast/literal.rb +8 -0
  21. data/lib/keisan/ast/logical_and.rb +9 -4
  22. data/lib/keisan/ast/logical_equal.rb +4 -4
  23. data/lib/keisan/ast/logical_greater_than.rb +4 -4
  24. data/lib/keisan/ast/logical_greater_than_or_equal_to.rb +4 -4
  25. data/lib/keisan/ast/logical_less_than.rb +4 -4
  26. data/lib/keisan/ast/logical_less_than_or_equal_to.rb +4 -4
  27. data/lib/keisan/ast/logical_not_equal.rb +4 -4
  28. data/lib/keisan/ast/logical_operator.rb +0 -3
  29. data/lib/keisan/ast/logical_or.rb +10 -5
  30. data/lib/keisan/ast/modulo.rb +4 -4
  31. data/lib/keisan/ast/node.rb +132 -20
  32. data/lib/keisan/ast/null.rb +1 -1
  33. data/lib/keisan/ast/number.rb +172 -1
  34. data/lib/keisan/ast/operator.rb +66 -8
  35. data/lib/keisan/ast/parent.rb +50 -0
  36. data/lib/keisan/ast/plus.rb +38 -4
  37. data/lib/keisan/ast/string.rb +10 -1
  38. data/lib/keisan/ast/times.rb +47 -4
  39. data/lib/keisan/ast/unary_bitwise_not.rb +9 -1
  40. data/lib/keisan/ast/unary_identity.rb +18 -1
  41. data/lib/keisan/ast/unary_inverse.rb +35 -1
  42. data/lib/keisan/ast/unary_logical_not.rb +5 -1
  43. data/lib/keisan/ast/unary_minus.rb +31 -1
  44. data/lib/keisan/ast/unary_operator.rb +29 -1
  45. data/lib/keisan/ast/unary_plus.rb +14 -1
  46. data/lib/keisan/ast/variable.rb +44 -0
  47. data/lib/keisan/calculator.rb +2 -2
  48. data/lib/keisan/context.rb +32 -16
  49. data/lib/keisan/evaluator.rb +10 -65
  50. data/lib/keisan/exceptions.rb +2 -0
  51. data/lib/keisan/function.rb +11 -5
  52. data/lib/keisan/function_definition_context.rb +34 -0
  53. data/lib/keisan/functions/default_registry.rb +13 -5
  54. data/lib/keisan/functions/diff.rb +82 -0
  55. data/lib/keisan/functions/expression_function.rb +63 -0
  56. data/lib/keisan/functions/if.rb +62 -0
  57. data/lib/keisan/functions/proc_function.rb +52 -0
  58. data/lib/keisan/functions/rand.rb +1 -1
  59. data/lib/keisan/functions/registry.rb +20 -10
  60. data/lib/keisan/functions/replace.rb +49 -0
  61. data/lib/keisan/functions/sample.rb +1 -1
  62. data/lib/keisan/parser.rb +13 -1
  63. data/lib/keisan/parsing/assignment.rb +9 -0
  64. data/lib/keisan/parsing/operator.rb +8 -0
  65. data/lib/keisan/parsing/unary_operator.rb +1 -1
  66. data/lib/keisan/tokenizer.rb +1 -0
  67. data/lib/keisan/tokens/assignment.rb +15 -0
  68. data/lib/keisan/variables/default_registry.rb +4 -4
  69. data/lib/keisan/variables/registry.rb +17 -8
  70. data/lib/keisan/version.rb +1 -1
  71. metadata +15 -3
  72. data/lib/keisan/ast/priorities.rb +0 -27
@@ -15,8 +15,10 @@ module Keisan
15
15
  @components = Array.wrap(components)
16
16
  end
17
17
 
18
- @nodes = nodes_split_by_operators(@components)
19
- @operators = @components.select {|component| component.is_a?(Keisan::Parsing::Operator)}
18
+ @nodes = components_to_basic_nodes(@components)
19
+
20
+ # Negative means not an operator
21
+ @priorities = @nodes.map {|node| node.is_a?(Keisan::Parsing::Operator) ? node.priority : -1}
20
22
 
21
23
  consume_operators!
22
24
 
@@ -35,27 +37,44 @@ module Keisan
35
37
 
36
38
  private
37
39
 
38
- def nodes_split_by_operators(components)
39
- components.split {|component|
40
- component.is_a?(Keisan::Parsing::Operator)
41
- }.map {|group_of_components|
42
- node_from_components(group_of_components)
43
- }
40
+ # Array of AST elements, and Parsing operators
41
+ def components_to_basic_nodes(components)
42
+ nodes_components = []
43
+
44
+ components.each do |component|
45
+ if nodes_components.empty?
46
+ nodes_components << [component]
47
+ else
48
+ is_operator = [nodes_components.last.last.is_a?(Keisan::Parsing::Operator), component.is_a?(Keisan::Parsing::Operator)]
49
+
50
+ if is_operator.first == is_operator.last
51
+ nodes_components.last << component
52
+ else
53
+ nodes_components << [component]
54
+ end
55
+ end
56
+ end
57
+
58
+ nodes_components.inject([]) do |nodes, node_or_component_group|
59
+ if node_or_component_group.first.is_a?(Keisan::Parsing::Operator)
60
+ node_or_component_group.each do |component|
61
+ nodes << component
62
+ end
63
+ else
64
+ nodes << node_from_components(node_or_component_group)
65
+ end
66
+
67
+ nodes
68
+ end
44
69
  end
45
70
 
46
71
  def node_from_components(components)
47
- unary_components, node, postfix_components = *unarys_node_postfixes(components)
48
-
72
+ node, postfix_components = *node_postfixes(components)
49
73
  # Apply postfix operators
50
74
  postfix_components.each do |postfix_component|
51
75
  node = apply_postfix_component_to_node(postfix_component, node)
52
76
  end
53
77
 
54
- # Apply prefix unary operators
55
- unary_components.reverse.each do |unary_component|
56
- node = unary_component.node_class.new(node)
57
- end
58
-
59
78
  node
60
79
  end
61
80
 
@@ -69,16 +88,16 @@ module Keisan
69
88
  }
70
89
  )
71
90
  when Keisan::Parsing::DotWord
72
- Keisan::AST::Function.build(
73
- postfix_component.name,
74
- [node]
91
+ Keisan::AST::Function.new(
92
+ [node],
93
+ postfix_component.name
75
94
  )
76
95
  when Keisan::Parsing::DotOperator
77
- Keisan::AST::Function.build(
78
- postfix_component.name,
96
+ Keisan::AST::Function.new(
79
97
  [node] + postfix_component.arguments.map {|parsing_argument|
80
98
  Builder.new(components: parsing_argument.components).node
81
- }
99
+ },
100
+ postfix_component.name
82
101
  )
83
102
  else
84
103
  raise Keisan::Exceptions::ASTError.new("Invalid postfix component #{postfix_component}")
@@ -86,17 +105,10 @@ module Keisan
86
105
  end
87
106
 
88
107
  # Returns an array of the form
89
- # [unary_operators, middle_node, postfix_operators]
90
- # unary_operators is an array of Keisan::Parsing::UnaryOperator objects
108
+ # [node, postfix_operators]
91
109
  # middle_node is the main node which will be modified by prefix and postfix operators
92
110
  # postfix_operators is an array of Keisan::Parsing::Indexing, DotWord, and DotOperator objects
93
- def unarys_node_postfixes(components)
94
- index_of_unary_components = components.map.with_index {|c,i| [c,i]}.select {|c,i| c.is_a?(Keisan::Parsing::UnaryOperator)}.map(&:last)
95
- # Must be all in the front
96
- unless index_of_unary_components.map.with_index.all? {|i,j| i == j}
97
- raise Keisan::Exceptions::ASTError.new("unary operators must be in front")
98
- end
99
-
111
+ def node_postfixes(components)
100
112
  index_of_postfix_components = components.map.with_index {|c,i| [c,i]}.select {|c,i|
101
113
  c.is_a?(Keisan::Parsing::Indexing) || c.is_a?(Keisan::Parsing::DotWord) || c.is_a?(Keisan::Parsing::DotOperator)
102
114
  }.map(&:last)
@@ -104,16 +116,14 @@ module Keisan
104
116
  raise Keisan::Exceptions::ASTError.new("postfix components must be in back")
105
117
  end
106
118
 
107
- num_unary = index_of_unary_components.size
108
119
  num_postfix = index_of_postfix_components.size
109
120
 
110
- unless num_unary + 1 + num_postfix == components.size
121
+ unless num_postfix + 1 == components.size
111
122
  raise Keisan::Exceptions::ASTError.new("have too many components")
112
123
  end
113
124
 
114
125
  [
115
- index_of_unary_components.map {|i| components[i]},
116
- node_of_component(components[index_of_unary_components.size]),
126
+ node_of_component(components[0]),
117
127
  index_of_postfix_components.map {|i| components[i]}
118
128
  ]
119
129
  end
@@ -139,23 +149,23 @@ module Keisan
139
149
  when Keisan::Parsing::Group
140
150
  Builder.new(components: component.components).node
141
151
  when Keisan::Parsing::Function
142
- Keisan::AST::Function.build(
143
- component.name,
152
+ Keisan::AST::Function.new(
144
153
  component.arguments.map {|parsing_argument|
145
154
  Builder.new(components: parsing_argument.components).node
146
- }
155
+ },
156
+ component.name
147
157
  )
148
158
  when Keisan::Parsing::DotWord
149
- Keisan::AST::Function.build(
150
- component.name,
151
- [node_of_component(component.target)]
159
+ Keisan::AST::Function.new(
160
+ [node_of_component(component.target)],
161
+ component.name
152
162
  )
153
163
  when Keisan::Parsing::DotOperator
154
- Keisan::AST::Function.build(
155
- component.name,
164
+ Keisan::AST::Function.new(
156
165
  [node_of_component(component.target)] + component.arguments.map {|parsing_argument|
157
166
  Builder.new(components: parsing_argument.components).node
158
- }
167
+ },
168
+ component.name
159
169
  )
160
170
  else
161
171
  raise Keisan::Exceptions::ASTError.new("Unhandled component, #{component}")
@@ -163,32 +173,57 @@ module Keisan
163
173
  end
164
174
 
165
175
  def consume_operators!
166
- while @operators.count > 0
167
- priorities = @operators.map(&:priority)
168
- max_priority = priorities.uniq.max
176
+ loop do
177
+ break if @priorities.empty?
178
+ max_priority = @priorities.max
179
+ break if max_priority < 0
180
+
169
181
  consume_operators_with_priority!(max_priority)
170
182
  end
171
183
  end
172
184
 
173
185
  def consume_operators_with_priority!(priority)
174
- # Treat back-to-back operators with same priority as one single call (e.g. 1 + 2 + 3 is add(1,2,3))
175
- while @operators.any? {|operator| operator.priority == priority}
176
- next_operator_group = @operators.each.with_index.to_a.split {|operator,i|
177
- operator.priority != priority
178
- }.select {|ops| !ops.empty?}.first
179
- operator_group_indexes = next_operator_group.map(&:last)
180
-
181
- first_index = operator_group_indexes.first
182
- last_index = operator_group_indexes.last
183
-
184
- replacement_node = next_operator_group.first.first.node_class.new(
185
- children = @nodes[first_index..(last_index+1)],
186
- parsing_operators = @operators[first_index..last_index]
187
- )
186
+ p_indexes = @priorities.map.with_index.select {|p,index| p == priority}
187
+ # :left, :right, or :none
188
+ associativity = AST::Operator.associativity_of_priority(priority)
189
+
190
+ if associativity == :right
191
+ index = p_indexes[-1][1]
192
+ else
193
+ index = p_indexes[0][1]
194
+ end
188
195
 
189
- @nodes.delete_if.with_index {|node, i| i >= first_index && i <= last_index+1}
190
- @operators.delete_if.with_index {|node, i| i >= first_index && i <= last_index}
191
- @nodes.insert(first_index, replacement_node)
196
+ operator = @nodes[index]
197
+
198
+ # If has unary operators after, must process those first
199
+ if @nodes[index+1].is_a?(Keisan::Parsing::UnaryOperator)
200
+ loop do
201
+ break if !@nodes[index+1].is_a?(Keisan::Parsing::UnaryOperator)
202
+ index += 1
203
+ end
204
+ operator = @nodes[index]
205
+ end
206
+
207
+ # operator is the current operator to process, and index is its index
208
+ if operator.is_a?(Keisan::Parsing::UnaryOperator)
209
+ replacement_node = operator.node_class.new(
210
+ children = [@nodes[index+1]]
211
+ )
212
+ @nodes.delete_if.with_index {|node, i| i >= index && i <= index+1}
213
+ @priorities.delete_if.with_index {|node, i| i >= index && i <= index+1}
214
+ @nodes.insert(index, replacement_node)
215
+ @priorities.insert(index, -1)
216
+ elsif operator.is_a?(Keisan::Parsing::Operator)
217
+ replacement_node = operator.node_class.new(
218
+ children = [@nodes[index-1],@nodes[index+1]],
219
+ parsing_operators = [operator]
220
+ )
221
+ @nodes.delete_if.with_index {|node, i| i >= index-1 && i <= index+1}
222
+ @priorities.delete_if.with_index {|node, i| i >= index-1 && i <= index+1}
223
+ @nodes.insert(index-1, replacement_node)
224
+ @priorities.insert(index-1, -1)
225
+ else
226
+ raise Keisan::Exceptions::ASTError.new("Can only consume operators")
192
227
  end
193
228
  end
194
229
  end
@@ -0,0 +1,13 @@
1
+ module Keisan
2
+ module AST
3
+ class ConstantLiteral < Literal
4
+ def evaluate(context = nil)
5
+ self
6
+ end
7
+
8
+ def to_s
9
+ value.to_s
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,14 +1,6 @@
1
1
  module Keisan
2
2
  module AST
3
3
  class Exponent < ArithmeticOperator
4
- def arity
5
- (2..2)
6
- end
7
-
8
- def associativity
9
- :right
10
- end
11
-
12
4
  def self.symbol
13
5
  :**
14
6
  end
@@ -16,6 +8,68 @@ module Keisan
16
8
  def blank_value
17
9
  1
18
10
  end
11
+
12
+ def simplify(context = nil)
13
+ context ||= Context.new
14
+
15
+ super(context)
16
+
17
+ # Reduce to basic binary exponents
18
+ reduced = children.reverse[1..-1].inject(children.last) do |total, child|
19
+ child ** total
20
+ end
21
+
22
+ unless reduced.is_a?(AST::Exponent)
23
+ return reduced.simplify(context)
24
+ end
25
+
26
+ if reduced.children.count != 2
27
+ raise Keisan::Exceptions::InternalError.new("Exponent should be binary")
28
+ end
29
+ @children = reduced.children
30
+
31
+ if children[1].is_a?(AST::Number) && children[1].value(context) == 1
32
+ return children[0]
33
+ end
34
+
35
+ if children.all? {|child| child.is_a?(AST::Number)}
36
+ (children[0] ** children[1]).simplify(context)
37
+ else
38
+ self
39
+ end
40
+ end
41
+
42
+ def evaluate(context = nil)
43
+ children.reverse[1..-1].inject(children.last.evaluate(context)) {|total, child| child.evaluate(context) ** total}
44
+ end
45
+
46
+ # d ( a^b ) = d ( exp(b log(a)) ) = a^b * d (b log(a))
47
+ # = a^b * [ db * log(a) + da * b/a] = da b a^(b-1) + db log(a) a^b
48
+ def differentiate(variable, context = nil)
49
+ context ||= Context.new
50
+
51
+ # Reduce to binary form
52
+ unless children.count == 2
53
+ return simplify(context).differentiate(variable, context)
54
+ end
55
+
56
+ base = children[0].simplified(context)
57
+ exponent = children[1].simplified(context)
58
+
59
+ # Special simple case where exponent is a pure number
60
+ if exponent.is_a?(AST::Number)
61
+ return (exponent * base ** (exponent -1)).simplify(context)
62
+ end
63
+
64
+ base_diff = base.differentiate(variable, context)
65
+ exponent_diff = exponent.differentiate(variable, context)
66
+
67
+ res = base ** exponent * (
68
+ exponent_diff * AST::Function.new([base], "log") +
69
+ base_diff * exponent / base
70
+ )
71
+ res.simplify(context)
72
+ end
19
73
  end
20
74
  end
21
75
  end
@@ -9,10 +9,8 @@ module Keisan
9
9
  end
10
10
 
11
11
  def value(context = nil)
12
- context = Keisan::Context.new if context.nil?
13
- argument_values = children.map {|child| child.value(context)}
14
- function = function_from_context(context)
15
- function.call(context, *argument_values)
12
+ context ||= Keisan::Context.new
13
+ function_from_context(context).value(self, context)
16
14
  end
17
15
 
18
16
  def unbound_functions(context = nil)
@@ -25,43 +23,56 @@ module Keisan
25
23
  context.has_function?(name) ? functions : functions | Set.new([name])
26
24
  end
27
25
 
26
+ def function_defined?(context = nil)
27
+ context ||= Keisan::Context.new
28
+ context.has_function?(name)
29
+ end
30
+
28
31
  def function_from_context(context)
29
- @override || context.function(name)
32
+ context.function(name)
30
33
  end
31
- end
32
34
 
33
- class If < Function
34
- def value(context = nil)
35
- unless (2..3).cover? children.size
36
- raise Keisan::Exceptions::InvalidFunctionError.new("Require 2 or 3 arguments to if")
35
+ def ==(other)
36
+ case other
37
+ when AST::Function
38
+ name == other.name && super
39
+ else
40
+ false
37
41
  end
42
+ end
38
43
 
39
- bool = children[0].value(context)
44
+ def evaluate(context = nil)
45
+ context ||= Keisan::Context.new
40
46
 
41
- if bool
42
- children[1].value(context)
47
+ if function_defined?(context)
48
+ function_from_context(context).evaluate(self, context)
43
49
  else
44
- children.size == 3 ? children[2].value(context) : nil
50
+ @children = children.map {|child| child.evaluate(context)}
51
+ self
45
52
  end
46
53
  end
47
54
 
48
- def unbound_functions(context = nil)
49
- context ||= Keisan::Context.new
55
+ def simplify(context = nil)
56
+ context ||= Context.new
50
57
 
51
- children.inject(Set.new) do |res, child|
52
- res | child.unbound_functions(context)
58
+ if function_defined?(context)
59
+ function_from_context(context).simplify(self, context)
60
+ else
61
+ @children = children.map {|child| child.simplify(context)}
62
+ self
53
63
  end
54
64
  end
55
- end
56
65
 
57
- class Function
58
- def self.build(name, arguments = [])
59
- case name.downcase
60
- when "if"
61
- If.new(arguments, name)
62
- else
63
- Function.new(arguments, name)
66
+ def to_s
67
+ "#{name}(#{children.map(&:to_s).join(',')})"
68
+ end
69
+
70
+ def differentiate(variable, context = nil)
71
+ unless unbound_variables(context).include?(variable.name)
72
+ return AST::Number.new(0)
64
73
  end
74
+ # Do not know how to differentiate a function in general, so leave as derivative
75
+ AST::Function.new([self, variable], "diff")
65
76
  end
66
77
  end
67
78
  end
@@ -0,0 +1,57 @@
1
+ module Keisan
2
+ module AST
3
+ module Functions
4
+ class Diff < AST::Function
5
+ def initialize(arguments, name = "diff")
6
+ super(arguments, name)
7
+ end
8
+
9
+ def value(context = nil)
10
+ raise Keisan::Exceptions::InvalidFunctionError.new("Derivative not defined")
11
+ end
12
+
13
+ def unbound_functions(context = nil)
14
+ context ||= Keisan::Context.new
15
+
16
+ children.inject(Set.new) do |res, child|
17
+ res | child.unbound_functions(context)
18
+ end
19
+ end
20
+
21
+ def simplify(context = nil)
22
+ unless children.size > 0
23
+ raise Keisan::Exceptions::InvalidFunctionError.new("Diff requires at least one argument")
24
+ end
25
+
26
+ vars = children[1..-1]
27
+
28
+ unless vars.all? {|var| var.is_a?(AST::Variable)}
29
+ raise Keisan::Exceptions::InvalidFunctionError.new("Diff must differentiate with respect to variables")
30
+ end
31
+
32
+ result = children.first.simplify(context)
33
+
34
+ while vars.size > 0
35
+ begin
36
+ var = vars.first
37
+ if result.unbound_variables(context).include?(var.name)
38
+ result = result.differentiate(var, context)
39
+ else
40
+ return AST::Number.new(0)
41
+ end
42
+ rescue Keisan::Exceptions::NonDifferentiableError => e
43
+ return AST::Functions::Diff.new(
44
+ [result] + vars,
45
+ "diff"
46
+ )
47
+ end
48
+
49
+ vars.shift
50
+ end
51
+
52
+ result
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end