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.
- checksums.yaml +4 -4
- data/README.md +57 -9
- data/lib/keisan.rb +21 -12
- data/lib/keisan/ast.rb +50 -0
- data/lib/keisan/ast/arithmetic_operator.rb +0 -3
- data/lib/keisan/ast/assignment.rb +72 -0
- data/lib/keisan/ast/bitwise_and.rb +4 -4
- data/lib/keisan/ast/bitwise_operator.rb +0 -3
- data/lib/keisan/ast/bitwise_or.rb +4 -4
- data/lib/keisan/ast/bitwise_xor.rb +4 -4
- data/lib/keisan/ast/boolean.rb +25 -1
- data/lib/keisan/ast/builder.rb +98 -63
- data/lib/keisan/ast/constant_literal.rb +13 -0
- data/lib/keisan/ast/exponent.rb +62 -8
- data/lib/keisan/ast/function.rb +37 -26
- data/lib/keisan/ast/functions/diff.rb +57 -0
- data/lib/keisan/ast/functions/if.rb +47 -0
- data/lib/keisan/ast/indexing.rb +44 -4
- data/lib/keisan/ast/list.rb +4 -0
- data/lib/keisan/ast/literal.rb +8 -0
- data/lib/keisan/ast/logical_and.rb +9 -4
- data/lib/keisan/ast/logical_equal.rb +4 -4
- data/lib/keisan/ast/logical_greater_than.rb +4 -4
- data/lib/keisan/ast/logical_greater_than_or_equal_to.rb +4 -4
- data/lib/keisan/ast/logical_less_than.rb +4 -4
- data/lib/keisan/ast/logical_less_than_or_equal_to.rb +4 -4
- data/lib/keisan/ast/logical_not_equal.rb +4 -4
- data/lib/keisan/ast/logical_operator.rb +0 -3
- data/lib/keisan/ast/logical_or.rb +10 -5
- data/lib/keisan/ast/modulo.rb +4 -4
- data/lib/keisan/ast/node.rb +132 -20
- data/lib/keisan/ast/null.rb +1 -1
- data/lib/keisan/ast/number.rb +172 -1
- data/lib/keisan/ast/operator.rb +66 -8
- data/lib/keisan/ast/parent.rb +50 -0
- data/lib/keisan/ast/plus.rb +38 -4
- data/lib/keisan/ast/string.rb +10 -1
- data/lib/keisan/ast/times.rb +47 -4
- data/lib/keisan/ast/unary_bitwise_not.rb +9 -1
- data/lib/keisan/ast/unary_identity.rb +18 -1
- data/lib/keisan/ast/unary_inverse.rb +35 -1
- data/lib/keisan/ast/unary_logical_not.rb +5 -1
- data/lib/keisan/ast/unary_minus.rb +31 -1
- data/lib/keisan/ast/unary_operator.rb +29 -1
- data/lib/keisan/ast/unary_plus.rb +14 -1
- data/lib/keisan/ast/variable.rb +44 -0
- data/lib/keisan/calculator.rb +2 -2
- data/lib/keisan/context.rb +32 -16
- data/lib/keisan/evaluator.rb +10 -65
- data/lib/keisan/exceptions.rb +2 -0
- data/lib/keisan/function.rb +11 -5
- data/lib/keisan/function_definition_context.rb +34 -0
- data/lib/keisan/functions/default_registry.rb +13 -5
- data/lib/keisan/functions/diff.rb +82 -0
- data/lib/keisan/functions/expression_function.rb +63 -0
- data/lib/keisan/functions/if.rb +62 -0
- data/lib/keisan/functions/proc_function.rb +52 -0
- data/lib/keisan/functions/rand.rb +1 -1
- data/lib/keisan/functions/registry.rb +20 -10
- data/lib/keisan/functions/replace.rb +49 -0
- data/lib/keisan/functions/sample.rb +1 -1
- data/lib/keisan/parser.rb +13 -1
- data/lib/keisan/parsing/assignment.rb +9 -0
- data/lib/keisan/parsing/operator.rb +8 -0
- data/lib/keisan/parsing/unary_operator.rb +1 -1
- data/lib/keisan/tokenizer.rb +1 -0
- data/lib/keisan/tokens/assignment.rb +15 -0
- data/lib/keisan/variables/default_registry.rb +4 -4
- data/lib/keisan/variables/registry.rb +17 -8
- data/lib/keisan/version.rb +1 -1
- metadata +15 -3
- data/lib/keisan/ast/priorities.rb +0 -27
data/lib/keisan/function.rb
CHANGED
@@ -2,14 +2,20 @@ module Keisan
|
|
2
2
|
class Function
|
3
3
|
attr_reader :name
|
4
4
|
|
5
|
-
def initialize(name
|
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
|
12
|
-
|
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
|
32
|
+
Proc.new {|*args|
|
26
33
|
Math.send(method, *args)
|
27
|
-
|
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,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
|
-
|
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
|
-
|
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::
|
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
|