keisan 0.3.0 → 0.4.0

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