keisan 0.7.0 → 0.8.4

Sign up to get free protection for your applications and to get access to all the features.
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