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