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
@@ -1,6 +1,6 @@
1
1
  module Keisan
2
2
  module AST
3
- class Null < Literal
3
+ class Null < ConstantLiteral
4
4
  def initialize
5
5
  end
6
6
 
@@ -1,6 +1,6 @@
1
1
  module Keisan
2
2
  module AST
3
- class Number < Literal
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
@@ -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
- raise Keisan::Exceptions::NotImplementedError.new
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
- Keisan::AST::Priorities.priorities[symbol]
67
+ PRIORITIES[symbol]
29
68
  end
30
69
 
31
70
  def associativity
32
- raise Keisan::Exceptions::NotImplementedError.new
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
@@ -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
@@ -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!
@@ -1,6 +1,6 @@
1
1
  module Keisan
2
2
  module AST
3
- class String < Literal
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
@@ -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!