keisan 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +90 -132
- data/bin/keisan +13 -2
- data/lib/keisan.rb +5 -0
- data/lib/keisan/ast.rb +11 -0
- data/lib/keisan/ast/assignment.rb +20 -55
- data/lib/keisan/ast/boolean.rb +16 -12
- data/lib/keisan/ast/cell.rb +17 -0
- data/lib/keisan/ast/cell_assignment.rb +70 -0
- data/lib/keisan/ast/function_assignment.rb +52 -0
- data/lib/keisan/ast/hash.rb +82 -0
- data/lib/keisan/ast/indexing.rb +26 -15
- data/lib/keisan/ast/line_builder.rb +22 -4
- data/lib/keisan/ast/list.rb +14 -7
- data/lib/keisan/ast/node.rb +13 -0
- data/lib/keisan/ast/null.rb +14 -0
- data/lib/keisan/ast/parent.rb +8 -3
- data/lib/keisan/ast/string.rb +10 -0
- data/lib/keisan/ast/variable.rb +4 -0
- data/lib/keisan/ast/variable_assignment.rb +62 -0
- data/lib/keisan/context.rb +16 -2
- data/lib/keisan/functions/default_registry.rb +4 -0
- data/lib/keisan/functions/enumerable_function.rb +56 -0
- data/lib/keisan/functions/filter.rb +34 -32
- data/lib/keisan/functions/map.rb +25 -31
- data/lib/keisan/functions/puts.rb +23 -0
- data/lib/keisan/functions/reduce.rb +29 -29
- data/lib/keisan/functions/registry.rb +4 -4
- data/lib/keisan/functions/sample.rb +5 -3
- data/lib/keisan/functions/to_h.rb +34 -0
- data/lib/keisan/interpreter.rb +42 -0
- data/lib/keisan/parser.rb +59 -50
- data/lib/keisan/parsing/compound_assignment.rb +15 -0
- data/lib/keisan/parsing/hash.rb +36 -0
- data/lib/keisan/repl.rb +1 -1
- data/lib/keisan/token.rb +1 -0
- data/lib/keisan/tokenizer.rb +23 -19
- data/lib/keisan/tokens/assignment.rb +21 -1
- data/lib/keisan/tokens/colon.rb +11 -0
- data/lib/keisan/tokens/unknown.rb +11 -0
- data/lib/keisan/variables/registry.rb +11 -6
- data/lib/keisan/version.rb +1 -1
- metadata +14 -2
data/lib/keisan/functions/map.rb
CHANGED
@@ -1,27 +1,22 @@
|
|
1
|
+
require "keisan/functions/enumerable_function"
|
2
|
+
|
1
3
|
module Keisan
|
2
4
|
module Functions
|
3
|
-
class Map <
|
4
|
-
# Maps
|
5
|
-
#
|
6
|
-
#
|
5
|
+
class Map < EnumerableFunction
|
6
|
+
# Maps
|
7
|
+
# (list, variable, expression)
|
8
|
+
# (hash, key, value, expression)
|
7
9
|
def initialize
|
8
|
-
super("map"
|
9
|
-
end
|
10
|
-
|
11
|
-
def value(ast_function, context = nil)
|
12
|
-
evaluate(ast_function, context)
|
13
|
-
end
|
14
|
-
|
15
|
-
def evaluate(ast_function, context = nil)
|
16
|
-
context ||= Context.new
|
17
|
-
simplify(ast_function, context).evaluate(context)
|
10
|
+
super("map")
|
18
11
|
end
|
19
12
|
|
20
|
-
|
21
|
-
validate_arguments!(ast_function.children.count)
|
13
|
+
private
|
22
14
|
|
23
|
-
|
24
|
-
|
15
|
+
def evaluate_list(list, arguments, expression, context)
|
16
|
+
unless arguments.count == 1
|
17
|
+
raise Exceptions::InvalidFunctionError.new("Map on list must take 3 arguments")
|
18
|
+
end
|
19
|
+
variable = arguments.first
|
25
20
|
|
26
21
|
local = context.spawn_child(transient: false, shadowed: [variable.name])
|
27
22
|
|
@@ -33,22 +28,21 @@ module Keisan
|
|
33
28
|
)
|
34
29
|
end
|
35
30
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
list = ast_function.children[0].simplify(context)
|
40
|
-
variable = ast_function.children[1]
|
41
|
-
expression = ast_function.children[2]
|
42
|
-
|
43
|
-
unless list.is_a?(AST::List)
|
44
|
-
raise Exceptions::InvalidFunctionError.new("First argument to map must be a list")
|
31
|
+
def evaluate_hash(hash, arguments, expression, context)
|
32
|
+
unless arguments.count == 2
|
33
|
+
raise Exceptions::InvalidFunctionError.new("Map on hash must take 4 arguments")
|
45
34
|
end
|
35
|
+
key, value = arguments[0..1]
|
46
36
|
|
47
|
-
|
48
|
-
raise Exceptions::InvalidFunctionError.new("Second argument to map must be a variable")
|
49
|
-
end
|
37
|
+
local = context.spawn_child(transient: false, shadowed: [key.name, value.name])
|
50
38
|
|
51
|
-
|
39
|
+
AST::List.new(
|
40
|
+
hash.map do |cur_key, cur_value|
|
41
|
+
local.register_variable!(key, cur_key)
|
42
|
+
local.register_variable!(value, cur_value)
|
43
|
+
expression.simplified(local)
|
44
|
+
end
|
45
|
+
)
|
52
46
|
end
|
53
47
|
end
|
54
48
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Keisan
|
2
|
+
module Functions
|
3
|
+
class Puts < Function
|
4
|
+
def initialize
|
5
|
+
super("puts", 1)
|
6
|
+
end
|
7
|
+
|
8
|
+
def value(ast_function, context = nil)
|
9
|
+
evaluate(ast_function, context)
|
10
|
+
end
|
11
|
+
|
12
|
+
def evaluate(ast_function, context = nil)
|
13
|
+
validate_arguments!(ast_function.children.count)
|
14
|
+
puts ast_function.children.first.evaluate(context).to_s
|
15
|
+
Keisan::AST::Null.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def simplify(ast_function, context = nil)
|
19
|
+
evaluate(ast_function, context)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -1,27 +1,31 @@
|
|
1
|
+
require "keisan/functions/enumerable_function"
|
2
|
+
|
1
3
|
module Keisan
|
2
4
|
module Functions
|
3
|
-
class Reduce <
|
5
|
+
class Reduce < EnumerableFunction
|
4
6
|
# Reduces (list, initial, accumulator, variable, expression)
|
5
7
|
# e.g. reduce([1,2,3,4], 0, total, x, total+x)
|
6
8
|
# should give 10
|
7
9
|
def initialize
|
8
|
-
super("reduce"
|
10
|
+
super("reduce")
|
9
11
|
end
|
10
12
|
|
11
|
-
|
12
|
-
evaluate(ast_function, context)
|
13
|
-
end
|
13
|
+
protected
|
14
14
|
|
15
|
-
def
|
16
|
-
|
17
|
-
|
15
|
+
def verify_arguments!(arguments)
|
16
|
+
unless arguments[1..-1].all? {|argument| argument.is_a?(AST::Variable)}
|
17
|
+
raise Exceptions::InvalidFunctionError.new("Middle arguments to #{name} must be variables")
|
18
|
+
end
|
18
19
|
end
|
19
20
|
|
20
|
-
|
21
|
-
|
21
|
+
private
|
22
|
+
|
23
|
+
def evaluate_list(list, arguments, expression, context)
|
24
|
+
unless arguments.count == 3
|
25
|
+
raise Exceptions::InvalidFunctionError.new("Reduce on list must take 3 arguments")
|
26
|
+
end
|
22
27
|
|
23
|
-
|
24
|
-
list, initial, accumulator, variable, expression = list_initial_accumulator_variable_expression_for(ast_function, context)
|
28
|
+
initial, accumulator, variable = arguments[0...3]
|
25
29
|
|
26
30
|
local = context.spawn_child(transient: false, shadowed: [accumulator.name, variable.name])
|
27
31
|
local.register_variable!(accumulator, initial.simplify(context))
|
@@ -35,28 +39,24 @@ module Keisan
|
|
35
39
|
local.variable(accumulator.name)
|
36
40
|
end
|
37
41
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
list = ast_function.children[0].simplify(context)
|
42
|
-
initial = ast_function.children[1]
|
43
|
-
accumulator = ast_function.children[2]
|
44
|
-
variable = ast_function.children[3]
|
45
|
-
expression = ast_function.children[4]
|
46
|
-
|
47
|
-
unless list.is_a?(AST::List)
|
48
|
-
raise Exceptions::InvalidFunctionError.new("First argument to reduce must be a list")
|
42
|
+
def evaluate_hash(hash, arguments, expression, context)
|
43
|
+
unless arguments.count == 4
|
44
|
+
raise Exceptions::InvalidFunctionError.new("Reduce on list must take 3 arguments")
|
49
45
|
end
|
50
46
|
|
51
|
-
|
52
|
-
|
53
|
-
|
47
|
+
initial, accumulator, key, value = arguments[0...4]
|
48
|
+
|
49
|
+
local = context.spawn_child(transient: false, shadowed: [accumulator.name, key.name, value.name])
|
50
|
+
local.register_variable!(accumulator, initial.simplify(context))
|
54
51
|
|
55
|
-
|
56
|
-
|
52
|
+
hash.each do |cur_key, cur_value|
|
53
|
+
local.register_variable!(key, cur_key)
|
54
|
+
local.register_variable!(value, cur_value)
|
55
|
+
result = expression.simplified(local)
|
56
|
+
local.register_variable!(accumulator, result)
|
57
57
|
end
|
58
58
|
|
59
|
-
|
59
|
+
local.variable(accumulator.name)
|
60
60
|
end
|
61
61
|
end
|
62
62
|
end
|
@@ -32,14 +32,14 @@ module Keisan
|
|
32
32
|
false
|
33
33
|
end
|
34
34
|
|
35
|
+
def modifiable?(name)
|
36
|
+
!frozen? && has?(name)
|
37
|
+
end
|
38
|
+
|
35
39
|
def register!(name, function, force: false)
|
36
40
|
raise Exceptions::UnmodifiableError.new("Cannot modify frozen functions registry") if frozen?
|
37
41
|
name = name.to_s
|
38
42
|
|
39
|
-
if !force && @use_defaults && default_registry.has_name?(name)
|
40
|
-
raise Exceptions::UnmodifiableError.new("Cannot overwrite default function")
|
41
|
-
end
|
42
|
-
|
43
43
|
case function
|
44
44
|
when Proc
|
45
45
|
self[name] = ProcFunction.new(name, function)
|
@@ -3,15 +3,17 @@ module Keisan
|
|
3
3
|
class Sample < ProcFunction
|
4
4
|
def initialize
|
5
5
|
@name = "sample"
|
6
|
-
@arity = 1
|
6
|
+
@arity = ::Range.new(1, 2)
|
7
7
|
end
|
8
8
|
|
9
|
-
# Single argument:
|
10
|
-
# Double argument:
|
9
|
+
# Single argument: list to sample element from
|
10
|
+
# Double argument: list and number of elements to sample
|
11
11
|
def call(context, *args)
|
12
12
|
case args.size
|
13
13
|
when 1
|
14
14
|
args.first.sample(random: context.random)
|
15
|
+
when 2
|
16
|
+
args[0].sample(args[1], random: context.random)
|
15
17
|
else
|
16
18
|
raise Exceptions::InvalidFunctionError.new
|
17
19
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Keisan
|
2
|
+
module Functions
|
3
|
+
class ToH < Function
|
4
|
+
def initialize
|
5
|
+
super("to_h", 1)
|
6
|
+
end
|
7
|
+
|
8
|
+
def value(ast_function, context = nil)
|
9
|
+
validate_arguments!(ast_function.children.count)
|
10
|
+
evaluate(ast_function, context).value(context)
|
11
|
+
end
|
12
|
+
|
13
|
+
def evaluate(ast_function, context = nil)
|
14
|
+
validate_arguments!(ast_function.children.count)
|
15
|
+
context ||= Context.new
|
16
|
+
|
17
|
+
child = ast_function.children[0].simplify(context)
|
18
|
+
|
19
|
+
case child
|
20
|
+
when AST::List
|
21
|
+
AST::Hash.new(child.children)
|
22
|
+
when AST::Hash
|
23
|
+
child
|
24
|
+
else
|
25
|
+
raise Exceptions::InvalidFunctionError.new("Cannot call to_h on a #{child.class}")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def simplify(ast_function, context = nil)
|
30
|
+
evaluate(ast_function, context)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "keisan/repl"
|
2
|
+
|
3
|
+
module Keisan
|
4
|
+
class Interpreter
|
5
|
+
attr_reader :calculator
|
6
|
+
|
7
|
+
def initialize(allow_recursive: false)
|
8
|
+
@calculator = Calculator.new(allow_recursive: allow_recursive)
|
9
|
+
end
|
10
|
+
|
11
|
+
def run(file_name)
|
12
|
+
if file_name.nil?
|
13
|
+
run_from_stdin
|
14
|
+
else
|
15
|
+
run_from_file(file_name)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def run_from_stdin
|
22
|
+
run_on_content STDIN.tty? ? "" : STDIN.read
|
23
|
+
end
|
24
|
+
|
25
|
+
def run_from_file(file_name)
|
26
|
+
run_on_content(
|
27
|
+
File.exists?(file_name) ? File.open(file_name) do |file|
|
28
|
+
file.read
|
29
|
+
end : ""
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
def run_on_content(content)
|
34
|
+
content = content.strip
|
35
|
+
if content.nil? || content.empty?
|
36
|
+
Repl.new.start
|
37
|
+
else
|
38
|
+
calculator.evaluate(content)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/keisan/parser.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Keisan
|
2
2
|
class Parser
|
3
|
-
KEYWORDS = %w(let).freeze
|
3
|
+
KEYWORDS = %w(let puts).freeze
|
4
4
|
|
5
5
|
attr_reader :tokens, :components
|
6
6
|
|
@@ -119,7 +119,9 @@ module Keisan
|
|
119
119
|
elsif token.type == :operator
|
120
120
|
add_operator_to_components!(token)
|
121
121
|
else
|
122
|
-
|
122
|
+
# Concatenation is multiplication
|
123
|
+
@components << Parsing::Times.new
|
124
|
+
add_token_to_components!(token)
|
123
125
|
end
|
124
126
|
|
125
127
|
elsif @components[-1].is_a?(Parsing::Dot)
|
@@ -196,18 +198,26 @@ module Keisan
|
|
196
198
|
when Tokens::Boolean
|
197
199
|
@components << Parsing::Boolean.new(token.value)
|
198
200
|
when Tokens::Group
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
201
|
+
add_group_element_components!(token)
|
202
|
+
else
|
203
|
+
raise Exceptions::ParseError.new("Unhandled operator type #{token.operator_type}")
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def add_group_element_components!(token)
|
208
|
+
case token.group_type
|
209
|
+
when :round
|
210
|
+
@components << Parsing::RoundGroup.new(token.sub_tokens)
|
211
|
+
when :square
|
212
|
+
@components << Parsing::List.new(arguments_from_group(token))
|
213
|
+
when :curly
|
214
|
+
if token.sub_tokens.any? {|token| token.is_a?(Tokens::Colon)}
|
215
|
+
@components << Parsing::Hash.new(token.sub_tokens.split {|token| token.is_a?(Tokens::Comma)})
|
206
216
|
else
|
207
|
-
|
217
|
+
@components << Parsing::CurlyGroup.new(token.sub_tokens)
|
208
218
|
end
|
209
219
|
else
|
210
|
-
raise Exceptions::ParseError.new("Unhandled
|
220
|
+
raise Exceptions::ParseError.new("Unhandled group type #{token.group_type}")
|
211
221
|
end
|
212
222
|
end
|
213
223
|
|
@@ -215,46 +225,45 @@ module Keisan
|
|
215
225
|
case token.operator_type
|
216
226
|
# Assignment
|
217
227
|
when :"="
|
218
|
-
|
219
|
-
# Arithmetic
|
220
|
-
when :+
|
221
|
-
@components << Parsing::Plus.new
|
222
|
-
when :-
|
223
|
-
@components << Parsing::Minus.new
|
224
|
-
when :*
|
225
|
-
@components << Parsing::Times.new
|
226
|
-
when :/
|
227
|
-
@components << Parsing::Divide.new
|
228
|
-
when :**
|
229
|
-
@components << Parsing::Exponent.new
|
230
|
-
when :%
|
231
|
-
@components << Parsing::Modulo.new
|
232
|
-
# Bitwise
|
233
|
-
when :"&"
|
234
|
-
@components << Parsing::BitwiseAnd.new
|
235
|
-
when :"|"
|
236
|
-
@components << Parsing::BitwiseOr.new
|
237
|
-
when :"^"
|
238
|
-
@components << Parsing::BitwiseXor.new
|
239
|
-
# Logical
|
240
|
-
when :"=="
|
241
|
-
@components << Parsing::LogicalEqual.new
|
242
|
-
when :"!="
|
243
|
-
@components << Parsing::LogicalNotEqual.new
|
244
|
-
when :"&&"
|
245
|
-
@components << Parsing::LogicalAnd.new
|
246
|
-
when :"||"
|
247
|
-
@components << Parsing::LogicalOr.new
|
248
|
-
when :">"
|
249
|
-
@components << Parsing::LogicalGreaterThan.new
|
250
|
-
when :"<"
|
251
|
-
@components << Parsing::LogicalLessThan.new
|
252
|
-
when :">="
|
253
|
-
@components << Parsing::LogicalGreaterThanOrEqualTo.new
|
254
|
-
when :"<="
|
255
|
-
@components << Parsing::LogicalLessThanOrEqualTo.new
|
228
|
+
add_assignment_to_components!(token)
|
256
229
|
else
|
257
|
-
|
230
|
+
@components << operator_to_component(token.operator_type)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
OPERATOR_TO_PARSING_CLASS = {
|
235
|
+
:+ => Parsing::Plus,
|
236
|
+
:- => Parsing::Minus,
|
237
|
+
:* => Parsing::Times,
|
238
|
+
:/ => Parsing::Divide,
|
239
|
+
:** => Parsing::Exponent,
|
240
|
+
:% => Parsing::Modulo,
|
241
|
+
:"&" => Parsing::BitwiseAnd,
|
242
|
+
:"|" => Parsing::BitwiseOr,
|
243
|
+
:"^" => Parsing::BitwiseXor,
|
244
|
+
:"==" => Parsing::LogicalEqual,
|
245
|
+
:"!=" => Parsing::LogicalNotEqual,
|
246
|
+
:"&&" => Parsing::LogicalAnd,
|
247
|
+
:"||" => Parsing::LogicalOr,
|
248
|
+
:">" => Parsing::LogicalGreaterThan,
|
249
|
+
:"<" => Parsing::LogicalLessThan,
|
250
|
+
:">=" => Parsing::LogicalGreaterThanOrEqualTo,
|
251
|
+
:"<=" => Parsing::LogicalLessThanOrEqualTo
|
252
|
+
}.freeze
|
253
|
+
|
254
|
+
def operator_to_component(operator)
|
255
|
+
if klass = OPERATOR_TO_PARSING_CLASS[operator]
|
256
|
+
klass.new
|
257
|
+
else
|
258
|
+
raise Exceptions::ParseError.new("Unhandled operator type #{operator}")
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
def add_assignment_to_components!(token)
|
263
|
+
if compound_operator = token.compound_operator
|
264
|
+
@components << Parsing::CompoundAssignment.new(compound_operator)
|
265
|
+
else
|
266
|
+
@components << Parsing::Assignment.new
|
258
267
|
end
|
259
268
|
end
|
260
269
|
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Keisan
|
2
|
+
module Parsing
|
3
|
+
class CompoundAssignment < Operator
|
4
|
+
attr_reader :compound_operator
|
5
|
+
|
6
|
+
def initialize(compound_operator)
|
7
|
+
@compound_operator = compound_operator
|
8
|
+
end
|
9
|
+
|
10
|
+
def node_class
|
11
|
+
AST::Assignment
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|