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
@@ -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