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
@@ -19,7 +19,7 @@ module Keisan
19
19
  @children = [child.simplify(context)]
20
20
  case child
21
21
  when Number
22
- Number.new(Rational(1,child.value(context))).simplify(context)
22
+ Number.new(child.value**-1)
23
23
  else
24
24
  (child ** -1).simplify(context)
25
25
  end
@@ -2,7 +2,7 @@ module Keisan
2
2
  module AST
3
3
  class UnaryOperator < Operator
4
4
  def initialize(children = [])
5
- children = Array.wrap(children)
5
+ children = Array(children)
6
6
  super(children)
7
7
  if children.count != 1
8
8
  raise Exceptions::ASTError.new("Unary operator takes has a single child")
@@ -13,7 +13,13 @@ module Keisan
13
13
 
14
14
  def value(context = nil)
15
15
  context ||= Context.new
16
- variable_node_from_context(context).value(context)
16
+ node = variable_node_from_context(context)
17
+ case node
18
+ when Variable
19
+ node
20
+ else
21
+ node.value(context)
22
+ end
17
23
  end
18
24
 
19
25
  def unbound_variables(context = nil)
@@ -60,11 +66,8 @@ module Keisan
60
66
  end
61
67
 
62
68
  def replace(variable, replacement)
63
- if name == variable.name
64
- replacement
65
- else
66
- self
67
- end
69
+ to_replace_name = variable.is_a?(::String) ? variable : variable.name
70
+ name == to_replace_name ? replacement : self
68
71
  end
69
72
 
70
73
  def differentiate(variable, context = nil)
@@ -81,9 +84,7 @@ module Keisan
81
84
 
82
85
  def variable_node_from_context(context)
83
86
  variable = context.variable(name)
84
- if variable.is_a?(Cell)
85
- variable = variable.node
86
- end
87
+ variable = variable.node if variable.is_a?(Cell)
87
88
  variable
88
89
  end
89
90
  end
@@ -2,8 +2,14 @@ module Keisan
2
2
  class Calculator
3
3
  attr_reader :context
4
4
 
5
- def initialize(context: nil, allow_recursive: false)
6
- @context = context || Context.new(allow_recursive: allow_recursive)
5
+ # Note, allow_recursive would be more appropriately named:
6
+ # allow_unbound_functions_in_function_definitions, but it is too late for that.
7
+ def initialize(context: nil, allow_recursive: false, allow_blocks: true, allow_multiline: true)
8
+ @context = context || Context.new(
9
+ allow_recursive: allow_recursive,
10
+ allow_blocks: allow_blocks,
11
+ allow_multiline: allow_multiline
12
+ )
7
13
  end
8
14
 
9
15
  def allow_recursive
@@ -14,6 +20,14 @@ module Keisan
14
20
  context.allow_recursive!
15
21
  end
16
22
 
23
+ def allow_blocks
24
+ context.allow_blocks
25
+ end
26
+
27
+ def allow_multiline
28
+ context.allow_multiline
29
+ end
30
+
17
31
  def evaluate(expression, definitions = {})
18
32
  Evaluator.new(self).evaluate(expression, definitions)
19
33
  end
@@ -23,7 +37,7 @@ module Keisan
23
37
  end
24
38
 
25
39
  def ast(expression)
26
- Evaluator.new(self).ast(expression)
40
+ Evaluator.new(self).parse_ast(expression)
27
41
  end
28
42
 
29
43
  def define_variable!(name, value)
@@ -1,13 +1,24 @@
1
1
  module Keisan
2
2
  class Context
3
- attr_reader :function_registry, :variable_registry, :allow_recursive
4
-
5
- def initialize(parent: nil, random: nil, allow_recursive: false, shadowed: [])
3
+ attr_reader :function_registry,
4
+ :variable_registry,
5
+ :allow_recursive,
6
+ :allow_multiline,
7
+ :allow_blocks
8
+
9
+ def initialize(parent: nil,
10
+ random: nil,
11
+ allow_recursive: false,
12
+ allow_multiline: true,
13
+ allow_blocks: true,
14
+ shadowed: [])
6
15
  @parent = parent
7
- @function_registry = Functions::Registry.new(parent: @parent.try(:function_registry))
8
- @variable_registry = Variables::Registry.new(parent: @parent.try(:variable_registry), shadowed: shadowed)
16
+ @function_registry = Functions::Registry.new(parent: @parent&.function_registry)
17
+ @variable_registry = Variables::Registry.new(parent: @parent&.variable_registry, shadowed: shadowed)
9
18
  @random = random
10
19
  @allow_recursive = allow_recursive
20
+ @allow_multiline = allow_multiline
21
+ @allow_blocks = allow_blocks
11
22
  end
12
23
 
13
24
  def allow_recursive!
@@ -47,7 +58,7 @@ module Keisan
47
58
 
48
59
  def transient_definitions
49
60
  return {} unless @transient
50
- parent_definitions = @parent.present? ? @parent.transient_definitions : {}
61
+ parent_definitions = @parent.nil? ? {} : @parent.transient_definitions
51
62
  parent_definitions.merge(
52
63
  @variable_registry.locals
53
64
  ).merge(
@@ -73,7 +84,7 @@ module Keisan
73
84
 
74
85
  def register_variable!(name, value, local: false)
75
86
  if !@variable_registry.shadowed.member?(name) && (transient? || !local && @parent&.variable_is_modifiable?(name))
76
- @parent.register_variable!(name, value)
87
+ @parent.register_variable!(name, value, local: local)
77
88
  else
78
89
  @variable_registry.register!(name, value)
79
90
  end
@@ -93,14 +104,14 @@ module Keisan
93
104
 
94
105
  def register_function!(name, function, local: false)
95
106
  if transient? || !local && @parent&.function_is_modifiable?(name)
96
- @parent.register_function!(name, function)
107
+ @parent.register_function!(name, function, local: local)
97
108
  else
98
109
  @function_registry.register!(name.to_s, function)
99
110
  end
100
111
  end
101
112
 
102
113
  def random
103
- @random || @parent.try(:random) || Random.new
114
+ @random || @parent&.random || Random.new
104
115
  end
105
116
 
106
117
  protected
@@ -110,7 +121,13 @@ module Keisan
110
121
  end
111
122
 
112
123
  def pure_child(shadowed: [])
113
- self.class.new(parent: self, shadowed: shadowed, allow_recursive: allow_recursive)
124
+ self.class.new(
125
+ parent: self,
126
+ shadowed: shadowed,
127
+ allow_recursive: allow_recursive,
128
+ allow_multiline: allow_multiline,
129
+ allow_blocks: allow_blocks
130
+ )
114
131
  end
115
132
  end
116
133
  end
@@ -8,7 +8,7 @@ module Keisan
8
8
 
9
9
  def evaluate(expression, definitions = {})
10
10
  context = calculator.context.spawn_child(definitions: definitions, transient: true)
11
- ast = ast(expression)
11
+ ast = parse_ast(expression)
12
12
  last_line = last_line(ast)
13
13
 
14
14
  evaluation = ast.evaluated(context)
@@ -24,16 +24,28 @@ module Keisan
24
24
 
25
25
  def simplify(expression, definitions = {})
26
26
  context = calculator.context.spawn_child(definitions: definitions, transient: true)
27
- ast = AST.parse(expression)
27
+ ast = parse_ast(expression)
28
28
  ast.simplify(context)
29
29
  end
30
30
 
31
- def ast(expression)
32
- AST.parse(expression)
31
+ def parse_ast(expression)
32
+ AST.parse(expression).tap do |ast|
33
+ disallowed = disallowed_nodes
34
+ if !disallowed.empty? && ast.contains_a?(disallowed)
35
+ raise Keisan::Exceptions::InvalidExpression.new("Context does not permit expressions with #{disallowed}")
36
+ end
37
+ end
33
38
  end
34
39
 
35
40
  private
36
41
 
42
+ def disallowed_nodes
43
+ disallowed = []
44
+ disallowed << Keisan::AST::Block unless calculator.allow_blocks
45
+ disallowed << Keisan::AST::MultiLine unless calculator.allow_multiline
46
+ disallowed
47
+ end
48
+
37
49
  def last_line(ast)
38
50
  ast.is_a?(AST::MultiLine) ? ast.children.last : ast
39
51
  end
@@ -18,5 +18,8 @@ module Keisan
18
18
  class InvalidExpression < StandardError; end
19
19
  class TypeError < StandardError; end
20
20
  class NonDifferentiableError < StandardError; end
21
+
22
+ class BreakError < StandardError; end
23
+ class ContinueError < StandardError; end
21
24
  end
22
25
  end
@@ -23,6 +23,12 @@ module Keisan
23
23
  raise Exceptions::NotImplementedError.new
24
24
  end
25
25
 
26
+ def unbound_variables(children, context)
27
+ children.inject(Set.new) do |vars, child|
28
+ vars | child.unbound_variables(context)
29
+ end
30
+ end
31
+
26
32
  protected
27
33
 
28
34
  def validate_arguments!(count)
@@ -0,0 +1,11 @@
1
+ require_relative "loop_control_flow_function"
2
+
3
+ module Keisan
4
+ module Functions
5
+ class Break < LoopControlFlowFuntion
6
+ def initialize
7
+ super("break", Exceptions::BreakError)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -4,7 +4,9 @@ module Keisan
4
4
  module Functions
5
5
  class CMathFunction < MathFunction
6
6
  def initialize(name, proc_function = nil)
7
- super(name, proc_function || Proc.new {|arg| CMath.send(name, arg)})
7
+ super(name, proc_function || Proc.new {|arg|
8
+ CMath.send(name, arg)
9
+ })
8
10
  end
9
11
  end
10
12
  end
@@ -0,0 +1,11 @@
1
+ require_relative "loop_control_flow_function"
2
+
3
+ module Keisan
4
+ module Functions
5
+ class Continue < LoopControlFlowFuntion
6
+ def initialize
7
+ super("continue", Exceptions::ContinueError)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,5 +1,7 @@
1
1
  require_relative "let"
2
2
  require_relative "puts"
3
+ require_relative "break"
4
+ require_relative "continue"
3
5
 
4
6
  require_relative "if"
5
7
  require_relative "while"
@@ -49,6 +51,8 @@ module Keisan
49
51
  def self.register_defaults!(registry)
50
52
  registry.register!(:let, Let.new, force: true)
51
53
  registry.register!(:puts, Puts.new, force: true)
54
+ registry.register!(:break, Break.new, force: true)
55
+ registry.register!(:continue, Continue.new, force: true)
52
56
 
53
57
  registry.register!(:if, If.new, force: true)
54
58
  registry.register!(:while, While.new, force: true)
@@ -65,6 +69,7 @@ module Keisan
65
69
  register_math!(registry)
66
70
  register_array_methods!(registry)
67
71
  register_random_methods!(registry)
72
+ register_date_time_methods!(registry)
68
73
  end
69
74
 
70
75
  def self.register_math!(registry)
@@ -124,6 +129,40 @@ module Keisan
124
129
  registry.register!(:rand, Rand.new, force: true)
125
130
  registry.register!(:sample, Sample.new, force: true)
126
131
  end
132
+
133
+ def self.register_date_time_methods!(registry)
134
+ register_date_time!(registry)
135
+
136
+ registry.register!(:today, Proc.new { ::Date.today }, force: true)
137
+ registry.register!(:day, Proc.new {|d| d.mday }, force: true)
138
+ registry.register!(:weekday, Proc.new {|d| d.wday }, force: true)
139
+ registry.register!(:month, Proc.new {|d| d.month }, force: true)
140
+ registry.register!(:year, Proc.new {|d| d.year }, force: true)
141
+
142
+ registry.register!(:now, Proc.new { ::Time.now }, force: true)
143
+ registry.register!(:hour, Proc.new {|t| t.hour }, force: true)
144
+ registry.register!(:minute, Proc.new {|t| t.min }, force: true)
145
+ registry.register!(:second, Proc.new {|t| t.sec }, force: true)
146
+ registry.register!(:strftime, Proc.new {|*args| args.first.strftime(*args[1..-1]) }, force: true)
147
+
148
+ registry.register!(:to_time, Proc.new {|d| d.to_time }, force: true)
149
+ registry.register!(:to_date, Proc.new {|t| t.to_date }, force: true)
150
+
151
+ registry.register!(:epoch_seconds, Proc.new {|d| d.to_time - Time.new(1970, 1, 1, 0, 0, 0) }, force: true)
152
+ registry.register!(:epoch_days, Proc.new {|t| t.to_date - Date.new(1970, 1, 1) }, force: true)
153
+ end
154
+
155
+ def self.register_date_time!(registry)
156
+ [::Date, ::Time].each do |klass|
157
+ registry.register!(klass.to_s.downcase.to_sym, Proc.new {|*args|
158
+ if args.count == 1 && args.first.is_a?(::String)
159
+ AST.const_get(klass.to_s).new(klass.parse(args.first))
160
+ else
161
+ AST.const_get(klass.to_s).new(klass.new(*args))
162
+ end
163
+ }, force: true)
164
+ end
165
+ end
127
166
  end
128
167
  end
129
168
  end
@@ -12,6 +12,10 @@ module Keisan
12
12
  evaluate(ast_function, context)
13
13
  end
14
14
 
15
+ def unbound_variables(children, context)
16
+ super - Set.new(shadowing_variable_names(children).map(&:name))
17
+ end
18
+
15
19
  def evaluate(ast_function, context = nil)
16
20
  validate_arguments!(ast_function.children.count)
17
21
  context ||= Context.new
@@ -20,9 +24,9 @@ module Keisan
20
24
 
21
25
  case operand
22
26
  when AST::List
23
- evaluate_list(operand, arguments, expression, context)
27
+ evaluate_list(operand, arguments, expression, context).evaluate(context)
24
28
  when AST::Hash
25
- evaluate_hash(operand, arguments, expression, context)
29
+ evaluate_hash(operand, arguments, expression, context).evaluate(context)
26
30
  else
27
31
  raise Exceptions::InvalidFunctionError.new("Unhandled first argument to #{name}: #{operand}")
28
32
  end
@@ -34,6 +38,10 @@ module Keisan
34
38
 
35
39
  protected
36
40
 
41
+ def shadowing_variable_names(children)
42
+ raise Exceptions::NotImplementedError.new
43
+ end
44
+
37
45
  def verify_arguments!(arguments)
38
46
  unless arguments.all? {|argument| argument.is_a?(AST::Variable)}
39
47
  raise Exceptions::InvalidFunctionError.new("Middle arguments to #{name} must be variables")
@@ -5,7 +5,11 @@ module Keisan
5
5
 
6
6
  def initialize(name, arguments, expression, transient_definitions)
7
7
  super(name, arguments.count)
8
- @expression = expression.deep_dup
8
+ if expression.is_a?(::String)
9
+ @expression = AST::parse(expression)
10
+ else
11
+ @expression = expression.deep_dup
12
+ end
9
13
  @arguments = arguments
10
14
  @transient_definitions = transient_definitions
11
15
  end
@@ -70,17 +74,20 @@ module Keisan
70
74
 
71
75
  local = local_context_for(context)
72
76
 
73
- # expression.differentiate(variable, context)
74
-
75
- argument_values = ast_function.children.map {|child| child.evaluate(local)}
77
+ argument_values = ast_function.children.map {|child| child.evaluated(local)}
76
78
 
77
79
  argument_derivatives = ast_function.children.map do |child|
78
- child.differentiate(variable, context)
80
+ child.differentiated(variable, context)
79
81
  end
80
82
 
83
+ partial_derivatives = calculate_partial_derivatives(context)
84
+
81
85
  AST::Plus.new(
82
86
  argument_derivatives.map.with_index {|argument_derivative, i|
83
- partial_derivative = partial_derivatives[i].replace(argument_variables[i], argument_values[i])
87
+ partial_derivative = partial_derivatives[i]
88
+ argument_variables.each.with_index {|argument_variable, j|
89
+ partial_derivative = partial_derivative.replaced(argument_variable, argument_values[j])
90
+ }
84
91
  AST::Times.new([argument_derivative, partial_derivative])
85
92
  }
86
93
  )
@@ -92,9 +99,9 @@ module Keisan
92
99
  @argument_variables ||= arguments.map {|argument| AST::Variable.new(argument)}
93
100
  end
94
101
 
95
- def partial_derivatives
96
- @partial_derivatives ||= argument_variables.map.with_index do |variable, i|
97
- partial_derivative = expression.differentiate(variable)
102
+ def calculate_partial_derivatives(context)
103
+ argument_variables.map.with_index do |variable, i|
104
+ partial_derivative = expression.differentiated(variable, context)
98
105
  end
99
106
  end
100
107
 
@@ -10,6 +10,12 @@ module Keisan
10
10
  super("filter")
11
11
  end
12
12
 
13
+ protected
14
+
15
+ def shadowing_variable_names(children)
16
+ children.size == 3 ? children[1..1] : children[1..2]
17
+ end
18
+
13
19
  private
14
20
 
15
21
  def evaluate_list(list, arguments, expression, context)
@@ -0,0 +1,22 @@
1
+ module Keisan
2
+ module Functions
3
+ class LoopControlFlowFuntion < Function
4
+ def initialize(name, exception_class)
5
+ super(name, 0)
6
+ @exception_class = exception_class
7
+ end
8
+
9
+ def value(ast_function, context = nil)
10
+ raise @exception_class.new
11
+ end
12
+
13
+ def evaluate(ast_function, context = nil)
14
+ raise @exception_class.new
15
+ end
16
+
17
+ def simplify(ast_function, context = nil)
18
+ raise @exception_class.new
19
+ end
20
+ end
21
+ end
22
+ end