keisan 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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