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,14 +2,20 @@ module Keisan
2
2
  class Function
3
3
  attr_reader :name
4
4
 
5
- def initialize(name, function_proc)
6
- raise Keisan::Exceptions::InvalidFunctionError.new unless function_proc.is_a?(Proc)
5
+ def initialize(name)
7
6
  @name = name
8
- @function_proc = function_proc
9
7
  end
10
8
 
11
- def call(context, *args)
12
- @function_proc.call(*args)
9
+ def value(ast_function, context = nil)
10
+ raise Keisan::Exceptions::NotImplementedError.new
11
+ end
12
+
13
+ def evaluate(ast_function, context = nil)
14
+ raise Keisan::Exceptions::NotImplementedError.new
15
+ end
16
+
17
+ def simplify(ast_function, context = nil)
18
+ raise Keisan::Exceptions::NotImplementedError.new
13
19
  end
14
20
  end
15
21
  end
@@ -0,0 +1,34 @@
1
+ module Keisan
2
+ class FunctionDefinitionContext < Context
3
+ def initialize(parent:, arguments:)
4
+ super(parent: parent)
5
+ @arguments = Set.new(arguments)
6
+ @arguments_context = Context.new
7
+ set_transient!
8
+ end
9
+
10
+ def variable(name)
11
+ if @arguments.member?(name)
12
+ @arguments_context.variable(name)
13
+ else
14
+ super
15
+ end
16
+ end
17
+
18
+ def has_variable?(name)
19
+ if @arguments.member?(name)
20
+ @arguments_context.has_variable?(name)
21
+ else
22
+ super
23
+ end
24
+ end
25
+
26
+ def register_variable!(name, value)
27
+ if @arguments.member?(name)
28
+ @arguments_context.register_variable!(name, value)
29
+ else
30
+ super
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,3 +1,6 @@
1
+ require_relative "if"
2
+ require_relative "diff"
3
+ require_relative "replace"
1
4
  require_relative "rand"
2
5
  require_relative "sample"
3
6
 
@@ -13,6 +16,10 @@ module Keisan
13
16
  private
14
17
 
15
18
  def self.register_defaults!(registry)
19
+ registry.register!(:if, Keisan::Functions::If.new, force: true)
20
+ registry.register!(:diff, Keisan::Functions::Diff.new, force: true)
21
+ registry.register!(:replace, Keisan::Functions::Replace.new, force: true)
22
+
16
23
  register_builtin_math!(registry)
17
24
  register_array_methods!(registry)
18
25
  register_random_methods!(registry)
@@ -22,22 +29,23 @@ module Keisan
22
29
  Math.methods(false).each do |method|
23
30
  registry.register!(
24
31
  method,
25
- Proc.new do |*args|
32
+ Proc.new {|*args|
26
33
  Math.send(method, *args)
27
- end
34
+ },
35
+ force: true
28
36
  )
29
37
  end
30
38
  end
31
39
 
32
40
  def self.register_array_methods!(registry)
33
41
  %i(min max size).each do |method|
34
- registry.register!(method, Proc.new {|a| a.send(method)})
42
+ registry.register!(method, Proc.new {|a| a.send(method)}, force: true)
35
43
  end
36
44
  end
37
45
 
38
46
  def self.register_random_methods!(registry)
39
- registry.register!(:rand, Keisan::Functions::Rand.new)
40
- registry.register!(:sample, Keisan::Functions::Sample.new)
47
+ registry.register!(:rand, Keisan::Functions::Rand.new, force: true)
48
+ registry.register!(:sample, Keisan::Functions::Sample.new, force: true)
41
49
  end
42
50
  end
43
51
  end
@@ -0,0 +1,82 @@
1
+ module Keisan
2
+ module Functions
3
+ class Diff < Keisan::Function
4
+ def initialize
5
+ @name = "diff"
6
+ end
7
+
8
+ def value(ast_function, context = nil)
9
+ context ||= Keisan::Context.new
10
+ evaluation = evaluate(ast_function, context)
11
+
12
+ if is_ast_derivative?(evaluation)
13
+ raise Keisan::Exceptions::NonDifferentiableError.new
14
+ else
15
+ evaluation.value(context)
16
+ end
17
+ end
18
+
19
+ def evaluate(ast_function, context = nil)
20
+ context ||= Context.new
21
+ function, vars = function_and_vars(ast_function)
22
+
23
+ vars.inject(function.evaluate(context)) do |result, variable|
24
+ result = differentiate(result, variable, context)
25
+ if !is_ast_derivative?(result)
26
+ result = result.evaluate(context)
27
+ end
28
+ result
29
+ end
30
+ end
31
+
32
+ def simplify(ast_function, context = nil)
33
+ context ||= Context.new
34
+ function, vars = function_and_vars(ast_function)
35
+
36
+ vars.inject(function.simplify(context)) do |result, variable|
37
+ result = differentiate(result, variable, context)
38
+ if !is_ast_derivative?(result)
39
+ result = result.simplify(context)
40
+ end
41
+ result
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def is_ast_derivative?(node)
48
+ node.is_a?(Keisan::AST::Function) && node.name == name
49
+ end
50
+
51
+ def differentiate(node, variable, context)
52
+ if node.unbound_variables(context).include?(variable.name)
53
+ node.differentiate(variable, context)
54
+ else
55
+ return AST::Number.new(0)
56
+ end
57
+ rescue Keisan::Exceptions::NonDifferentiableError => e
58
+ return AST::Function.new(
59
+ [node, variable],
60
+ "diff"
61
+ )
62
+ end
63
+
64
+ def function_and_vars(ast_function)
65
+ unless ast_function.is_a?(Keisan::AST::Function) && ast_function.name == name
66
+ raise Keisan::Exceptions::InvalidFunctionError.new("Must receive diff function")
67
+ end
68
+
69
+ vars = ast_function.children[1..-1]
70
+
71
+ unless vars.all? {|var| var.is_a?(AST::Variable)}
72
+ raise Keisan::Exceptions::InvalidFunctionError.new("Diff must differentiate with respect to variables")
73
+ end
74
+
75
+ [
76
+ ast_function.children.first,
77
+ vars
78
+ ]
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,63 @@
1
+ module Keisan
2
+ module Functions
3
+ class ExpressionFunction < Keisan::Function
4
+ attr_reader :arguments, :expression
5
+
6
+ def initialize(name, arguments, expression, local_context)
7
+ super(name)
8
+ @expression = expression.deep_dup
9
+ @arguments = arguments
10
+ @local_context = local_context
11
+ end
12
+
13
+ def call(context, *args)
14
+ unless @arguments.count == args.count
15
+ raise Keisan::Exceptions::InvalidFunctionError.new("Invalid number of arguments for #{name} function")
16
+ end
17
+
18
+ local = @local_context.spawn_child
19
+ arguments.each.with_index do |arg_name, i|
20
+ local.register_variable!(arg_name, args[i])
21
+ end
22
+
23
+ expression.value(local)
24
+ end
25
+
26
+ def value(ast_function, context = nil)
27
+ context ||= Keisan::Context.new
28
+ argument_values = ast_function.children.map {|child| child.value(context)}
29
+ call(context, *argument_values)
30
+ end
31
+
32
+ def evaluate(ast_function, context = nil)
33
+ context ||= Keisan::Context.new
34
+
35
+ ast_function.instance_variable_set(
36
+ :@children,
37
+ ast_function.children.map {|child| child.evaluate(context)}
38
+ )
39
+
40
+ if ast_function.children.all? {|child| child.well_defined?(context)}
41
+ value(ast_function, context).to_node.evaluate(context)
42
+ else
43
+ ast_function
44
+ end
45
+ end
46
+
47
+ def simplify(ast_function, context = nil)
48
+ context ||= Context.new
49
+
50
+ ast_function.instance_variable_set(
51
+ :@children,
52
+ ast_function.children.map {|child| child.evaluate(context)}
53
+ )
54
+
55
+ if ast_function.children.all? {|child| child.is_a?(Keisan::AST::ConstantLiteral)}
56
+ value(ast_function, context).to_node.simplify(context)
57
+ else
58
+ ast_function
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,62 @@
1
+ module Keisan
2
+ module Functions
3
+ class If < Keisan::Function
4
+ def initialize
5
+ @name = "if"
6
+ end
7
+
8
+ def value(ast_function, context = nil)
9
+ context ||= Keisan::Context.new
10
+
11
+ unless (2..3).cover? ast_function.children.size
12
+ raise Keisan::Exceptions::InvalidFunctionError.new("Require 2 or 3 arguments to if")
13
+ end
14
+
15
+ bool = ast_function.children[0].value(context)
16
+
17
+ if bool
18
+ ast_function.children[1].value(context)
19
+ else
20
+ ast_function.children.size == 3 ? ast_function.children[2].value(context) : nil
21
+ end
22
+ end
23
+
24
+ def evaluate(ast_function, context = nil)
25
+ context ||= Keisan::Context.new
26
+
27
+ bool = ast_function.children[0].evaluate(context)
28
+
29
+ if bool.is_a?(Keisan::AST::Boolean)
30
+ if bool.value
31
+ ast_function.children[1].evaluate(context)
32
+ else
33
+ ast_function.children[2].to_node.evaluate(context)
34
+ end
35
+ else
36
+ ast_function
37
+ end
38
+
39
+ if ast_function.children.all? {|child| child.well_defined?(context)}
40
+ value(ast_function, context).to_node.evaluate(context)
41
+ else
42
+ ast_function
43
+ end
44
+ end
45
+
46
+ def simplify(ast_function, context = nil)
47
+ context ||= Context.new
48
+ bool = ast_function.children[0].simplify(context)
49
+
50
+ if bool.is_a?(Keisan::AST::Boolean)
51
+ if bool.value
52
+ ast_function.children[1].simplify(context)
53
+ else
54
+ ast_function.children[2].to_node.simplify(context)
55
+ end
56
+ else
57
+ ast_function
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,52 @@
1
+ module Keisan
2
+ module Functions
3
+ class ProcFunction < Keisan::Function
4
+ def initialize(name, function_proc)
5
+ raise Keisan::Exceptions::InvalidFunctionError.new unless function_proc.is_a?(Proc)
6
+
7
+ super(name)
8
+ @function_proc = function_proc
9
+ end
10
+
11
+ def call(context, *args)
12
+ @function_proc.call(*args)
13
+ end
14
+
15
+ def value(ast_function, context = nil)
16
+ context ||= Keisan::Context.new
17
+ argument_values = ast_function.children.map {|child| child.value(context)}
18
+ call(context, *argument_values)
19
+ end
20
+
21
+ def evaluate(ast_function, context = nil)
22
+ context ||= Keisan::Context.new
23
+
24
+ ast_function.instance_variable_set(
25
+ :@children,
26
+ ast_function.children.map {|child| child.evaluate(context)}
27
+ )
28
+
29
+ if ast_function.children.all? {|child| child.well_defined?(context)}
30
+ value(ast_function, context).to_node.evaluate(context)
31
+ else
32
+ ast_function
33
+ end
34
+ end
35
+
36
+ def simplify(ast_function, context = nil)
37
+ context ||= Context.new
38
+
39
+ ast_function.instance_variable_set(
40
+ :@children,
41
+ ast_function.children.map {|child| child.evaluate(context)}
42
+ )
43
+
44
+ if ast_function.children.all? {|child| child.is_a?(Keisan::AST::ConstantLiteral)}
45
+ value(ast_function, context).to_node.simplify(context)
46
+ else
47
+ ast_function
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -1,6 +1,6 @@
1
1
  module Keisan
2
2
  module Functions
3
- class Rand < Keisan::Function
3
+ class Rand < ProcFunction
4
4
  def initialize
5
5
  @name = "rand"
6
6
  end
@@ -1,19 +1,23 @@
1
1
  module Keisan
2
2
  module Functions
3
3
  class Registry
4
- def initialize(functions: {}, parent: nil, use_defaults: true)
4
+ def initialize(functions: {}, parent: nil, use_defaults: true, force: false)
5
5
  @hash = {}
6
6
  @parent = parent
7
7
  @use_defaults = use_defaults
8
8
 
9
9
  functions.each do |name, function|
10
- register!(name, function)
10
+ register!(name, function, force: force)
11
11
  end
12
12
  end
13
13
 
14
14
  def [](name)
15
15
  return @hash[name] if @hash.has_key?(name)
16
- return @parent[name] if !@parent.nil? && @parent.has_name?(name)
16
+
17
+ if @parent && (parent_value = @parent[name])
18
+ return parent_value
19
+ end
20
+
17
21
  return default_registry[name] if @use_defaults && default_registry.has_name?(name)
18
22
  raise Keisan::Exceptions::UndefinedFunctionError.new name
19
23
  end
@@ -24,18 +28,17 @@ module Keisan
24
28
  false
25
29
  end
26
30
 
27
- # For checking if locally defined
28
- def has_name?(name)
29
- @hash.has_key?(name)
30
- end
31
-
32
- def register!(name, function)
31
+ def register!(name, function, force: false)
33
32
  raise Keisan::Exceptions::UnmodifiableError.new("Cannot modify frozen functions registry") if frozen?
34
33
  name = name.to_s
35
34
 
35
+ if !force && @use_defaults && default_registry.has_name?(name)
36
+ raise Keisan::Exceptions::UnmodifiableError.new("Cannot overwrite default function")
37
+ end
38
+
36
39
  case function
37
40
  when Proc
38
- self[name] = Keisan::Function.new(name, function)
41
+ self[name] = Keisan::Functions::ProcFunction.new(name, function)
39
42
  when Keisan::Function
40
43
  self[function.name] = function
41
44
  else
@@ -43,6 +46,13 @@ module Keisan
43
46
  end
44
47
  end
45
48
 
49
+ protected
50
+
51
+ # For checking if locally defined
52
+ def has_name?(name)
53
+ @hash.has_key?(name)
54
+ end
55
+
46
56
  private
47
57
 
48
58
  def []=(name, function)
@@ -0,0 +1,49 @@
1
+ module Keisan
2
+ module Functions
3
+ class Replace < Keisan::Function
4
+ def initialize
5
+ @name = "replace"
6
+ end
7
+
8
+ def value(ast_function, context = nil)
9
+ context ||= Keisan::Context.new
10
+ evaluate(ast_function, context).value(context)
11
+ end
12
+
13
+ def evaluate(ast_function, context = nil)
14
+ context ||= Keisan::Context.new
15
+ expression, variable, replacement = expression_variable_replacement(ast_function)
16
+
17
+ expression = expression.evaluate(context)
18
+ replacement = replacement.evaluate(context)
19
+
20
+ expression.replace(variable, replacement).evaluate(context)
21
+ end
22
+
23
+ def simplify(ast_function, context = nil)
24
+ context ||= Context.new
25
+ evaluate(ast_function, context).simplify(context)
26
+ end
27
+
28
+ private
29
+
30
+ def expression_variable_replacement(ast_function)
31
+ unless ast_function.is_a?(Keisan::AST::Function) && ast_function.name == name
32
+ raise Keisan::Exceptions::InvalidFunctionError.new("Must receive replace function")
33
+ end
34
+
35
+ unless ast_function.children.size == 3
36
+ raise Keisan::Exceptions::InvalidFunctionError.new("Require 3 arguments to replace")
37
+ end
38
+
39
+ expression, variable, replacement = *ast_function.children.map(&:deep_dup)
40
+
41
+ unless variable.is_a?(Keisan::AST::Variable)
42
+ raise Keisan::Exceptions::InvalidFunctionError.new("Replace must replace a variable")
43
+ end
44
+
45
+ [expression, variable, replacement]
46
+ end
47
+ end
48
+ end
49
+ end