keisan 0.7.0 → 0.8.4

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 (69) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +6 -3
  3. data/README.md +47 -3
  4. data/keisan.gemspec +5 -5
  5. data/lib/keisan.rb +9 -3
  6. data/lib/keisan/ast.rb +25 -0
  7. data/lib/keisan/ast/bitwise_left_shift.rb +17 -0
  8. data/lib/keisan/ast/bitwise_right_shift.rb +17 -0
  9. data/lib/keisan/ast/block.rb +4 -0
  10. data/lib/keisan/ast/boolean.rb +1 -1
  11. data/lib/keisan/ast/builder.rb +2 -2
  12. data/lib/keisan/ast/cell.rb +10 -0
  13. data/lib/keisan/ast/date.rb +23 -0
  14. data/lib/keisan/ast/date_time_methods.rb +75 -0
  15. data/lib/keisan/ast/function.rb +9 -0
  16. data/lib/keisan/ast/function_assignment.rb +16 -6
  17. data/lib/keisan/ast/hash.rb +4 -0
  18. data/lib/keisan/ast/logical_and.rb +20 -3
  19. data/lib/keisan/ast/logical_equal.rb +6 -5
  20. data/lib/keisan/ast/logical_greater_than.rb +6 -4
  21. data/lib/keisan/ast/logical_greater_than_or_equal_to.rb +6 -4
  22. data/lib/keisan/ast/logical_less_than.rb +6 -4
  23. data/lib/keisan/ast/logical_less_than_or_equal_to.rb +6 -4
  24. data/lib/keisan/ast/logical_not_equal.rb +6 -5
  25. data/lib/keisan/ast/logical_operator.rb +24 -0
  26. data/lib/keisan/ast/logical_or.rb +18 -1
  27. data/lib/keisan/ast/node.rb +25 -0
  28. data/lib/keisan/ast/number.rb +24 -0
  29. data/lib/keisan/ast/operator.rb +3 -1
  30. data/lib/keisan/ast/parent.rb +5 -1
  31. data/lib/keisan/ast/plus.rb +10 -0
  32. data/lib/keisan/ast/time.rb +23 -0
  33. data/lib/keisan/ast/unary_inverse.rb +1 -1
  34. data/lib/keisan/ast/unary_operator.rb +1 -1
  35. data/lib/keisan/ast/variable.rb +10 -9
  36. data/lib/keisan/calculator.rb +17 -3
  37. data/lib/keisan/context.rb +27 -10
  38. data/lib/keisan/evaluator.rb +16 -4
  39. data/lib/keisan/exceptions.rb +3 -0
  40. data/lib/keisan/function.rb +6 -0
  41. data/lib/keisan/functions/break.rb +11 -0
  42. data/lib/keisan/functions/cmath_function.rb +3 -1
  43. data/lib/keisan/functions/continue.rb +11 -0
  44. data/lib/keisan/functions/default_registry.rb +39 -0
  45. data/lib/keisan/functions/enumerable_function.rb +10 -2
  46. data/lib/keisan/functions/expression_function.rb +16 -9
  47. data/lib/keisan/functions/filter.rb +6 -0
  48. data/lib/keisan/functions/loop_control_flow_function.rb +22 -0
  49. data/lib/keisan/functions/map.rb +6 -0
  50. data/lib/keisan/functions/proc_function.rb +2 -2
  51. data/lib/keisan/functions/reduce.rb +5 -0
  52. data/lib/keisan/functions/replace.rb +6 -6
  53. data/lib/keisan/functions/while.rb +7 -1
  54. data/lib/keisan/parser.rb +7 -5
  55. data/lib/keisan/parsing/bitwise_left_shift.rb +9 -0
  56. data/lib/keisan/parsing/bitwise_right_shift.rb +9 -0
  57. data/lib/keisan/parsing/function.rb +1 -1
  58. data/lib/keisan/parsing/hash.rb +2 -2
  59. data/lib/keisan/string_and_group_parser.rb +229 -0
  60. data/lib/keisan/token.rb +1 -1
  61. data/lib/keisan/tokenizer.rb +20 -18
  62. data/lib/keisan/tokens/assignment.rb +3 -1
  63. data/lib/keisan/tokens/bitwise_shift.rb +23 -0
  64. data/lib/keisan/tokens/group.rb +1 -7
  65. data/lib/keisan/tokens/string.rb +2 -4
  66. data/lib/keisan/util.rb +19 -0
  67. data/lib/keisan/variables/default_registry.rb +2 -1
  68. data/lib/keisan/version.rb +1 -1
  69. metadata +40 -28
@@ -29,8 +29,7 @@ module Keisan
29
29
 
30
30
  def evaluate
31
31
  # Blocks might have local variable/function definitions, so skip check
32
- verify_rhs_of_function_assignment_is_valid! unless rhs.is_a?(Block)
33
-
32
+ verify_rhs_of_function_assignment_is_valid!
34
33
  context.register_function!(lhs.name, expression_function, local: local)
35
34
  rhs
36
35
  end
@@ -38,15 +37,26 @@ module Keisan
38
37
  private
39
38
 
40
39
  def verify_rhs_of_function_assignment_is_valid!
41
- # Only variables that can appear are those that are arguments to the function
42
- unless rhs.unbound_variables(context) <= Set.new(argument_names)
43
- raise Exceptions::InvalidExpression.new("Unbound variables found in function definition")
44
- end
40
+ verify_unbound_functions!
41
+ verify_unbound_variables!
42
+ end
43
+
44
+ def verify_unbound_functions!
45
45
  # Cannot have undefined functions unless allowed by context
46
46
  unless context.allow_recursive || rhs.unbound_functions(context).empty?
47
47
  raise Exceptions::InvalidExpression.new("Unbound function definitions are not allowed by current context")
48
48
  end
49
49
  end
50
+
51
+ def verify_unbound_variables!
52
+ # We allow unbound variables inside block statements, as they could be temporary
53
+ # variables assigned locally
54
+ return if rhs.is_a?(Block)
55
+ # Only variables that can appear are those that are arguments to the function
56
+ unless rhs.unbound_variables(context) <= Set.new(argument_names)
57
+ raise Exceptions::InvalidExpression.new("Unbound variables found in function definition")
58
+ end
59
+ end
50
60
  end
51
61
  end
52
62
  end
@@ -21,6 +21,10 @@ module Keisan
21
21
  end
22
22
  end
23
23
 
24
+ def contains_a?(klass)
25
+ super || @hash.any? {|k, v| k.to_node.contains_a?(klass) || v.contains_a?(klass) }
26
+ end
27
+
24
28
  def evaluate(context = nil)
25
29
  context ||= Context.new
26
30
 
@@ -5,18 +5,35 @@ module Keisan
5
5
  :"&&"
6
6
  end
7
7
 
8
+ def blank_value
9
+ true
10
+ end
11
+
8
12
  def evaluate(context = nil)
9
- children[0].evaluate(context).and(children[1].evaluate(context))
13
+ short_circuit_do(:evaluate, context)
10
14
  end
11
15
 
12
- def blank_value
13
- true
16
+ def simplify(context = nil)
17
+ short_circuit_do(:simplify, context)
14
18
  end
15
19
 
16
20
  def value(context = nil)
17
21
  context ||= Context.new
18
22
  children[0].value(context) && children[1].value(context)
19
23
  end
24
+
25
+ private
26
+
27
+ def short_circuit_do(method, context)
28
+ context ||= Context.new
29
+ lhs = children[0].send(method, context)
30
+ case lhs
31
+ when AST::Boolean
32
+ lhs.false? ? AST::Boolean.new(false) : children[1].send(method, context)
33
+ else
34
+ lhs.and(children[1].send(method, context))
35
+ end
36
+ end
20
37
  end
21
38
  end
22
39
  end
@@ -5,13 +5,14 @@ module Keisan
5
5
  :"=="
6
6
  end
7
7
 
8
- def evaluate(context = nil)
9
- children[0].evaluate(context).equal(children[1].evaluate(context))
8
+ protected
9
+
10
+ def value_operator
11
+ :==
10
12
  end
11
13
 
12
- def value(context=nil)
13
- context ||= Context.new
14
- children[0].value(context) == children[1].value(context)
14
+ def operator
15
+ :equal
15
16
  end
16
17
  end
17
18
  end
@@ -5,12 +5,14 @@ module Keisan
5
5
  :">"
6
6
  end
7
7
 
8
- def evaluate(context = nil)
9
- children[0].evaluate(context) > children[1].evaluate(context)
8
+ protected
9
+
10
+ def value_operator
11
+ :>
10
12
  end
11
13
 
12
- def value(context = nil)
13
- children.first.value(context) > children.last.value(context)
14
+ def operator
15
+ :>
14
16
  end
15
17
  end
16
18
  end
@@ -5,12 +5,14 @@ module Keisan
5
5
  :">="
6
6
  end
7
7
 
8
- def evaluate(context = nil)
9
- children[0].evaluate(context) >= children[1].evaluate(context)
8
+ protected
9
+
10
+ def value_operator
11
+ :>=
10
12
  end
11
13
 
12
- def value(context = nil)
13
- children.first.value(context) >= children.last.value(context)
14
+ def operator
15
+ :>=
14
16
  end
15
17
  end
16
18
  end
@@ -5,12 +5,14 @@ module Keisan
5
5
  :"<"
6
6
  end
7
7
 
8
- def evaluate(context = nil)
9
- children[0].evaluate(context) < children[1].evaluate(context)
8
+ protected
9
+
10
+ def value_operator
11
+ :<
10
12
  end
11
13
 
12
- def value(context = nil)
13
- children.first.value(context) < children.last.value(context)
14
+ def operator
15
+ :<
14
16
  end
15
17
  end
16
18
  end
@@ -5,12 +5,14 @@ module Keisan
5
5
  :"<="
6
6
  end
7
7
 
8
- def evaluate(context = nil)
9
- children[0].evaluate(context) <= children[1].evaluate(context)
8
+ protected
9
+
10
+ def value_operator
11
+ :<=
10
12
  end
11
13
 
12
- def value(context = nil)
13
- children.first.value(context) <= children.last.value(context)
14
+ def operator
15
+ :<=
14
16
  end
15
17
  end
16
18
  end
@@ -5,13 +5,14 @@ module Keisan
5
5
  :"!="
6
6
  end
7
7
 
8
- def evaluate(context = nil)
9
- children[0].evaluate(context).not_equal(children[1].evaluate(context))
8
+ protected
9
+
10
+ def value_operator
11
+ :!=
10
12
  end
11
13
 
12
- def value(context=nil)
13
- context ||= Context.new
14
- children[0].value(context) != children[1].value(context)
14
+ def operator
15
+ :not_equal
15
16
  end
16
17
  end
17
18
  end
@@ -1,6 +1,30 @@
1
1
  module Keisan
2
2
  module AST
3
3
  class LogicalOperator < Operator
4
+ def evaluate(context = nil)
5
+ context ||= Context.new
6
+ children[0].evaluate(context).send(operator, children[1].evaluate(context))
7
+ end
8
+
9
+ def simplify(context = nil)
10
+ context ||= Context.new
11
+ children[0].simplify(context).send(operator, children[1].simplify(context))
12
+ end
13
+
14
+ def value(context=nil)
15
+ context ||= Context.new
16
+ children[0].value(context).send(value_operator, children[1].value(context))
17
+ end
18
+
19
+ protected
20
+
21
+ def value_operator
22
+ raise Exceptions::NotImplementedError.new
23
+ end
24
+
25
+ def operator
26
+ raise Exceptions::NotImplementedError.new
27
+ end
4
28
  end
5
29
  end
6
30
  end
@@ -10,13 +10,30 @@ module Keisan
10
10
  end
11
11
 
12
12
  def evaluate(context = nil)
13
- children[0].evaluate(context).or(children[1].evaluate(context))
13
+ short_circuit_do(:evaluate, context)
14
+ end
15
+
16
+ def simplify(context = nil)
17
+ short_circuit_do(:simplify, context)
14
18
  end
15
19
 
16
20
  def value(context = nil)
17
21
  context ||= Context.new
18
22
  children[0].value(context) || children[1].value(context)
19
23
  end
24
+
25
+ private
26
+
27
+ def short_circuit_do(method, context)
28
+ context ||= Context.new
29
+ lhs = children[0].send(method, context)
30
+ case lhs
31
+ when AST::Boolean
32
+ lhs.true? ? AST::Boolean.new(true) : children[1].send(method, context)
33
+ else
34
+ lhs.or(children[1].send(method, context))
35
+ end
36
+ end
20
37
  end
21
38
  end
22
39
  end
@@ -37,6 +37,15 @@ module Keisan
37
37
  value(context)
38
38
  end
39
39
 
40
+ def contains_a?(klass)
41
+ case klass
42
+ when Array
43
+ klass.any? {|k| is_a?(k) }
44
+ else
45
+ is_a?(klass)
46
+ end
47
+ end
48
+
40
49
  def evaluate_assignments(context = nil)
41
50
  self
42
51
  end
@@ -45,10 +54,18 @@ module Keisan
45
54
  raise Exceptions::NonDifferentiableError.new
46
55
  end
47
56
 
57
+ def differentiated(variable, context = nil)
58
+ deep_dup.differentiate(variable, context)
59
+ end
60
+
48
61
  def replace(variable, replacement)
49
62
  self
50
63
  end
51
64
 
65
+ def replaced(variable, replacement)
66
+ deep_dup.replace(variable, replacement)
67
+ end
68
+
52
69
  def coerce(other)
53
70
  [other.to_node, self]
54
71
  end
@@ -132,6 +149,14 @@ module Keisan
132
149
  BitwiseOr.new([self, other.to_node])
133
150
  end
134
151
 
152
+ def <<(other)
153
+ BitwiseLeftShift.new([self, other.to_node])
154
+ end
155
+
156
+ def >>(other)
157
+ BitwiseRightShift.new([self, other.to_node])
158
+ end
159
+
135
160
  def >(other)
136
161
  LogicalGreaterThan.new([self, other.to_node])
137
162
  end
@@ -24,6 +24,10 @@ module Keisan
24
24
  case other
25
25
  when Number
26
26
  Number.new(value + other.value)
27
+ when Date
28
+ Date.new(other.value + value)
29
+ when Time
30
+ Time.new(other.value + value)
27
31
  else
28
32
  super
29
33
  end
@@ -107,6 +111,26 @@ module Keisan
107
111
  end
108
112
  end
109
113
 
114
+ def <<(other)
115
+ other = other.to_node
116
+ case other
117
+ when Number
118
+ Number.new(value << other.value)
119
+ else
120
+ super
121
+ end
122
+ end
123
+
124
+ def >>(other)
125
+ other = other.to_node
126
+ case other
127
+ when Number
128
+ Number.new(value >> other.value)
129
+ else
130
+ super
131
+ end
132
+ end
133
+
110
134
  def >(other)
111
135
  other = other.to_node
112
136
  case other
@@ -13,6 +13,8 @@ module Keisan
13
13
  "%": [2, 85, :left], # Modulo
14
14
  "+": [2, 80, :left], # Plus
15
15
  # "-": [2, 80, :left], # Minus
16
+ "<<": [2, 75, :left], # Bitwise left shift
17
+ ">>": [2, 75, :left], # Bitwise right shift
16
18
  "&": [2, 70, :left], # Bitwise and
17
19
  "^": [2, 65, :left], # Bitwise xor
18
20
  "|": [2, 65, :left], # Bitwise or
@@ -41,7 +43,7 @@ module Keisan
41
43
  raise Exceptions::ASTError.new("Mismatch of children and operators")
42
44
  end
43
45
 
44
- children = Array.wrap(children)
46
+ children = Array(children)
45
47
  super(children)
46
48
 
47
49
  @parsing_operators = parsing_operators
@@ -4,7 +4,7 @@ module Keisan
4
4
  attr_reader :children
5
5
 
6
6
  def initialize(children = [])
7
- children = Array.wrap(children).map do |child|
7
+ children = Array(children).map do |child|
8
8
  child.is_a?(Cell) ? child : child.to_node
9
9
  end
10
10
  raise Exceptions::InternalError.new unless children.is_a?(Array)
@@ -25,6 +25,10 @@ module Keisan
25
25
  end
26
26
  end
27
27
 
28
+ def contains_a?(klass)
29
+ super || children.any? {|child| child.contains_a?(klass) }
30
+ end
31
+
28
32
  def freeze
29
33
  children.each(&:freeze)
30
34
  super
@@ -22,6 +22,10 @@ module Keisan
22
22
  # Special case of array concatenation
23
23
  elsif children_values.all? {|child| child.is_a?(::Array)}
24
24
  children_values.inject([], &:+)
25
+ elsif children_values.one? {|child| child.is_a?(::Date)}
26
+ date_time_plus(children_values, ::Date)
27
+ elsif children_values.one? {|child| child.is_a?(::Time)}
28
+ date_time_plus(children_values, ::Time)
25
29
  else
26
30
  children_values.inject(0, &:+)
27
31
  end.to_node.value(context)
@@ -77,6 +81,12 @@ module Keisan
77
81
 
78
82
  private
79
83
 
84
+ def date_time_plus(elements, klass)
85
+ date_time = elements.select {|child| child.is_a?(klass)}.first
86
+ others = elements.select {|child| !child.is_a?(klass)}
87
+ date_time + others.inject(0, &:+)
88
+ end
89
+
80
90
  def convert_minus_to_plus!
81
91
  @parsing_operators.each.with_index do |parsing_operator, index|
82
92
  if parsing_operator.is_a?(Parsing::Minus)
@@ -0,0 +1,23 @@
1
+ require_relative "date_time_methods"
2
+
3
+ module Keisan
4
+ module AST
5
+ class Time < ConstantLiteral
6
+ include DateTimeMethods
7
+
8
+ attr_reader :time
9
+
10
+ def initialize(time)
11
+ @time = time
12
+ end
13
+
14
+ def value(context = nil)
15
+ time
16
+ end
17
+
18
+ def to_s
19
+ value.strftime("%Y-%m-%d %H:%M:%S")
20
+ end
21
+ end
22
+ end
23
+ end