keisan 0.1.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 +7 -0
- data/.gitignore +12 -0
- data/.rspec +1 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +19 -0
- data/README.md +188 -0
- data/Rakefile +6 -0
- data/bin/console +10 -0
- data/bin/setup +8 -0
- data/keisan.gemspec +31 -0
- data/lib/keisan.rb +118 -0
- data/lib/keisan/ast/arithmetic_operator.rb +9 -0
- data/lib/keisan/ast/bitwise_and.rb +21 -0
- data/lib/keisan/ast/bitwise_operator.rb +9 -0
- data/lib/keisan/ast/bitwise_or.rb +21 -0
- data/lib/keisan/ast/bitwise_xor.rb +21 -0
- data/lib/keisan/ast/boolean.rb +15 -0
- data/lib/keisan/ast/builder.rb +141 -0
- data/lib/keisan/ast/exponent.rb +25 -0
- data/lib/keisan/ast/function.rb +19 -0
- data/lib/keisan/ast/indexing.rb +16 -0
- data/lib/keisan/ast/list.rb +10 -0
- data/lib/keisan/ast/literal.rb +6 -0
- data/lib/keisan/ast/logical_and.rb +21 -0
- data/lib/keisan/ast/logical_greater_than.rb +17 -0
- data/lib/keisan/ast/logical_greater_than_or_equal_to.rb +17 -0
- data/lib/keisan/ast/logical_less_than.rb +17 -0
- data/lib/keisan/ast/logical_less_than_or_equal_to.rb +17 -0
- data/lib/keisan/ast/logical_operator.rb +9 -0
- data/lib/keisan/ast/logical_or.rb +21 -0
- data/lib/keisan/ast/node.rb +9 -0
- data/lib/keisan/ast/null.rb +12 -0
- data/lib/keisan/ast/number.rb +15 -0
- data/lib/keisan/ast/operator.rb +50 -0
- data/lib/keisan/ast/parent.rb +15 -0
- data/lib/keisan/ast/plus.rb +49 -0
- data/lib/keisan/ast/string.rb +15 -0
- data/lib/keisan/ast/times.rb +36 -0
- data/lib/keisan/ast/unary_bitwise_not.rb +9 -0
- data/lib/keisan/ast/unary_identity.rb +9 -0
- data/lib/keisan/ast/unary_inverse.rb +9 -0
- data/lib/keisan/ast/unary_logical_not.rb +9 -0
- data/lib/keisan/ast/unary_minus.rb +9 -0
- data/lib/keisan/ast/unary_operator.rb +13 -0
- data/lib/keisan/ast/unary_plus.rb +9 -0
- data/lib/keisan/ast/variable.rb +16 -0
- data/lib/keisan/calculator.rb +30 -0
- data/lib/keisan/context.rb +36 -0
- data/lib/keisan/exceptions.rb +19 -0
- data/lib/keisan/function.rb +15 -0
- data/lib/keisan/functions/default_registry.rb +58 -0
- data/lib/keisan/functions/rand.rb +22 -0
- data/lib/keisan/functions/registry.rb +50 -0
- data/lib/keisan/functions/sample.rb +20 -0
- data/lib/keisan/parser.rb +211 -0
- data/lib/keisan/parsing/argument.rb +6 -0
- data/lib/keisan/parsing/arithmetic_operator.rb +6 -0
- data/lib/keisan/parsing/bitwise_and.rb +9 -0
- data/lib/keisan/parsing/bitwise_not.rb +9 -0
- data/lib/keisan/parsing/bitwise_not_not.rb +9 -0
- data/lib/keisan/parsing/bitwise_operator.rb +6 -0
- data/lib/keisan/parsing/bitwise_or.rb +9 -0
- data/lib/keisan/parsing/bitwise_xor.rb +9 -0
- data/lib/keisan/parsing/boolean.rb +11 -0
- data/lib/keisan/parsing/component.rb +6 -0
- data/lib/keisan/parsing/divide.rb +9 -0
- data/lib/keisan/parsing/element.rb +6 -0
- data/lib/keisan/parsing/exponent.rb +9 -0
- data/lib/keisan/parsing/function.rb +12 -0
- data/lib/keisan/parsing/group.rb +11 -0
- data/lib/keisan/parsing/indexing.rb +14 -0
- data/lib/keisan/parsing/list.rb +10 -0
- data/lib/keisan/parsing/logical_and.rb +9 -0
- data/lib/keisan/parsing/logical_greater_than.rb +9 -0
- data/lib/keisan/parsing/logical_greater_than_or_equal_to.rb +9 -0
- data/lib/keisan/parsing/logical_less_than.rb +9 -0
- data/lib/keisan/parsing/logical_less_than_or_equal_to.rb +9 -0
- data/lib/keisan/parsing/logical_not.rb +9 -0
- data/lib/keisan/parsing/logical_not_not.rb +9 -0
- data/lib/keisan/parsing/logical_operator.rb +6 -0
- data/lib/keisan/parsing/logical_or.rb +9 -0
- data/lib/keisan/parsing/minus.rb +9 -0
- data/lib/keisan/parsing/null.rb +6 -0
- data/lib/keisan/parsing/number.rb +10 -0
- data/lib/keisan/parsing/operator.rb +13 -0
- data/lib/keisan/parsing/plus.rb +9 -0
- data/lib/keisan/parsing/round_group.rb +6 -0
- data/lib/keisan/parsing/square_group.rb +6 -0
- data/lib/keisan/parsing/string.rb +10 -0
- data/lib/keisan/parsing/times.rb +9 -0
- data/lib/keisan/parsing/unary_minus.rb +9 -0
- data/lib/keisan/parsing/unary_operator.rb +9 -0
- data/lib/keisan/parsing/unary_plus.rb +9 -0
- data/lib/keisan/parsing/variable.rb +11 -0
- data/lib/keisan/token.rb +25 -0
- data/lib/keisan/tokenizer.rb +41 -0
- data/lib/keisan/tokens/arithmetic_operator.rb +29 -0
- data/lib/keisan/tokens/bitwise_operator.rb +29 -0
- data/lib/keisan/tokens/boolean.rb +20 -0
- data/lib/keisan/tokens/comma.rb +11 -0
- data/lib/keisan/tokens/group.rb +28 -0
- data/lib/keisan/tokens/logical_operator.rb +38 -0
- data/lib/keisan/tokens/null.rb +15 -0
- data/lib/keisan/tokens/number.rb +24 -0
- data/lib/keisan/tokens/operator.rb +9 -0
- data/lib/keisan/tokens/string.rb +15 -0
- data/lib/keisan/tokens/word.rb +11 -0
- data/lib/keisan/variables/default_registry.rb +20 -0
- data/lib/keisan/variables/registry.rb +41 -0
- data/lib/keisan/version.rb +3 -0
- metadata +238 -0
@@ -0,0 +1,50 @@
|
|
1
|
+
module Keisan
|
2
|
+
module AST
|
3
|
+
class Operator < Parent
|
4
|
+
def initialize(children = [], parsing_operators = [])
|
5
|
+
unless children.count == parsing_operators.count + 1
|
6
|
+
raise Keisan::Exceptions::ASTError.new("Mismatch of children and operators")
|
7
|
+
end
|
8
|
+
|
9
|
+
unless arity.include?(children.count)
|
10
|
+
raise Keisan::Exceptions::ASTError.new("Invalid number of arguments")
|
11
|
+
end
|
12
|
+
|
13
|
+
children = Array.wrap(children)
|
14
|
+
super(children)
|
15
|
+
|
16
|
+
@parsing_operators = parsing_operators
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def arity
|
21
|
+
raise Keisan::Exceptions::NotImplementedError.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def associativity
|
25
|
+
raise Keisan::Exceptions::NotImplementedError.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def symbol
|
29
|
+
raise Keisan::Exceptions::NotImplementedError.new
|
30
|
+
end
|
31
|
+
|
32
|
+
def blank_value
|
33
|
+
raise Keisan::Exceptions::NotImplementedError.new
|
34
|
+
end
|
35
|
+
|
36
|
+
def value(context = nil)
|
37
|
+
args = children
|
38
|
+
args = args.reverse if associativity == :right
|
39
|
+
|
40
|
+
args.inject(blank_value) do |result, child|
|
41
|
+
if associativity == :left
|
42
|
+
result.send(symbol, child.value(context))
|
43
|
+
else
|
44
|
+
child.value(context).send(symbol, result)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Keisan
|
2
|
+
module AST
|
3
|
+
class Parent < Node
|
4
|
+
attr_reader :children
|
5
|
+
|
6
|
+
def initialize(children = [])
|
7
|
+
children = Array.wrap(children)
|
8
|
+
unless children.is_a?(Array) && children.all? {|children| children.is_a?(Node)}
|
9
|
+
raise Keisan::Exceptions::InternalError.new
|
10
|
+
end
|
11
|
+
@children = children
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Keisan
|
2
|
+
module AST
|
3
|
+
class Plus < ArithmeticOperator
|
4
|
+
def initialize(children = [], parsing_operators = [])
|
5
|
+
super
|
6
|
+
convert_minus_to_plus!
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.priority
|
10
|
+
10
|
11
|
+
end
|
12
|
+
|
13
|
+
def arity
|
14
|
+
2..Float::INFINITY
|
15
|
+
end
|
16
|
+
|
17
|
+
def symbol
|
18
|
+
:+
|
19
|
+
end
|
20
|
+
|
21
|
+
def blank_value
|
22
|
+
0
|
23
|
+
end
|
24
|
+
|
25
|
+
def value(context = nil)
|
26
|
+
children_values = children.map {|child| child.value(context)}
|
27
|
+
# Special case of string concatenation
|
28
|
+
if children_values.all? {|child| child.is_a?(::String)}
|
29
|
+
children_values.join
|
30
|
+
# Special case of array concatenation
|
31
|
+
elsif children_values.all? {|child| child.is_a?(::Array)}
|
32
|
+
children_values.inject([], &:+)
|
33
|
+
else
|
34
|
+
children_values.inject(0, &:+)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def convert_minus_to_plus!
|
41
|
+
@parsing_operators.each.with_index do |parsing_operator, index|
|
42
|
+
if parsing_operator.is_a?(Keisan::Parsing::Minus)
|
43
|
+
@children[index+1] = Keisan::AST::UnaryMinus.new(@children[index+1])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Keisan
|
2
|
+
module AST
|
3
|
+
class Times < ArithmeticOperator
|
4
|
+
def initialize(children = [], parsing_operators = [])
|
5
|
+
super
|
6
|
+
convert_divide_to_inverse!
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.priority
|
10
|
+
20
|
11
|
+
end
|
12
|
+
|
13
|
+
def arity
|
14
|
+
2..Float::INFINITY
|
15
|
+
end
|
16
|
+
|
17
|
+
def symbol
|
18
|
+
:*
|
19
|
+
end
|
20
|
+
|
21
|
+
def blank_value
|
22
|
+
1
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def convert_divide_to_inverse!
|
28
|
+
@parsing_operators.each.with_index do |parsing_operator, index|
|
29
|
+
if parsing_operator.is_a?(Keisan::Parsing::Divide)
|
30
|
+
@children[index+1] = Keisan::AST::UnaryInverse.new(@children[index+1])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Keisan
|
2
|
+
module AST
|
3
|
+
class UnaryOperator < Parent
|
4
|
+
def initialize(children = [])
|
5
|
+
children = Array.wrap(children)
|
6
|
+
super
|
7
|
+
if children.count != 1
|
8
|
+
raise Keisan::Exceptions::ASTError.new("Unary operator takes has a single child")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Keisan
|
2
|
+
module AST
|
3
|
+
class Variable < Literal
|
4
|
+
attr_reader :name
|
5
|
+
|
6
|
+
def initialize(name)
|
7
|
+
@name = name
|
8
|
+
end
|
9
|
+
|
10
|
+
def value(context = nil)
|
11
|
+
context = Keisan::Context.new if context.nil?
|
12
|
+
context.variable(name)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Keisan
|
2
|
+
class Calculator
|
3
|
+
attr_reader :context
|
4
|
+
|
5
|
+
def initialize(context = nil)
|
6
|
+
@context = context || Context.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def evaluate(expression, definitions = {})
|
10
|
+
local_context = context.spawn_child
|
11
|
+
definitions.each do |name, value|
|
12
|
+
case value
|
13
|
+
when Proc
|
14
|
+
local_context.register_function!(name, value)
|
15
|
+
else
|
16
|
+
local_context.register_variable!(name, value)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
Keisan::AST::Builder.new(string: expression).ast.value(local_context)
|
20
|
+
end
|
21
|
+
|
22
|
+
def define_variable!(name, value)
|
23
|
+
context.register_variable!(name, value)
|
24
|
+
end
|
25
|
+
|
26
|
+
def define_function!(name, function)
|
27
|
+
context.register_function!(name, function)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Keisan
|
2
|
+
class Context
|
3
|
+
attr_reader :function_registry, :variable_registry
|
4
|
+
|
5
|
+
def initialize(parent: nil, random: nil)
|
6
|
+
@parent = parent
|
7
|
+
@function_registry = Functions::Registry.new(parent: @parent.try(:function_registry))
|
8
|
+
@variable_registry = Variables::Registry.new(parent: @parent.try(:variable_registry))
|
9
|
+
@random = random
|
10
|
+
end
|
11
|
+
|
12
|
+
def spawn_child
|
13
|
+
self.class.new(parent: self)
|
14
|
+
end
|
15
|
+
|
16
|
+
def function(name)
|
17
|
+
@function_registry[name.to_s]
|
18
|
+
end
|
19
|
+
|
20
|
+
def register_function!(name, function)
|
21
|
+
@function_registry.register!(name.to_s, function)
|
22
|
+
end
|
23
|
+
|
24
|
+
def variable(name)
|
25
|
+
@variable_registry[name.to_s]
|
26
|
+
end
|
27
|
+
|
28
|
+
def register_variable!(name, value)
|
29
|
+
@variable_registry.register!(name.to_s, value)
|
30
|
+
end
|
31
|
+
|
32
|
+
def random
|
33
|
+
@random || @parent.try(:random) || Random.new
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Keisan
|
2
|
+
module Exceptions
|
3
|
+
class BaseError < ::StandardError; end
|
4
|
+
|
5
|
+
class InternalError < BaseError; end
|
6
|
+
class StandardError < BaseError; end
|
7
|
+
|
8
|
+
class NotImplementedError < InternalError; end
|
9
|
+
|
10
|
+
class InvalidToken < StandardError; end
|
11
|
+
class TokenizingError < StandardError; end
|
12
|
+
class ParseError < StandardError; end
|
13
|
+
class ASTError < StandardError; end
|
14
|
+
class InvalidFunctionError < StandardError; end
|
15
|
+
class UndefinedFunctionError < StandardError; end
|
16
|
+
class UndefinedVariableError < StandardError; end
|
17
|
+
class UnmodifiableError < StandardError; end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Keisan
|
2
|
+
class Function
|
3
|
+
attr_reader :name
|
4
|
+
|
5
|
+
def initialize(name, function_proc)
|
6
|
+
raise Keisan::Exceptions::InvalidFunctionError.new unless function_proc.is_a?(Proc)
|
7
|
+
@name = name
|
8
|
+
@function_proc = function_proc
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(context, *args)
|
12
|
+
@function_proc.call(*args)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require_relative "rand"
|
2
|
+
require_relative "sample"
|
3
|
+
|
4
|
+
module Keisan
|
5
|
+
module Functions
|
6
|
+
class DefaultRegistry
|
7
|
+
def self.registry
|
8
|
+
@registry ||= Registry.new.tap do |r|
|
9
|
+
register_defaults!(r)
|
10
|
+
end.freeze
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def self.register_defaults!(registry)
|
16
|
+
register_builtin_math!(registry)
|
17
|
+
register_branch_methods!(registry)
|
18
|
+
register_array_methods!(registry)
|
19
|
+
register_random_methods!(registry)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.register_builtin_math!(registry)
|
23
|
+
Math.methods(false).each do |method|
|
24
|
+
registry.register!(
|
25
|
+
method,
|
26
|
+
Proc.new do |*args|
|
27
|
+
Math.send(method, *args)
|
28
|
+
end
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
%i(exp sin cos).each do |method|
|
33
|
+
registry.register!(
|
34
|
+
:"c#{method}",
|
35
|
+
Proc.new do |z|
|
36
|
+
CMath.send(method, z)
|
37
|
+
end
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.register_branch_methods!(registry)
|
43
|
+
registry.register!(:if, Proc.new {|bool, a, b=nil| bool ? a : b })
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.register_array_methods!(registry)
|
47
|
+
%i(min max size).each do |method|
|
48
|
+
registry.register!(method, Proc.new {|a| a.send(method)})
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.register_random_methods!(registry)
|
53
|
+
registry.register!(:rand, Keisan::Functions::Rand.new)
|
54
|
+
registry.register!(:sample, Keisan::Functions::Sample.new)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|