keisan 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +57 -9
  3. data/lib/keisan.rb +21 -12
  4. data/lib/keisan/ast.rb +50 -0
  5. data/lib/keisan/ast/arithmetic_operator.rb +0 -3
  6. data/lib/keisan/ast/assignment.rb +72 -0
  7. data/lib/keisan/ast/bitwise_and.rb +4 -4
  8. data/lib/keisan/ast/bitwise_operator.rb +0 -3
  9. data/lib/keisan/ast/bitwise_or.rb +4 -4
  10. data/lib/keisan/ast/bitwise_xor.rb +4 -4
  11. data/lib/keisan/ast/boolean.rb +25 -1
  12. data/lib/keisan/ast/builder.rb +98 -63
  13. data/lib/keisan/ast/constant_literal.rb +13 -0
  14. data/lib/keisan/ast/exponent.rb +62 -8
  15. data/lib/keisan/ast/function.rb +37 -26
  16. data/lib/keisan/ast/functions/diff.rb +57 -0
  17. data/lib/keisan/ast/functions/if.rb +47 -0
  18. data/lib/keisan/ast/indexing.rb +44 -4
  19. data/lib/keisan/ast/list.rb +4 -0
  20. data/lib/keisan/ast/literal.rb +8 -0
  21. data/lib/keisan/ast/logical_and.rb +9 -4
  22. data/lib/keisan/ast/logical_equal.rb +4 -4
  23. data/lib/keisan/ast/logical_greater_than.rb +4 -4
  24. data/lib/keisan/ast/logical_greater_than_or_equal_to.rb +4 -4
  25. data/lib/keisan/ast/logical_less_than.rb +4 -4
  26. data/lib/keisan/ast/logical_less_than_or_equal_to.rb +4 -4
  27. data/lib/keisan/ast/logical_not_equal.rb +4 -4
  28. data/lib/keisan/ast/logical_operator.rb +0 -3
  29. data/lib/keisan/ast/logical_or.rb +10 -5
  30. data/lib/keisan/ast/modulo.rb +4 -4
  31. data/lib/keisan/ast/node.rb +132 -20
  32. data/lib/keisan/ast/null.rb +1 -1
  33. data/lib/keisan/ast/number.rb +172 -1
  34. data/lib/keisan/ast/operator.rb +66 -8
  35. data/lib/keisan/ast/parent.rb +50 -0
  36. data/lib/keisan/ast/plus.rb +38 -4
  37. data/lib/keisan/ast/string.rb +10 -1
  38. data/lib/keisan/ast/times.rb +47 -4
  39. data/lib/keisan/ast/unary_bitwise_not.rb +9 -1
  40. data/lib/keisan/ast/unary_identity.rb +18 -1
  41. data/lib/keisan/ast/unary_inverse.rb +35 -1
  42. data/lib/keisan/ast/unary_logical_not.rb +5 -1
  43. data/lib/keisan/ast/unary_minus.rb +31 -1
  44. data/lib/keisan/ast/unary_operator.rb +29 -1
  45. data/lib/keisan/ast/unary_plus.rb +14 -1
  46. data/lib/keisan/ast/variable.rb +44 -0
  47. data/lib/keisan/calculator.rb +2 -2
  48. data/lib/keisan/context.rb +32 -16
  49. data/lib/keisan/evaluator.rb +10 -65
  50. data/lib/keisan/exceptions.rb +2 -0
  51. data/lib/keisan/function.rb +11 -5
  52. data/lib/keisan/function_definition_context.rb +34 -0
  53. data/lib/keisan/functions/default_registry.rb +13 -5
  54. data/lib/keisan/functions/diff.rb +82 -0
  55. data/lib/keisan/functions/expression_function.rb +63 -0
  56. data/lib/keisan/functions/if.rb +62 -0
  57. data/lib/keisan/functions/proc_function.rb +52 -0
  58. data/lib/keisan/functions/rand.rb +1 -1
  59. data/lib/keisan/functions/registry.rb +20 -10
  60. data/lib/keisan/functions/replace.rb +49 -0
  61. data/lib/keisan/functions/sample.rb +1 -1
  62. data/lib/keisan/parser.rb +13 -1
  63. data/lib/keisan/parsing/assignment.rb +9 -0
  64. data/lib/keisan/parsing/operator.rb +8 -0
  65. data/lib/keisan/parsing/unary_operator.rb +1 -1
  66. data/lib/keisan/tokenizer.rb +1 -0
  67. data/lib/keisan/tokens/assignment.rb +15 -0
  68. data/lib/keisan/variables/default_registry.rb +4 -4
  69. data/lib/keisan/variables/registry.rb +17 -8
  70. data/lib/keisan/version.rb +1 -1
  71. metadata +15 -3
  72. data/lib/keisan/ast/priorities.rb +0 -27
@@ -2,7 +2,15 @@ module Keisan
2
2
  module AST
3
3
  class UnaryBitwiseNot < UnaryOperator
4
4
  def value(context = nil)
5
- return ~children.first.value(context)
5
+ return ~child.value(context)
6
+ end
7
+
8
+ def evaluate(context = nil)
9
+ ~child.evaluate(context)
10
+ end
11
+
12
+ def self.symbol
13
+ :"~"
6
14
  end
7
15
  end
8
16
  end
@@ -2,7 +2,24 @@ module Keisan
2
2
  module AST
3
3
  class UnaryIdentity < UnaryOperator
4
4
  def value(context = nil)
5
- return children.first.value(context)
5
+ return child.value(context)
6
+ end
7
+
8
+ def evaluate(context = nil)
9
+ child.evaluate(context)
10
+ end
11
+
12
+ def self.symbol
13
+ nil
14
+ end
15
+
16
+ def simplify(context = nil)
17
+ child.simplify(context)
18
+ end
19
+
20
+ def differentiate(variable, context = nil)
21
+ context ||= Context.new
22
+ child.differentiate(variable, context).simplify(context)
6
23
  end
7
24
  end
8
25
  end
@@ -2,7 +2,41 @@ module Keisan
2
2
  module AST
3
3
  class UnaryInverse < UnaryOperator
4
4
  def value(context = nil)
5
- return Rational(1, children.first.value(context))
5
+ return Rational(1, child.value(context))
6
+ end
7
+
8
+ def to_s
9
+ "(#{child.to_s})**(-1)"
10
+ end
11
+
12
+ def evaluate(context = nil)
13
+ 1.to_node / child.evaluate(context)
14
+ end
15
+
16
+ def simplify(context = nil)
17
+ context ||= Context.new
18
+
19
+ @children = [child.simplify(context)]
20
+ case child
21
+ when AST::Number
22
+ AST::Number.new(Rational(1,child.value(context))).simplify(context)
23
+ else
24
+ (child ** -1).simplify(context)
25
+ end
26
+ end
27
+
28
+ def differentiate(variable, context = nil)
29
+ context ||= Context.new
30
+ AST::Times.new(
31
+ [
32
+ AST::UnaryMinus.new(child.differentiate(variable, context)),
33
+ AST::UnaryInverse.new(
34
+ AST::Exponent.new([
35
+ child.deep_dup, AST::Number.new(2)
36
+ ])
37
+ )
38
+ ]
39
+ ).simplify(context)
6
40
  end
7
41
  end
8
42
  end
@@ -2,7 +2,11 @@ module Keisan
2
2
  module AST
3
3
  class UnaryLogicalNot < UnaryOperator
4
4
  def value(context = nil)
5
- return !children.first.value(context)
5
+ return !child.value(context)
6
+ end
7
+
8
+ def self.symbol
9
+ :"!"
6
10
  end
7
11
  end
8
12
  end
@@ -2,7 +2,37 @@ module Keisan
2
2
  module AST
3
3
  class UnaryMinus < UnaryOperator
4
4
  def value(context = nil)
5
- return -1 * children.first.value(context)
5
+ return -1 * child.value(context)
6
+ end
7
+
8
+ def evaluate(context = nil)
9
+ -child.evaluate(context)
10
+ end
11
+
12
+ def self.symbol
13
+ :"-"
14
+ end
15
+
16
+ def simplify(context = nil)
17
+ context ||= Context.new
18
+
19
+ case child
20
+ when AST::Number
21
+ AST::Number.new(-child.value(context)).simplify(context)
22
+ else
23
+ AST::Times.new([
24
+ AST::Number.new(-1),
25
+ child
26
+ ]).simplify(context)
27
+ end
28
+ end
29
+
30
+ def differentiate(variable, context = nil)
31
+ context ||= Context.new
32
+ AST::Times.new([
33
+ -1.to_node,
34
+ child.differentiate(variable, context)
35
+ ]).simplify(context)
6
36
  end
7
37
  end
8
38
  end
@@ -1,6 +1,6 @@
1
1
  module Keisan
2
2
  module AST
3
- class UnaryOperator < Parent
3
+ class UnaryOperator < Operator
4
4
  def initialize(children = [])
5
5
  children = Array.wrap(children)
6
6
  super
@@ -8,6 +8,34 @@ module Keisan
8
8
  raise Keisan::Exceptions::ASTError.new("Unary operator takes has a single child")
9
9
  end
10
10
  end
11
+
12
+ def child
13
+ children.first
14
+ end
15
+
16
+ def self.arity
17
+ ARITIES[:"u#{symbol}"]
18
+ end
19
+
20
+ def self.priority
21
+ PRIORITIES[:"u#{symbol}"]
22
+ end
23
+
24
+ def self.associativity
25
+ ASSOCIATIVITIES[:"u#{symbol}"]
26
+ end
27
+
28
+ def to_s
29
+ if child.is_a?(AST::Operator)
30
+ "#{symbol.to_s}(#{child.to_s})"
31
+ else
32
+ "#{symbol.to_s}#{child.to_s}"
33
+ end
34
+ end
35
+
36
+ def simplify(context = nil)
37
+ self
38
+ end
11
39
  end
12
40
  end
13
41
  end
@@ -1,9 +1,22 @@
1
1
  module Keisan
2
2
  module AST
3
- class UnaryPlus < UnaryOperator
3
+ class UnaryPlus < UnaryIdentity
4
4
  def value(context = nil)
5
5
  return children.first.value(context)
6
6
  end
7
+
8
+ def self.symbol
9
+ :"+"
10
+ end
11
+
12
+ def simplify(context = nil)
13
+ case child
14
+ when AST::Number
15
+ AST::Number.new(child.value(context)).simplify(context)
16
+ else
17
+ super
18
+ end
19
+ end
7
20
  end
8
21
  end
9
22
  end
@@ -16,6 +16,50 @@ module Keisan
16
16
  context ||= Keisan::Context.new
17
17
  context.has_variable?(name) ? Set.new : Set.new([name])
18
18
  end
19
+
20
+ def ==(other)
21
+ name == other.name
22
+ end
23
+
24
+ def to_s
25
+ name.to_s
26
+ end
27
+
28
+ def evaluate(context = nil)
29
+ context ||= Keisan::Context.new
30
+ if context.has_variable?(name)
31
+ context.variable(name).to_node.evaluate(context)
32
+ else
33
+ self
34
+ end
35
+ end
36
+
37
+ def simplify(context = nil)
38
+ context ||= Keisan::Context.new
39
+ if context.has_variable?(name)
40
+ context.variable(name).to_node.simplify(context)
41
+ else
42
+ self
43
+ end
44
+ end
45
+
46
+ def replace(variable, replacement)
47
+ if name == variable.name
48
+ replacement
49
+ else
50
+ self
51
+ end
52
+ end
53
+
54
+ def differentiate(variable, context = nil)
55
+ context ||= Keisan::Context.new
56
+
57
+ if name == variable.name && !context.has_variable?(name)
58
+ 1.to_node
59
+ else
60
+ 0.to_node
61
+ end
62
+ end
19
63
  end
20
64
  end
21
65
  end
@@ -2,8 +2,8 @@ module Keisan
2
2
  class Calculator
3
3
  attr_reader :context
4
4
 
5
- def initialize(context = nil)
6
- @context = context || Context.new
5
+ def initialize(context: nil, allow_recursive: false)
6
+ @context = context || Context.new(allow_recursive: allow_recursive)
7
7
  end
8
8
 
9
9
  def evaluate(expression, definitions = {})
@@ -1,16 +1,17 @@
1
1
  module Keisan
2
2
  class Context
3
- attr_reader :function_registry, :variable_registry
3
+ attr_reader :function_registry, :variable_registry, :allow_recursive
4
4
 
5
- def initialize(parent: nil, random: nil)
5
+ def initialize(parent: nil, random: nil, allow_recursive: false)
6
6
  @parent = parent
7
7
  @function_registry = Functions::Registry.new(parent: @parent.try(:function_registry))
8
8
  @variable_registry = Variables::Registry.new(parent: @parent.try(:variable_registry))
9
9
  @random = random
10
+ @allow_recursive = allow_recursive
10
11
  end
11
12
 
12
- def spawn_child(definitions = {})
13
- child = self.class.new(parent: self)
13
+ def spawn_child(definitions: {}, transient: false)
14
+ child = Context.new(parent: self, allow_recursive: allow_recursive)
14
15
 
15
16
  definitions.each do |name, value|
16
17
  case value
@@ -21,35 +22,50 @@ module Keisan
21
22
  end
22
23
  end
23
24
 
25
+ child.set_transient! if transient
24
26
  child
25
27
  end
26
28
 
27
- def function(name)
28
- @function_registry[name.to_s]
29
+ def variable(name)
30
+ @variable_registry[name.to_s]
29
31
  end
30
32
 
31
- def has_function?(name)
32
- @function_registry.has?(name)
33
+ def has_variable?(name)
34
+ @variable_registry.has?(name)
33
35
  end
34
36
 
35
- def register_function!(name, function)
36
- @function_registry.register!(name.to_s, function)
37
+ def register_variable!(name, value)
38
+ if @transient
39
+ @parent.register_variable!(name, value)
40
+ else
41
+ @variable_registry.register!(name.to_s, value)
42
+ end
37
43
  end
38
44
 
39
- def variable(name)
40
- @variable_registry[name.to_s]
45
+ def function(name)
46
+ @function_registry[name.to_s]
41
47
  end
42
48
 
43
- def has_variable?(name)
44
- @variable_registry.has?(name)
49
+ def has_function?(name)
50
+ @function_registry.has?(name)
45
51
  end
46
52
 
47
- def register_variable!(name, value)
48
- @variable_registry.register!(name.to_s, value)
53
+ def register_function!(name, function)
54
+ if @transient
55
+ @parent.register_function!(name, function)
56
+ else
57
+ @function_registry.register!(name.to_s, function)
58
+ end
49
59
  end
50
60
 
51
61
  def random
52
62
  @random || @parent.try(:random) || Random.new
53
63
  end
64
+
65
+ protected
66
+
67
+ def set_transient!
68
+ @transient = true
69
+ end
54
70
  end
55
71
  end
@@ -1,9 +1,5 @@
1
1
  module Keisan
2
2
  class Evaluator
3
- WORD = /[a-zA-Z_]\w*/
4
- VARIABLE_DEFINITION = /\A\s*(#{WORD})\s*=\s*(.+)\z/
5
- FUNCTION_DEFINITION = /\A\s*(#{WORD})\s*\(((?:\s*#{WORD}\s*)(?:,\s*#{WORD}\s*)*)\)\s*=\s*(.+)\z/
6
-
7
3
  attr_reader :calculator
8
4
 
9
5
  def initialize(calculator)
@@ -11,69 +7,18 @@ module Keisan
11
7
  end
12
8
 
13
9
  def evaluate(expression, definitions = {})
14
- case expression
15
- when VARIABLE_DEFINITION
16
- variable_evaluate(expression, definitions)
17
- when FUNCTION_DEFINITION
18
- function_evaluate(expression, definitions)
19
- else
20
- pure_evaluate(expression, definitions)
21
- end
22
- end
23
-
24
- private
25
-
26
- def variable_evaluate(expression, definitions = {})
27
- match = expression.match VARIABLE_DEFINITION
28
- name = match[1]
29
- expression = match[2]
30
- ast = Keisan::AST::Builder.new(string: expression).ast
31
-
32
- context = calculator.context.spawn_child(definitions)
33
- unbound_variables = ast.unbound_variables(context)
34
- unless unbound_variables <= Set.new([name])
35
- raise Keisan::Exceptions::InvalidExpression.new("Unbound variables found in variable definition")
36
- end
37
-
38
- calculator.context.register_variable!(name, ast.value(context))
39
- end
40
-
41
- def function_evaluate(expression, definitions = {})
42
- match = expression.match FUNCTION_DEFINITION
43
- name = match[1]
44
- args = match[2].split(",").map(&:strip)
45
- expression = match[3]
46
- ast = Keisan::AST::Builder.new(string: expression).ast
47
-
48
- context = calculator.context.spawn_child(definitions)
49
- unbound_variables = ast.unbound_variables(context)
50
- unbound_functions = ast.unbound_functions(context)
51
- # Ensure the variables are contained within the arguments
52
- unless unbound_variables <= Set.new(args) && unbound_functions <= Set.new([name])
53
- raise Keisan::Exceptions::InvalidExpression.new("Unbound variables found in function definition")
54
- end
55
-
56
- function = lambda do |*received_args|
57
- unless args.count == received_args.count
58
- raise Keisan::Exceptions::InvalidFunctionError.new("Invalid number of arguments for #{name} function")
59
- end
60
-
61
- local = calculator.context.spawn_child(definitions)
62
- args.each.with_index do |arg, i|
63
- local.register_variable!(arg, received_args[i])
10
+ context = calculator.context.spawn_child(definitions: definitions, transient: true)
11
+ ast = Keisan::AST.parse(expression)
12
+ evaluation = ast.evaluate(context)
13
+
14
+ case ast
15
+ when Keisan::AST::Assignment
16
+ if ast.children.first.is_a?(Keisan::AST::Variable)
17
+ context.variable(ast.children.first.name)
64
18
  end
65
-
66
- ast.value(local)
19
+ else
20
+ evaluation.value(context)
67
21
  end
68
-
69
- calculator.context.register_function!(
70
- name,
71
- Keisan::Function.new(name, function)
72
- )
73
- end
74
-
75
- def pure_evaluate(expression, definitions = {})
76
- Keisan::AST::Builder.new(string: expression).ast.value(calculator.context.spawn_child(definitions))
77
22
  end
78
23
  end
79
24
  end
@@ -16,5 +16,7 @@ module Keisan
16
16
  class UndefinedVariableError < StandardError; end
17
17
  class UnmodifiableError < StandardError; end
18
18
  class InvalidExpression < StandardError; end
19
+ class TypeError < StandardError; end
20
+ class NonDifferentiableError < StandardError; end
19
21
  end
20
22
  end