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