keisan 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +49 -1
  3. data/keisan.gemspec +1 -0
  4. data/lib/keisan.rb +30 -0
  5. data/lib/keisan/ast/assignment.rb +44 -17
  6. data/lib/keisan/ast/block.rb +60 -0
  7. data/lib/keisan/ast/boolean.rb +5 -5
  8. data/lib/keisan/ast/builder.rb +10 -207
  9. data/lib/keisan/ast/cell.rb +60 -0
  10. data/lib/keisan/ast/constant_literal.rb +9 -0
  11. data/lib/keisan/ast/exponent.rb +6 -6
  12. data/lib/keisan/ast/function.rb +12 -8
  13. data/lib/keisan/ast/indexing.rb +25 -15
  14. data/lib/keisan/ast/line_builder.rb +230 -0
  15. data/lib/keisan/ast/list.rb +28 -1
  16. data/lib/keisan/ast/literal.rb +0 -8
  17. data/lib/keisan/ast/logical_and.rb +1 -1
  18. data/lib/keisan/ast/logical_or.rb +1 -1
  19. data/lib/keisan/ast/multi_line.rb +28 -0
  20. data/lib/keisan/ast/node.rb +32 -24
  21. data/lib/keisan/ast/number.rb +31 -31
  22. data/lib/keisan/ast/operator.rb +12 -4
  23. data/lib/keisan/ast/parent.rb +4 -4
  24. data/lib/keisan/ast/plus.rb +10 -10
  25. data/lib/keisan/ast/string.rb +3 -3
  26. data/lib/keisan/ast/times.rb +8 -8
  27. data/lib/keisan/ast/unary_identity.rb +1 -1
  28. data/lib/keisan/ast/unary_inverse.rb +7 -7
  29. data/lib/keisan/ast/unary_minus.rb +5 -5
  30. data/lib/keisan/ast/unary_operator.rb +2 -2
  31. data/lib/keisan/ast/unary_plus.rb +2 -2
  32. data/lib/keisan/ast/variable.rb +26 -10
  33. data/lib/keisan/context.rb +5 -5
  34. data/lib/keisan/evaluator.rb +15 -8
  35. data/lib/keisan/function.rb +24 -6
  36. data/lib/keisan/functions/cbrt.rb +1 -1
  37. data/lib/keisan/functions/cos.rb +1 -1
  38. data/lib/keisan/functions/cosh.rb +1 -1
  39. data/lib/keisan/functions/cot.rb +1 -1
  40. data/lib/keisan/functions/coth.rb +1 -1
  41. data/lib/keisan/functions/csc.rb +1 -1
  42. data/lib/keisan/functions/csch.rb +1 -1
  43. data/lib/keisan/functions/default_registry.rb +53 -74
  44. data/lib/keisan/functions/diff.rb +18 -14
  45. data/lib/keisan/functions/erf.rb +15 -0
  46. data/lib/keisan/functions/exp.rb +1 -1
  47. data/lib/keisan/functions/expression_function.rb +15 -21
  48. data/lib/keisan/functions/filter.rb +13 -15
  49. data/lib/keisan/functions/if.rb +14 -20
  50. data/lib/keisan/functions/let.rb +36 -0
  51. data/lib/keisan/functions/map.rb +11 -13
  52. data/lib/keisan/functions/math_function.rb +2 -2
  53. data/lib/keisan/functions/proc_function.rb +10 -6
  54. data/lib/keisan/functions/rand.rb +2 -1
  55. data/lib/keisan/functions/range.rb +74 -0
  56. data/lib/keisan/functions/reduce.rb +12 -14
  57. data/lib/keisan/functions/registry.rb +7 -7
  58. data/lib/keisan/functions/replace.rb +8 -8
  59. data/lib/keisan/functions/sample.rb +2 -1
  60. data/lib/keisan/functions/sec.rb +1 -1
  61. data/lib/keisan/functions/sech.rb +1 -1
  62. data/lib/keisan/functions/sin.rb +1 -1
  63. data/lib/keisan/functions/sinh.rb +1 -1
  64. data/lib/keisan/functions/sqrt.rb +1 -1
  65. data/lib/keisan/functions/tan.rb +1 -1
  66. data/lib/keisan/functions/tanh.rb +1 -1
  67. data/lib/keisan/functions/while.rb +46 -0
  68. data/lib/keisan/parser.rb +121 -79
  69. data/lib/keisan/parsing/assignment.rb +1 -1
  70. data/lib/keisan/parsing/bitwise_and.rb +1 -1
  71. data/lib/keisan/parsing/bitwise_not.rb +1 -1
  72. data/lib/keisan/parsing/bitwise_not_not.rb +1 -1
  73. data/lib/keisan/parsing/bitwise_or.rb +1 -1
  74. data/lib/keisan/parsing/bitwise_xor.rb +1 -1
  75. data/lib/keisan/parsing/curly_group.rb +6 -0
  76. data/lib/keisan/parsing/divide.rb +1 -1
  77. data/lib/keisan/parsing/exponent.rb +1 -1
  78. data/lib/keisan/parsing/function.rb +1 -1
  79. data/lib/keisan/parsing/group.rb +1 -1
  80. data/lib/keisan/parsing/indexing.rb +1 -1
  81. data/lib/keisan/parsing/line_separator.rb +6 -0
  82. data/lib/keisan/parsing/logical_and.rb +1 -1
  83. data/lib/keisan/parsing/logical_equal.rb +1 -1
  84. data/lib/keisan/parsing/logical_greater_than.rb +1 -1
  85. data/lib/keisan/parsing/logical_greater_than_or_equal_to.rb +1 -1
  86. data/lib/keisan/parsing/logical_less_than.rb +1 -1
  87. data/lib/keisan/parsing/logical_less_than_or_equal_to.rb +1 -1
  88. data/lib/keisan/parsing/logical_not.rb +1 -1
  89. data/lib/keisan/parsing/logical_not_equal.rb +1 -1
  90. data/lib/keisan/parsing/logical_not_not.rb +1 -1
  91. data/lib/keisan/parsing/logical_or.rb +1 -1
  92. data/lib/keisan/parsing/minus.rb +1 -1
  93. data/lib/keisan/parsing/modulo.rb +1 -1
  94. data/lib/keisan/parsing/operator.rb +1 -1
  95. data/lib/keisan/parsing/plus.rb +1 -1
  96. data/lib/keisan/parsing/times.rb +1 -1
  97. data/lib/keisan/parsing/unary_minus.rb +1 -1
  98. data/lib/keisan/parsing/unary_operator.rb +1 -1
  99. data/lib/keisan/parsing/unary_plus.rb +1 -1
  100. data/lib/keisan/repl.rb +1 -1
  101. data/lib/keisan/tokenizer.rb +4 -9
  102. data/lib/keisan/tokens/group.rb +3 -1
  103. data/lib/keisan/tokens/line_separator.rb +11 -0
  104. data/lib/keisan/variables/default_registry.rb +0 -5
  105. data/lib/keisan/variables/registry.rb +7 -7
  106. data/lib/keisan/version.rb +1 -1
  107. metadata +27 -2
@@ -0,0 +1,60 @@
1
+ module Keisan
2
+ module AST
3
+ class Cell < Node
4
+ attr_accessor :node
5
+
6
+ def initialize(node)
7
+ @node = node
8
+ end
9
+
10
+ def unbound_variables(context = nil)
11
+ node.unbound_variables(context)
12
+ end
13
+
14
+ def unbound_functions(context = nil)
15
+ node.unbound_functions(context)
16
+ end
17
+
18
+ def deep_dup
19
+ dupped = dup
20
+ dupped.instance_variable_set(
21
+ :@node,
22
+ dupped.node.deep_dup
23
+ )
24
+ dupped
25
+ end
26
+
27
+ def value(context = nil)
28
+ node.value(context)
29
+ end
30
+
31
+ def evaluate(context = nil)
32
+ node.evaluate(context)
33
+ end
34
+
35
+ def simplify(context = nil)
36
+ node.simplify(context)
37
+ end
38
+
39
+ def evaluate_assignments(context = nil)
40
+ node.evaluate_assignments(context)
41
+ end
42
+
43
+ def differentiate(variable, context = nil)
44
+ node.differentiate(variable, context)
45
+ end
46
+
47
+ def replace(variable, replacement)
48
+ node.replace(variable, replacement)
49
+ end
50
+
51
+ def to_s
52
+ node.to_s
53
+ end
54
+
55
+ def to_node
56
+ node
57
+ end
58
+ end
59
+ end
60
+ end
@@ -5,6 +5,15 @@ module Keisan
5
5
  self
6
6
  end
7
7
 
8
+ def ==(other)
9
+ case other
10
+ when ConstantLiteral
11
+ value == other.value
12
+ else
13
+ false
14
+ end
15
+ end
16
+
8
17
  def to_s
9
18
  case value
10
19
  when Rational
@@ -19,20 +19,20 @@ module Keisan
19
19
  child ** total
20
20
  end
21
21
 
22
- unless reduced.is_a?(AST::Exponent)
22
+ unless reduced.is_a?(Exponent)
23
23
  return reduced.simplify(context)
24
24
  end
25
25
 
26
26
  if reduced.children.count != 2
27
- raise Keisan::Exceptions::InternalError.new("Exponent should be binary")
27
+ raise Exceptions::InternalError.new("Exponent should be binary")
28
28
  end
29
29
  @children = reduced.children
30
30
 
31
- if children[1].is_a?(AST::Number) && children[1].value(context) == 1
31
+ if children[1].is_a?(Number) && children[1].value(context) == 1
32
32
  return children[0]
33
33
  end
34
34
 
35
- if children.all? {|child| child.is_a?(AST::Number)}
35
+ if children.all? {|child| child.is_a?(Number)}
36
36
  (children[0] ** children[1]).simplify(context)
37
37
  else
38
38
  self
@@ -57,7 +57,7 @@ module Keisan
57
57
  exponent = children[1].simplified(context)
58
58
 
59
59
  # Special simple case where exponent is a pure number
60
- if exponent.is_a?(AST::Number)
60
+ if exponent.is_a?(Number)
61
61
  return (exponent * base.differentiate(variable, context) * base ** (exponent -1)).simplify(context)
62
62
  end
63
63
 
@@ -65,7 +65,7 @@ module Keisan
65
65
  exponent_diff = exponent.differentiate(variable, context)
66
66
 
67
67
  res = base ** exponent * (
68
- exponent_diff * AST::Function.new([base], "log") +
68
+ exponent_diff * Function.new([base], "log") +
69
69
  base_diff * exponent / base
70
70
  )
71
71
  res.simplify(context)
@@ -9,12 +9,12 @@ module Keisan
9
9
  end
10
10
 
11
11
  def value(context = nil)
12
- context ||= Keisan::Context.new
12
+ context ||= Context.new
13
13
  function_from_context(context).value(self, context)
14
14
  end
15
15
 
16
16
  def unbound_functions(context = nil)
17
- context ||= Keisan::Context.new
17
+ context ||= Context.new
18
18
 
19
19
  functions = children.inject(Set.new) do |res, child|
20
20
  res | child.unbound_functions(context)
@@ -23,8 +23,12 @@ module Keisan
23
23
  context.has_function?(name) ? functions : functions | Set.new([name])
24
24
  end
25
25
 
26
+ def evaluate_assignments(context = nil)
27
+ self
28
+ end
29
+
26
30
  def function_defined?(context = nil)
27
- context ||= Keisan::Context.new
31
+ context ||= Context.new
28
32
  context.has_function?(name)
29
33
  end
30
34
 
@@ -34,7 +38,7 @@ module Keisan
34
38
 
35
39
  def ==(other)
36
40
  case other
37
- when AST::Function
41
+ when Function
38
42
  name == other.name && super
39
43
  else
40
44
  false
@@ -42,7 +46,7 @@ module Keisan
42
46
  end
43
47
 
44
48
  def evaluate(context = nil)
45
- context ||= Keisan::Context.new
49
+ context ||= Context.new
46
50
 
47
51
  if function_defined?(context)
48
52
  function_from_context(context).evaluate(self, context)
@@ -71,12 +75,12 @@ module Keisan
71
75
  function = function_from_context(context)
72
76
  function.differentiate(self, variable, context)
73
77
 
74
- rescue Keisan::Exceptions::UndefinedFunctionError, Keisan::Exceptions::NotImplementedError
78
+ rescue Exceptions::UndefinedFunctionError, Exceptions::NotImplementedError
75
79
  unless unbound_variables(context).include?(variable.name)
76
- return AST::Number.new(0)
80
+ return Number.new(0)
77
81
  end
78
82
 
79
- AST::Function.new([self, variable], "diff")
83
+ self.class.new([self, variable], "diff")
80
84
  end
81
85
  end
82
86
  end
@@ -16,19 +16,20 @@ module Keisan
16
16
  "(#{child.to_s})[#{indexes.map(&:to_s).join(',')}]"
17
17
  end
18
18
 
19
+ def evaluate_assignments(context = nil)
20
+ self
21
+ end
22
+
19
23
  def evaluate(context = nil)
20
- context ||= Keisan::Context.new
24
+ context ||= Context.new
21
25
  @children = children.map {|child| child.evaluate(context)}
22
26
  @indexes = indexes.map {|index| index.evaluate(context)}
23
27
 
24
- case child
25
- when AST::List
26
- if @indexes.size == 1 && @indexes.first.is_a?(AST::Number)
27
- return child.children[@indexes.first.value(context)].evaluate(context)
28
- end
28
+ if list = extract_list
29
+ list.children[@indexes.first.value(context)]
30
+ else
31
+ self
29
32
  end
30
-
31
- self
32
33
  end
33
34
 
34
35
  def simplify(context = nil)
@@ -37,20 +38,29 @@ module Keisan
37
38
  @indexes = indexes.map {|index| index.simplify(context)}
38
39
  @children = [child.simplify(context)]
39
40
 
40
- case child
41
- when AST::List
42
- if @indexes.size == 1 && @indexes.first.is_a?(AST::Number)
43
- return child.children[@indexes.first.value(context)].simplify(context)
44
- end
41
+ if list = extract_list
42
+ Cell.new(list.children[@indexes.first.value(context)].simplify(context))
43
+ else
44
+ self
45
45
  end
46
-
47
- self
48
46
  end
49
47
 
50
48
  def replace(variable, replacement)
51
49
  super
52
50
  @indexes = indexes.map {|index| index.replace(variable, replacement)}
53
51
  end
52
+
53
+ private
54
+
55
+ def extract_list
56
+ if child.is_a?(List)
57
+ child
58
+ elsif child.is_a?(Cell) && child.node.is_a?(List)
59
+ child.node
60
+ else
61
+ nil
62
+ end
63
+ end
54
64
  end
55
65
  end
56
66
  end
@@ -0,0 +1,230 @@
1
+ module Keisan
2
+ module AST
3
+ class LineBuilder
4
+ # Build from parser
5
+ def initialize(components)
6
+ @components = components
7
+ @nodes = components_to_basic_nodes(@components)
8
+
9
+ # Negative means not an operator
10
+ @priorities = @nodes.map {|node| node.is_a?(Keisan::Parsing::Operator) ? node.priority : -1}
11
+
12
+ consume_operators!
13
+
14
+ case @nodes.count
15
+ when 0
16
+ # Empty string, set to just Null
17
+ @nodes = [Keisan::AST::Null.new]
18
+ when 1
19
+ # Good
20
+ else
21
+ raise Keisan::Exceptions::ASTError.new("Should end up with a single node")
22
+ end
23
+ end
24
+
25
+ def node
26
+ @nodes.first
27
+ end
28
+
29
+ def ast
30
+ node
31
+ end
32
+
33
+ private
34
+
35
+ # Array of AST elements, and Parsing operators
36
+ def components_to_basic_nodes(components)
37
+ nodes_components = []
38
+
39
+ components.each do |component|
40
+ if component.is_a?(Keisan::Parsing::LineSeparator)
41
+ nodes_components << [component]
42
+ elsif nodes_components.empty? || nodes_components.last.last.is_a?(Keisan::Parsing::LineSeparator)
43
+ nodes_components << [component]
44
+ else
45
+ is_operator = [nodes_components.last.last.is_a?(Keisan::Parsing::Operator), component.is_a?(Keisan::Parsing::Operator)]
46
+
47
+ if is_operator.first == is_operator.last
48
+ nodes_components.last << component
49
+ else
50
+ nodes_components << [component]
51
+ end
52
+ end
53
+ end
54
+
55
+ nodes_components.inject([]) do |nodes, node_or_component_group|
56
+ if node_or_component_group.first.is_a?(Keisan::Parsing::Operator)
57
+ node_or_component_group.each do |component|
58
+ nodes << component
59
+ end
60
+ else
61
+ nodes << node_from_components(node_or_component_group)
62
+ end
63
+
64
+ nodes
65
+ end
66
+ end
67
+
68
+ def node_from_components(components)
69
+ node, postfix_components = *node_postfixes(components)
70
+ # Apply postfix operators
71
+ postfix_components.each do |postfix_component|
72
+ node = apply_postfix_component_to_node(postfix_component, node)
73
+ end
74
+
75
+ node
76
+ end
77
+
78
+ def apply_postfix_component_to_node(postfix_component, node)
79
+ case postfix_component
80
+ when Keisan::Parsing::Indexing
81
+ postfix_component.node_class.new(
82
+ node,
83
+ postfix_component.arguments.map {|parsing_argument|
84
+ Builder.new(components: parsing_argument.components).node
85
+ }
86
+ )
87
+ when Keisan::Parsing::DotWord
88
+ Keisan::AST::Function.new(
89
+ [node],
90
+ postfix_component.name
91
+ )
92
+ when Keisan::Parsing::DotOperator
93
+ Keisan::AST::Function.new(
94
+ [node] + postfix_component.arguments.map {|parsing_argument|
95
+ Builder.new(components: parsing_argument.components).node
96
+ },
97
+ postfix_component.name
98
+ )
99
+ else
100
+ raise Keisan::Exceptions::ASTError.new("Invalid postfix component #{postfix_component}")
101
+ end
102
+ end
103
+
104
+ # Returns an array of the form
105
+ # [node, postfix_operators]
106
+ # middle_node is the main node which will be modified by prefix and postfix operators
107
+ # postfix_operators is an array of Keisan::Parsing::Indexing, DotWord, and DotOperator objects
108
+ def node_postfixes(components)
109
+ index_of_postfix_components = components.map.with_index {|c,i| [c,i]}.select {|c,i|
110
+ c.is_a?(Keisan::Parsing::Indexing) || c.is_a?(Keisan::Parsing::DotWord) || c.is_a?(Keisan::Parsing::DotOperator)
111
+ }.map(&:last)
112
+ unless index_of_postfix_components.reverse.map.with_index.all? {|i,j| i + j == components.size - 1 }
113
+ raise Keisan::Exceptions::ASTError.new("postfix components must be in back")
114
+ end
115
+
116
+ num_postfix = index_of_postfix_components.size
117
+
118
+ unless num_postfix + 1 == components.size
119
+ raise Keisan::Exceptions::ASTError.new("have too many components")
120
+ end
121
+
122
+ [
123
+ node_of_component(components[0]),
124
+ index_of_postfix_components.map {|i| components[i]}
125
+ ]
126
+ end
127
+
128
+ def node_of_component(component)
129
+ case component
130
+ when Parsing::Number
131
+ AST::Number.new(component.value)
132
+ when Parsing::String
133
+ AST::String.new(component.value)
134
+ when Parsing::Null
135
+ AST::Null.new
136
+ when Parsing::Variable
137
+ AST::Variable.new(component.name)
138
+ when Parsing::Boolean
139
+ AST::Boolean.new(component.value)
140
+ when Parsing::List
141
+ AST::List.new(
142
+ component.arguments.map {|parsing_argument|
143
+ Builder.new(components: parsing_argument.components).node
144
+ }
145
+ )
146
+ when Parsing::RoundGroup
147
+ Builder.new(components: component.components).node
148
+ when Parsing::CurlyGroup
149
+ Block.new(Builder.new(components: component.components).node)
150
+ when Parsing::Function
151
+ AST::Function.new(
152
+ component.arguments.map {|parsing_argument|
153
+ Builder.new(components: parsing_argument.components).node
154
+ },
155
+ component.name
156
+ )
157
+ when Parsing::DotWord
158
+ AST::Function.new(
159
+ [node_of_component(component.target)],
160
+ component.name
161
+ )
162
+ when Parsing::DotOperator
163
+ AST::Function.new(
164
+ [node_of_component(component.target)] + component.arguments.map {|parsing_argument|
165
+ Builder.new(components: parsing_argument.components).node
166
+ },
167
+ component.name
168
+ )
169
+ else
170
+ raise Exceptions::ASTError.new("Unhandled component, #{component}")
171
+ end
172
+ end
173
+
174
+ def consume_operators!
175
+ loop do
176
+ break if @priorities.empty?
177
+ max_priority = @priorities.max
178
+ break if max_priority < 0
179
+
180
+ consume_operators_with_priority!(max_priority)
181
+ end
182
+ end
183
+
184
+ def consume_operators_with_priority!(priority)
185
+ p_indexes = @priorities.map.with_index.select {|p,index| p == priority}
186
+ # :left, :right, or :none
187
+ associativity = AST::Operator.associativity_of_priority(priority)
188
+
189
+ if associativity == :right
190
+ index = p_indexes[-1][1]
191
+ else
192
+ index = p_indexes[0][1]
193
+ end
194
+
195
+ operator = @nodes[index]
196
+
197
+ # If has unary operators after, must process those first
198
+ if @nodes[index+1].is_a?(Keisan::Parsing::UnaryOperator)
199
+ loop do
200
+ break if !@nodes[index+1].is_a?(Keisan::Parsing::UnaryOperator)
201
+ index += 1
202
+ end
203
+ operator = @nodes[index]
204
+ end
205
+
206
+ # operator is the current operator to process, and index is its index
207
+ if operator.is_a?(Keisan::Parsing::UnaryOperator)
208
+ replacement_node = operator.node_class.new(
209
+ children = [@nodes[index+1]]
210
+ )
211
+ @nodes.delete_if.with_index {|node, i| i >= index && i <= index+1}
212
+ @priorities.delete_if.with_index {|node, i| i >= index && i <= index+1}
213
+ @nodes.insert(index, replacement_node)
214
+ @priorities.insert(index, -1)
215
+ elsif operator.is_a?(Keisan::Parsing::Operator)
216
+ replacement_node = operator.node_class.new(
217
+ children = [@nodes[index-1],@nodes[index+1]],
218
+ parsing_operators = [operator]
219
+ )
220
+ @nodes.delete_if.with_index {|node, i| i >= index-1 && i <= index+1}
221
+ @priorities.delete_if.with_index {|node, i| i >= index-1 && i <= index+1}
222
+ @nodes.insert(index-1, replacement_node)
223
+ @priorities.insert(index-1, -1)
224
+ else
225
+ raise Keisan::Exceptions::ASTError.new("Can only consume operators")
226
+ end
227
+ end
228
+ end
229
+ end
230
+ end