keisan 0.5.0 → 0.6.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 (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