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,141 @@
|
|
1
|
+
module Keisan
|
2
|
+
module AST
|
3
|
+
class Builder
|
4
|
+
# Build from parser
|
5
|
+
def initialize(string: nil, parser: nil, components: nil)
|
6
|
+
if string.nil? && parser.nil? && components.nil?
|
7
|
+
raise Keisan::Exceptions::InternalError.new("Require parser or components")
|
8
|
+
end
|
9
|
+
|
10
|
+
if string.present?
|
11
|
+
@components = Keisan::Parser.new(string: string).components
|
12
|
+
elsif parser.present?
|
13
|
+
@components = parser.components
|
14
|
+
else
|
15
|
+
@components = Array.wrap(components)
|
16
|
+
end
|
17
|
+
|
18
|
+
@nodes = @components.split {|component|
|
19
|
+
component.is_a?(Keisan::Parsing::Operator)
|
20
|
+
}.map {|group_of_components|
|
21
|
+
node_from_components(group_of_components)
|
22
|
+
}
|
23
|
+
@operators = @components.select {|component| component.is_a?(Keisan::Parsing::Operator)}
|
24
|
+
|
25
|
+
@priorities = @operators.map(&:priority)
|
26
|
+
|
27
|
+
while @operators.count > 0
|
28
|
+
priorities = @operators.map(&:priority)
|
29
|
+
max_priority = priorities.uniq.max
|
30
|
+
consume_operators_with_priority!(max_priority)
|
31
|
+
end
|
32
|
+
|
33
|
+
unless @nodes.count == 1
|
34
|
+
raise Keisan::Exceptions::ASTError.new("Should end up with a single node")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def node
|
39
|
+
@nodes.first
|
40
|
+
end
|
41
|
+
|
42
|
+
def ast
|
43
|
+
node
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def node_from_components(components)
|
49
|
+
index_of_unary_components = components.map.with_index {|c,i| [c,i]}.select {|c,i| c.is_a?(Keisan::Parsing::UnaryOperator)}.map(&:last)
|
50
|
+
# Must be all in the front
|
51
|
+
unless index_of_unary_components.map.with_index.all? {|i,j| i == j}
|
52
|
+
raise Keisan::Exceptions::ASTError.new("unary operators must be in front")
|
53
|
+
end
|
54
|
+
|
55
|
+
index_of_indexing_components = components.map.with_index {|c,i| [c,i]}.select {|c,i| c.is_a?(Keisan::Parsing::Indexing)}.map(&:last)
|
56
|
+
unless index_of_indexing_components.reverse.map.with_index.all? {|i,j| i + j == components.size - 1 }
|
57
|
+
raise Keisan::Exceptions::ASTError.new("indexing components must be in back")
|
58
|
+
end
|
59
|
+
|
60
|
+
num_unary = index_of_unary_components.size
|
61
|
+
num_indexing = index_of_indexing_components.size
|
62
|
+
|
63
|
+
unless num_unary + 1 + num_indexing == components.size
|
64
|
+
raise Keisan::Exceptions::ASTError.new("have too many components")
|
65
|
+
end
|
66
|
+
|
67
|
+
unary_components = index_of_unary_components.map {|i| components[i]}
|
68
|
+
indexing_components = index_of_indexing_components.map {|i| components[i]}
|
69
|
+
|
70
|
+
node = node_of_component(components[unary_components.size])
|
71
|
+
|
72
|
+
indexing_components.each do |indexing_component|
|
73
|
+
node = indexing_component.node_class.new(
|
74
|
+
node,
|
75
|
+
indexing_component.arguments.map {|parsing_argument|
|
76
|
+
Builder.new(components: parsing_argument.components).node
|
77
|
+
}
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
unary_components.reverse.each do |unary_component|
|
82
|
+
node = unary_component.node_class.new(node)
|
83
|
+
end
|
84
|
+
|
85
|
+
node
|
86
|
+
end
|
87
|
+
|
88
|
+
def node_of_component(component)
|
89
|
+
case component
|
90
|
+
when Keisan::Parsing::Number
|
91
|
+
Keisan::AST::Number.new(component.value)
|
92
|
+
when Keisan::Parsing::String
|
93
|
+
Keisan::AST::String.new(component.value)
|
94
|
+
when Keisan::Parsing::Null
|
95
|
+
Keisan::AST::Null.new
|
96
|
+
when Keisan::Parsing::Variable
|
97
|
+
Keisan::AST::Variable.new(component.name)
|
98
|
+
when Keisan::Parsing::Boolean
|
99
|
+
Keisan::AST::Boolean.new(component.value)
|
100
|
+
when Keisan::Parsing::List
|
101
|
+
Keisan::AST::List.new(
|
102
|
+
component.arguments.map {|parsing_argument|
|
103
|
+
Builder.new(components: parsing_argument.components).node
|
104
|
+
}
|
105
|
+
)
|
106
|
+
when Keisan::Parsing::Group
|
107
|
+
Builder.new(components: component.components).node
|
108
|
+
when Keisan::Parsing::Function
|
109
|
+
Keisan::AST::Function.new(
|
110
|
+
component.arguments.map {|parsing_argument|
|
111
|
+
Builder.new(components: parsing_argument.components).node
|
112
|
+
},
|
113
|
+
component.name
|
114
|
+
)
|
115
|
+
else
|
116
|
+
raise Keisan::Exceptions::ASTError.new("Unhandled component, #{component}")
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def consume_operators_with_priority!(priority)
|
121
|
+
# Treat back-to-back operators with same priority as one single call (e.g. 1 + 2 + 3 is add(1,2,3))
|
122
|
+
while @operators.any? {|operator| operator.priority == priority}
|
123
|
+
next_operator_group = @operators.each.with_index.to_a.split {|operator,i| operator.priority != priority}.select(&:present?).first
|
124
|
+
operator_group_indexes = next_operator_group.map(&:last)
|
125
|
+
|
126
|
+
first_index = operator_group_indexes.first
|
127
|
+
last_index = operator_group_indexes.last
|
128
|
+
|
129
|
+
replacement_node = next_operator_group.first.first.node_class.new(
|
130
|
+
children = @nodes[first_index..(last_index+1)],
|
131
|
+
parsing_operators = @operators[first_index..last_index]
|
132
|
+
)
|
133
|
+
|
134
|
+
@nodes.delete_if.with_index {|node, i| i >= first_index && i <= last_index+1}
|
135
|
+
@operators.delete_if.with_index {|node, i| i >= first_index && i <= last_index}
|
136
|
+
@nodes.insert(first_index, replacement_node)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Keisan
|
2
|
+
module AST
|
3
|
+
class Exponent < ArithmeticOperator
|
4
|
+
def self.priority
|
5
|
+
30
|
6
|
+
end
|
7
|
+
|
8
|
+
def arity
|
9
|
+
(2..2)
|
10
|
+
end
|
11
|
+
|
12
|
+
def associativity
|
13
|
+
:right
|
14
|
+
end
|
15
|
+
|
16
|
+
def symbol
|
17
|
+
:**
|
18
|
+
end
|
19
|
+
|
20
|
+
def blank_value
|
21
|
+
1
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Keisan
|
2
|
+
module AST
|
3
|
+
class Function < Parent
|
4
|
+
attr_reader :name
|
5
|
+
|
6
|
+
def initialize(arguments = [], name)
|
7
|
+
@name = name
|
8
|
+
super(arguments)
|
9
|
+
end
|
10
|
+
|
11
|
+
def value(context = nil)
|
12
|
+
context = Keisan::Context.new if context.nil?
|
13
|
+
argument_values = children.map {|child| child.value(context)}
|
14
|
+
function = context.function(name)
|
15
|
+
function.call(context, *argument_values)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Keisan
|
2
|
+
module AST
|
3
|
+
class Indexing < UnaryOperator
|
4
|
+
attr_reader :arguments
|
5
|
+
|
6
|
+
def initialize(child, arguments = [])
|
7
|
+
@children = [child]
|
8
|
+
@arguments = arguments
|
9
|
+
end
|
10
|
+
|
11
|
+
def value(context = nil)
|
12
|
+
return children.first.value(context).send(:[], *arguments.map {|arg| arg.value(context)})
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Keisan
|
2
|
+
module AST
|
3
|
+
class LogicalGreaterThan < LogicalOperator
|
4
|
+
def self.priority
|
5
|
+
82
|
6
|
+
end
|
7
|
+
|
8
|
+
def arity
|
9
|
+
2..2
|
10
|
+
end
|
11
|
+
|
12
|
+
def value(context = nil)
|
13
|
+
children.first.value(context) > children.last.value(context)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Keisan
|
2
|
+
module AST
|
3
|
+
class LogicalGreaterThanOrEqualTo < LogicalOperator
|
4
|
+
def self.priority
|
5
|
+
62
|
6
|
+
end
|
7
|
+
|
8
|
+
def arity
|
9
|
+
2..2
|
10
|
+
end
|
11
|
+
|
12
|
+
def value(context = nil)
|
13
|
+
children.first.value(context) >= children.last.value(context)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Keisan
|
2
|
+
module AST
|
3
|
+
class LogicalLessThan < LogicalOperator
|
4
|
+
def self.priority
|
5
|
+
72
|
6
|
+
end
|
7
|
+
|
8
|
+
def arity
|
9
|
+
2..2
|
10
|
+
end
|
11
|
+
|
12
|
+
def value(context = nil)
|
13
|
+
children.first.value(context) < children.last.value(context)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Keisan
|
2
|
+
module AST
|
3
|
+
class LogicalLessThanOrEqualTo < LogicalOperator
|
4
|
+
def self.priority
|
5
|
+
52
|
6
|
+
end
|
7
|
+
|
8
|
+
def arity
|
9
|
+
2..2
|
10
|
+
end
|
11
|
+
|
12
|
+
def value(context = nil)
|
13
|
+
children.first.value(context) <= children.last.value(context)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|