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,22 @@
|
|
1
|
+
module Keisan
|
2
|
+
module Functions
|
3
|
+
class Rand < Keisan::Function
|
4
|
+
def initialize
|
5
|
+
@name = "rand"
|
6
|
+
end
|
7
|
+
|
8
|
+
# Single argument: integer in range [0, max)
|
9
|
+
# Double argument: integer in range [min, max)
|
10
|
+
def call(context, *args)
|
11
|
+
case args.size
|
12
|
+
when 1
|
13
|
+
context.random.rand(args.first)
|
14
|
+
when 2
|
15
|
+
context.random.rand(args.first...args.last)
|
16
|
+
else
|
17
|
+
raise Keisan::Exceptions::InvalidFunctionError.new
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Keisan
|
2
|
+
module Functions
|
3
|
+
class Registry
|
4
|
+
def initialize(functions: {}, parent: nil, use_defaults: true)
|
5
|
+
@hash = {}
|
6
|
+
@parent = parent
|
7
|
+
@use_defaults = use_defaults
|
8
|
+
|
9
|
+
functions.each do |name, function|
|
10
|
+
register!(name, function)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](name)
|
15
|
+
return @hash[name] if @hash.has_key?(name)
|
16
|
+
return @parent[name] if @parent.present? && @parent.has_name?(name)
|
17
|
+
return default_registry[name] if @use_defaults && default_registry.has_name?(name)
|
18
|
+
raise Keisan::Exceptions::UndefinedFunctionError.new name
|
19
|
+
end
|
20
|
+
|
21
|
+
def has_name?(name)
|
22
|
+
@hash.has_key?(name)
|
23
|
+
end
|
24
|
+
|
25
|
+
def register!(name, function)
|
26
|
+
raise Keisan::Exceptions::UnmodifiableError.new("Cannot modify frozen functions registry") if frozen?
|
27
|
+
name = name.to_s
|
28
|
+
|
29
|
+
case function
|
30
|
+
when Proc
|
31
|
+
self[name] = Keisan::Function.new(name, function)
|
32
|
+
when Keisan::Function
|
33
|
+
self[function.name] = function
|
34
|
+
else
|
35
|
+
raise Keisan::Exceptions::InvalidFunctionError.new
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def []=(name, function)
|
42
|
+
@hash[name] = function
|
43
|
+
end
|
44
|
+
|
45
|
+
def default_registry
|
46
|
+
DefaultRegistry.registry
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Keisan
|
2
|
+
module Functions
|
3
|
+
class Sample < Keisan::Function
|
4
|
+
def initialize
|
5
|
+
@name = "sample"
|
6
|
+
end
|
7
|
+
|
8
|
+
# Single argument: integer in range [0, max)
|
9
|
+
# Double argument: integer in range [min, max)
|
10
|
+
def call(context, *args)
|
11
|
+
case args.size
|
12
|
+
when 1
|
13
|
+
args.first.sample(random: context.random)
|
14
|
+
else
|
15
|
+
raise Keisan::Exceptions::InvalidFunctionError.new
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
module Keisan
|
2
|
+
class Parser
|
3
|
+
attr_reader :tokens, :components
|
4
|
+
|
5
|
+
def initialize(string: nil, tokens: nil)
|
6
|
+
if string.nil? && tokens.nil?
|
7
|
+
raise Keisan::Exceptions::InternalError.new("Invalid arguments")
|
8
|
+
end
|
9
|
+
|
10
|
+
if string.present?
|
11
|
+
@tokens = Tokenizer.new(string).tokens
|
12
|
+
else
|
13
|
+
raise Keisan::Exceptions::InternalError.new("Invalid argument: tokens = #{tokens}") if tokens.nil? || !tokens.is_a?(Array)
|
14
|
+
@tokens = tokens
|
15
|
+
end
|
16
|
+
|
17
|
+
@components = []
|
18
|
+
|
19
|
+
parse_components!
|
20
|
+
end
|
21
|
+
|
22
|
+
def ast
|
23
|
+
@ast ||= Keisan::AST::Builder.new(parser: self).ast
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def parse_components!
|
29
|
+
@unparsed_tokens = tokens.dup
|
30
|
+
|
31
|
+
# Components will store the elements (numbers, variables, bracket components, function calls)
|
32
|
+
# and the operators in between
|
33
|
+
while @unparsed_tokens.count > 0
|
34
|
+
token = @unparsed_tokens.shift
|
35
|
+
add_token_to_components!(token)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Elements are groups of tokens separated by (non-unary) operators
|
40
|
+
# The following are basic elements:
|
41
|
+
# number
|
42
|
+
# variable
|
43
|
+
# function (word + round group)
|
44
|
+
# list (square group)
|
45
|
+
#
|
46
|
+
# Additionally these can be modified by having any number of unary operators in front,
|
47
|
+
# and any number of indexing groups (square groups) at the back
|
48
|
+
#
|
49
|
+
def add_token_to_components!(token)
|
50
|
+
if @components.empty? || @components[-1].is_a?(Parsing::Operator)
|
51
|
+
# Expect an element or a unary operator
|
52
|
+
if token.type == :operator
|
53
|
+
# Here it must be a unary operator
|
54
|
+
add_unary_operator_to_components!(token)
|
55
|
+
else
|
56
|
+
# Here it must be an element
|
57
|
+
add_element_to_components!(token)
|
58
|
+
end
|
59
|
+
|
60
|
+
elsif @components[-1].is_a?(Parsing::UnaryOperator)
|
61
|
+
# Expect an element
|
62
|
+
case token.type
|
63
|
+
when :number, :string, :word, :group, :null, :boolean
|
64
|
+
add_element_to_components!(token)
|
65
|
+
else
|
66
|
+
raise Keisan::Exceptions::ParseError.new("Expected an element, received #{token.string}")
|
67
|
+
end
|
68
|
+
|
69
|
+
elsif @components[-1].is_a?(Parsing::Element)
|
70
|
+
if @components[-1].is_a?(Parsing::Variable) && token.type == :group && token.group_type == :round
|
71
|
+
add_function_to_components!(token)
|
72
|
+
elsif token.type == :group && token.group_type == :square
|
73
|
+
add_indexing_to_components!(token)
|
74
|
+
else
|
75
|
+
# Expect an operator
|
76
|
+
raise Keisan::Exceptions::ParseError.new("Expected an operator, received #{token.string}") unless token.type == :operator
|
77
|
+
add_operator_to_components!(token)
|
78
|
+
end
|
79
|
+
|
80
|
+
else
|
81
|
+
raise Keisan::Exceptions::InternalError.new("Invalid parsing!")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def add_unary_operator_to_components!(token)
|
86
|
+
case token.operator_type
|
87
|
+
when :+
|
88
|
+
@components << Keisan::Parsing::UnaryPlus.new
|
89
|
+
when :-
|
90
|
+
@components << Keisan::Parsing::UnaryMinus.new
|
91
|
+
when :"~"
|
92
|
+
@components << Keisan::Parsing::BitwiseNot.new
|
93
|
+
when :"~~"
|
94
|
+
@components << Keisan::Parsing::BitwiseNotNot.new
|
95
|
+
when :"!"
|
96
|
+
@components << Keisan::Parsing::LogicalNot.new
|
97
|
+
when :"!!"
|
98
|
+
@components << Keisan::Parsing::LogicalNotNot.new
|
99
|
+
else
|
100
|
+
raise Keisan::Exceptions::ParseError.new("Unhandled unary operator type #{token.operator_type}")
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def add_element_to_components!(token)
|
105
|
+
case token
|
106
|
+
when Keisan::Tokens::Number
|
107
|
+
@components << Keisan::Parsing::Number.new(token.value)
|
108
|
+
when Keisan::Tokens::String
|
109
|
+
@components << Keisan::Parsing::String.new(token.value)
|
110
|
+
when Keisan::Tokens::Null
|
111
|
+
@components << Keisan::Parsing::Null.new
|
112
|
+
when Keisan::Tokens::Word
|
113
|
+
@components << Keisan::Parsing::Variable.new(token.string)
|
114
|
+
when Keisan::Tokens::Boolean
|
115
|
+
@components << Keisan::Parsing::Boolean.new(token.value)
|
116
|
+
when Keisan::Tokens::Group
|
117
|
+
case token.group_type
|
118
|
+
when :round
|
119
|
+
@components << Keisan::Parsing::RoundGroup.new(token.sub_tokens)
|
120
|
+
when :square
|
121
|
+
@components << if token.sub_tokens.empty?
|
122
|
+
Parsing::List.new([])
|
123
|
+
else
|
124
|
+
Parsing::List.new(
|
125
|
+
token.sub_tokens.split {|sub_token| sub_token.is_a?(Keisan::Tokens::Comma)}.map do |sub_tokens|
|
126
|
+
Parsing::Argument.new(sub_tokens)
|
127
|
+
end
|
128
|
+
)
|
129
|
+
end
|
130
|
+
else
|
131
|
+
raise Keisan::Exceptions::ParseError.new("Unhandled group type #{token.group_type}")
|
132
|
+
end
|
133
|
+
else
|
134
|
+
raise Keisan::Exceptions::ParseError.new("Unhandled operator type #{token.operator_type}")
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def add_operator_to_components!(token)
|
139
|
+
case token.operator_type
|
140
|
+
# Arithmetic
|
141
|
+
when :+
|
142
|
+
@components << Keisan::Parsing::Plus.new
|
143
|
+
when :-
|
144
|
+
@components << Keisan::Parsing::Minus.new
|
145
|
+
when :*
|
146
|
+
@components << Keisan::Parsing::Times.new
|
147
|
+
when :/
|
148
|
+
@components << Keisan::Parsing::Divide.new
|
149
|
+
when :**
|
150
|
+
@components << Keisan::Parsing::Exponent.new
|
151
|
+
# Bitwise
|
152
|
+
when :"&"
|
153
|
+
@components << Keisan::Parsing::BitwiseAnd.new
|
154
|
+
when :"|"
|
155
|
+
@components << Keisan::Parsing::BitwiseOr.new
|
156
|
+
when :"^"
|
157
|
+
@components << Keisan::Parsing::BitwiseXor.new
|
158
|
+
when :"~"
|
159
|
+
@components << Keisan::Parsing::BitwiseNot.new
|
160
|
+
when :"~~"
|
161
|
+
@components << Keisan::Parsing::BitwiseNotNot.new
|
162
|
+
# Logical
|
163
|
+
when :"&&"
|
164
|
+
@components << Keisan::Parsing::LogicalAnd.new
|
165
|
+
when :"||"
|
166
|
+
@components << Keisan::Parsing::LogicalOr.new
|
167
|
+
when :"!"
|
168
|
+
@components << Keisan::Parsing::LogicalNot.new
|
169
|
+
when :"!!"
|
170
|
+
@components << Keisan::Parsing::LogicalNotNot.new
|
171
|
+
when :">"
|
172
|
+
@components << Keisan::Parsing::LogicalGreaterThan.new
|
173
|
+
when :"<"
|
174
|
+
@components << Keisan::Parsing::LogicalLessThan.new
|
175
|
+
when :">="
|
176
|
+
@components << Keisan::Parsing::LogicalGreaterThanOrEqualTo.new
|
177
|
+
when :"<="
|
178
|
+
@components << Keisan::Parsing::LogicalLessThanOrEqualTo.new
|
179
|
+
else
|
180
|
+
raise Keisan::Exceptions::ParseError.new("Unhandled operator type #{token.operator_type}")
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def add_function_to_components!(token)
|
185
|
+
# Have a function actually, not a variable
|
186
|
+
if token.sub_tokens.empty?
|
187
|
+
@components[-1] = Parsing::Function.new(@components[-1].name, [])
|
188
|
+
else
|
189
|
+
@components[-1] = Parsing::Function.new(
|
190
|
+
@components[-1].name,
|
191
|
+
token.sub_tokens.split {|sub_token| sub_token.is_a?(Keisan::Tokens::Comma)}.map do |sub_tokens|
|
192
|
+
Parsing::Argument.new(sub_tokens)
|
193
|
+
end
|
194
|
+
)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def add_indexing_to_components!(token)
|
199
|
+
# Have an indexing
|
200
|
+
@components << if token.sub_tokens.empty?
|
201
|
+
Parsing::Indexing.new([])
|
202
|
+
else
|
203
|
+
Parsing::Indexing.new(
|
204
|
+
token.sub_tokens.split {|sub_token| sub_token.is_a?(Keisan::Tokens::Comma)}.map do |sub_tokens|
|
205
|
+
Parsing::Argument.new(sub_tokens)
|
206
|
+
end
|
207
|
+
)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|