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
@@ -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!