keisan 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|