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/ast.rb
CHANGED
@@ -66,9 +66,20 @@ module KeisanArray
|
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
|
+
module KeisanHash
|
70
|
+
def to_node
|
71
|
+
Keisan::AST::Hash.new(map {|k,v| [k.to_node, v.to_node]})
|
72
|
+
end
|
73
|
+
|
74
|
+
def value(context = nil)
|
75
|
+
self
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
69
79
|
class Numeric; prepend KeisanNumeric; end
|
70
80
|
class String; prepend KeisanString; end
|
71
81
|
class TrueClass; prepend KeisanTrueClass; end
|
72
82
|
class FalseClass; prepend KeisanFalseClass; end
|
73
83
|
class NilClass; prepend KeisanNilClass; end
|
74
84
|
class Array; prepend KeisanArray; end
|
85
|
+
class Hash; prepend KeisanHash; end
|
@@ -1,17 +1,26 @@
|
|
1
|
+
require_relative "variable_assignment"
|
2
|
+
require_relative "function_assignment"
|
3
|
+
require_relative "cell_assignment"
|
4
|
+
|
1
5
|
module Keisan
|
2
6
|
module AST
|
3
7
|
class Assignment < Operator
|
4
|
-
attr_reader :local
|
8
|
+
attr_reader :local, :compound_operator
|
5
9
|
|
6
|
-
def initialize(children = [], parsing_operators = [], local: false)
|
10
|
+
def initialize(children = [], parsing_operators = [], local: false, compound_operator: nil)
|
7
11
|
super(children, parsing_operators)
|
8
12
|
@local = local
|
13
|
+
@compound_operator = compound_operator
|
9
14
|
end
|
10
15
|
|
11
16
|
def self.symbol
|
12
17
|
:"="
|
13
18
|
end
|
14
19
|
|
20
|
+
def symbol
|
21
|
+
:"#{compound_operator}="
|
22
|
+
end
|
23
|
+
|
15
24
|
def evaluate(context = nil)
|
16
25
|
context ||= Context.new
|
17
26
|
|
@@ -19,9 +28,9 @@ module Keisan
|
|
19
28
|
rhs = children.last
|
20
29
|
|
21
30
|
if is_variable_definition?
|
22
|
-
|
31
|
+
evaluate_variable_assignment(context, lhs, rhs)
|
23
32
|
elsif is_function_definition?
|
24
|
-
|
33
|
+
evaluate_function_assignment(context, lhs, rhs)
|
25
34
|
else
|
26
35
|
# Try cell assignment
|
27
36
|
evaluate_cell_assignment(context, lhs, rhs)
|
@@ -64,61 +73,17 @@ module Keisan
|
|
64
73
|
|
65
74
|
private
|
66
75
|
|
67
|
-
def
|
68
|
-
|
69
|
-
unless lhs.is_a?(Cell)
|
70
|
-
raise Exceptions::InvalidExpression.new("Unhandled left hand side #{lhs} in assignment")
|
71
|
-
end
|
72
|
-
|
73
|
-
rhs = rhs.evaluate(context)
|
74
|
-
|
75
|
-
lhs.node = rhs
|
76
|
-
rhs
|
76
|
+
def evaluate_variable_assignment(context, lhs, rhs)
|
77
|
+
VariableAssignment.new(self, context, lhs, rhs).evaluate
|
77
78
|
end
|
78
79
|
|
79
|
-
def
|
80
|
-
|
81
|
-
|
82
|
-
unless rhs.well_defined?
|
83
|
-
raise Exceptions::InvalidExpression.new("Right hand side of assignment to variable must be well defined")
|
84
|
-
end
|
85
|
-
|
86
|
-
rhs_value = rhs.value(context)
|
87
|
-
context.register_variable!(lhs.name, rhs_value, local: local)
|
88
|
-
# Return the variable assigned value
|
89
|
-
rhs
|
80
|
+
def evaluate_function_assignment(context, lhs, rhs)
|
81
|
+
raise Exceptions::InvalidExpression.new("Cannot do compound assignment on functions") if compound_operator
|
82
|
+
FunctionAssignment.new(context, lhs, rhs, local).evaluate
|
90
83
|
end
|
91
84
|
|
92
|
-
def
|
93
|
-
|
94
|
-
raise Exceptions::InvalidExpression.new("Left hand side function must have variables as arguments")
|
95
|
-
end
|
96
|
-
|
97
|
-
argument_names = lhs.children.map(&:name)
|
98
|
-
function_definition_context = context.spawn_child(shadowed: argument_names, transient: true)
|
99
|
-
|
100
|
-
# Blocks might have local variable/function definitions
|
101
|
-
if !rhs.is_a?(Block)
|
102
|
-
unless rhs.unbound_variables(context) <= Set.new(argument_names)
|
103
|
-
raise Exceptions::InvalidExpression.new("Unbound variables found in function definition")
|
104
|
-
end
|
105
|
-
unless context.allow_recursive || rhs.unbound_functions(context).empty?
|
106
|
-
raise Exceptions::InvalidExpression.new("Unbound function definitions are not allowed by current context")
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
context.register_function!(
|
111
|
-
lhs.name,
|
112
|
-
Functions::ExpressionFunction.new(
|
113
|
-
lhs.name,
|
114
|
-
argument_names,
|
115
|
-
rhs.evaluate_assignments(function_definition_context),
|
116
|
-
context.transient_definitions
|
117
|
-
),
|
118
|
-
local: local
|
119
|
-
)
|
120
|
-
|
121
|
-
rhs
|
85
|
+
def evaluate_cell_assignment(context, lhs, rhs)
|
86
|
+
CellAssignment.new(self, context, lhs, rhs).evaluate
|
122
87
|
end
|
123
88
|
end
|
124
89
|
end
|
data/lib/keisan/ast/boolean.rb
CHANGED
@@ -11,28 +11,32 @@ module Keisan
|
|
11
11
|
bool
|
12
12
|
end
|
13
13
|
|
14
|
+
def true?
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
14
18
|
def !
|
15
19
|
Boolean.new(!bool)
|
16
20
|
end
|
17
21
|
|
18
22
|
def and(other)
|
19
23
|
other = other.to_node
|
20
|
-
|
21
|
-
when Boolean
|
22
|
-
Boolean.new(bool && other.bool)
|
23
|
-
else
|
24
|
-
super
|
25
|
-
end
|
24
|
+
other.is_a?(Boolean) ? Boolean.new(bool && other.bool) : super
|
26
25
|
end
|
27
26
|
|
28
27
|
def or(other)
|
29
28
|
other = other.to_node
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
29
|
+
other.is_a?(Boolean) ? Boolean.new(bool || other.bool) : super
|
30
|
+
end
|
31
|
+
|
32
|
+
def equal(other)
|
33
|
+
other = other.to_node
|
34
|
+
other.is_a?(Boolean) ? Boolean.new(value == other.value) : super
|
35
|
+
end
|
36
|
+
|
37
|
+
def not_equal(other)
|
38
|
+
other = other.to_node
|
39
|
+
other.is_a?(Boolean) ? Boolean.new(value != other.value) : super
|
36
40
|
end
|
37
41
|
end
|
38
42
|
end
|
data/lib/keisan/ast/cell.rb
CHANGED
@@ -24,10 +24,23 @@ module Keisan
|
|
24
24
|
dupped
|
25
25
|
end
|
26
26
|
|
27
|
+
def freeze
|
28
|
+
node.freeze
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
27
32
|
def value(context = nil)
|
28
33
|
node.value(context)
|
29
34
|
end
|
30
35
|
|
36
|
+
def true?
|
37
|
+
node.true?
|
38
|
+
end
|
39
|
+
|
40
|
+
def false?
|
41
|
+
node.false?
|
42
|
+
end
|
43
|
+
|
31
44
|
def evaluate(context = nil)
|
32
45
|
node.evaluate(context)
|
33
46
|
end
|
@@ -48,6 +61,10 @@ module Keisan
|
|
48
61
|
node.replace(variable, replacement)
|
49
62
|
end
|
50
63
|
|
64
|
+
def to_cell
|
65
|
+
self.class.new(node.to_cell)
|
66
|
+
end
|
67
|
+
|
51
68
|
def to_s
|
52
69
|
node.to_s
|
53
70
|
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Keisan
|
2
|
+
module AST
|
3
|
+
class CellAssignment
|
4
|
+
attr_reader :assignment, :context, :lhs, :rhs
|
5
|
+
|
6
|
+
def initialize(assignment, context, lhs, rhs)
|
7
|
+
@assignment = assignment
|
8
|
+
@context = context
|
9
|
+
@lhs = lhs
|
10
|
+
@rhs = rhs
|
11
|
+
end
|
12
|
+
|
13
|
+
def evaluate
|
14
|
+
lhs = lhs_evaluate_and_check_modifiable
|
15
|
+
|
16
|
+
unless lhs.is_a?(Cell)
|
17
|
+
raise Exceptions::InvalidExpression.new("Unhandled left hand side #{lhs} in assignment")
|
18
|
+
end
|
19
|
+
|
20
|
+
case assignment.compound_operator
|
21
|
+
when :"||"
|
22
|
+
evaluate_cell_or_assignment(context, lhs, rhs)
|
23
|
+
when :"&&"
|
24
|
+
evaluate_cell_and_assignment(context, lhs, rhs)
|
25
|
+
else
|
26
|
+
evaluate_cell_non_logical_assignment(context, lhs, rhs)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def lhs_evaluate_and_check_modifiable
|
33
|
+
lhs.evaluate(context)
|
34
|
+
rescue RuntimeError => e
|
35
|
+
raise Exceptions::UnmodifiableError.new("Cannot modify frozen variables") if e.message =~ /can't modify frozen/
|
36
|
+
raise
|
37
|
+
end
|
38
|
+
|
39
|
+
def evaluate_cell_or_assignment(context, lhs, rhs)
|
40
|
+
if lhs.false?
|
41
|
+
rhs = rhs.evaluate(context)
|
42
|
+
lhs.node = rhs.is_a?(Cell) ? rhs.node.deep_dup : rhs
|
43
|
+
rhs
|
44
|
+
else
|
45
|
+
lhs
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def evaluate_cell_and_assignment(context, lhs, rhs)
|
50
|
+
if lhs.true?
|
51
|
+
rhs = rhs.evaluate(context)
|
52
|
+
lhs.node = rhs.is_a?(Cell) ? rhs.node.deep_dup : rhs
|
53
|
+
rhs
|
54
|
+
else
|
55
|
+
lhs
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def evaluate_cell_non_logical_assignment(context, lhs, rhs)
|
60
|
+
rhs = rhs.evaluate(context)
|
61
|
+
if assignment.compound_operator
|
62
|
+
rhs = rhs.send(assignment.compound_operator, lhs.node).evaluate(context)
|
63
|
+
end
|
64
|
+
|
65
|
+
lhs.node = rhs.is_a?(Cell) ? rhs.node.deep_dup : rhs
|
66
|
+
rhs
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Keisan
|
2
|
+
module AST
|
3
|
+
class FunctionAssignment
|
4
|
+
attr_reader :context, :lhs, :rhs, :local
|
5
|
+
|
6
|
+
def initialize(context, lhs, rhs, local)
|
7
|
+
@context = context
|
8
|
+
@lhs = lhs
|
9
|
+
@rhs = rhs
|
10
|
+
@local = local
|
11
|
+
|
12
|
+
unless lhs.children.all? {|arg| arg.is_a?(Variable)}
|
13
|
+
raise Exceptions::InvalidExpression.new("Left hand side function must have variables as arguments")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def argument_names
|
18
|
+
@argument_names ||= lhs.children.map(&:name)
|
19
|
+
end
|
20
|
+
|
21
|
+
def expression_function
|
22
|
+
Functions::ExpressionFunction.new(
|
23
|
+
lhs.name,
|
24
|
+
argument_names,
|
25
|
+
rhs.evaluate_assignments(context.spawn_child(shadowed: argument_names, transient: true)),
|
26
|
+
context.transient_definitions
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
def evaluate
|
31
|
+
# Blocks might have local variable/function definitions, so skip check
|
32
|
+
verify_rhs_of_function_assignment_is_valid! unless rhs.is_a?(Block)
|
33
|
+
|
34
|
+
context.register_function!(lhs.name, expression_function, local: local)
|
35
|
+
rhs
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def verify_rhs_of_function_assignment_is_valid!
|
41
|
+
# Only variables that can appear are those that are arguments to the function
|
42
|
+
unless rhs.unbound_variables(context) <= Set.new(argument_names)
|
43
|
+
raise Exceptions::InvalidExpression.new("Unbound variables found in function definition")
|
44
|
+
end
|
45
|
+
# Cannot have undefined functions unless allowed by context
|
46
|
+
unless context.allow_recursive || rhs.unbound_functions(context).empty?
|
47
|
+
raise Exceptions::InvalidExpression.new("Unbound function definitions are not allowed by current context")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Keisan
|
2
|
+
module AST
|
3
|
+
class Hash < Node
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
def initialize(key_value_pairs)
|
7
|
+
@hash = ::Hash[key_value_pairs.map(&:to_a).map {|k,v| [k.value, v.to_node]}]
|
8
|
+
end
|
9
|
+
|
10
|
+
def freeze
|
11
|
+
values.each(&:freeze)
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](key)
|
16
|
+
key = key.to_node
|
17
|
+
return nil unless key.is_a?(AST::ConstantLiteral)
|
18
|
+
|
19
|
+
@hash[key.value] || Cell.new(Null.new).tap do |cell|
|
20
|
+
@hash[key.value] = cell
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def evaluate(context = nil)
|
25
|
+
context ||= Context.new
|
26
|
+
|
27
|
+
@hash = ::Hash[
|
28
|
+
@hash.map do |key, val|
|
29
|
+
if val.is_a?(Cell)
|
30
|
+
[key, val]
|
31
|
+
else
|
32
|
+
[key, val.evaluate(context)]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
]
|
36
|
+
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
def simplify(context = nil)
|
41
|
+
evaluate(context)
|
42
|
+
end
|
43
|
+
|
44
|
+
def each(&block)
|
45
|
+
@hash.each(&block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def keys
|
49
|
+
@hash.keys
|
50
|
+
end
|
51
|
+
|
52
|
+
def values
|
53
|
+
@hash.values
|
54
|
+
end
|
55
|
+
|
56
|
+
def value(context = nil)
|
57
|
+
context ||= Context.new
|
58
|
+
evaluate(context)
|
59
|
+
|
60
|
+
::Hash[
|
61
|
+
@hash.map {|key, val|
|
62
|
+
[key, val.value(context)]
|
63
|
+
}
|
64
|
+
]
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_s
|
68
|
+
"{#{@hash.map {|k,v| "#{k.is_a?(::String) ? "'#{k}'" : k}: #{v}"}.join(', ')}}"
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_cell
|
72
|
+
h = self.class.new([])
|
73
|
+
h.instance_variable_set(:@hash, ::Hash[
|
74
|
+
@hash.map do |key, value|
|
75
|
+
[key, value.to_cell]
|
76
|
+
end
|
77
|
+
])
|
78
|
+
AST::Cell.new(h)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/keisan/ast/indexing.rb
CHANGED
@@ -25,24 +25,11 @@ module Keisan
|
|
25
25
|
@children = children.map {|child| child.evaluate(context)}
|
26
26
|
@indexes = indexes.map {|index| index.evaluate(context)}
|
27
27
|
|
28
|
-
|
29
|
-
list.children[@indexes.first.value(context)]
|
30
|
-
else
|
31
|
-
self
|
32
|
-
end
|
28
|
+
evaluate_list(context) || evaluate_hash(context) || self
|
33
29
|
end
|
34
30
|
|
35
31
|
def simplify(context = nil)
|
36
|
-
context
|
37
|
-
|
38
|
-
@indexes = indexes.map {|index| index.simplify(context)}
|
39
|
-
@children = [child.simplify(context)]
|
40
|
-
|
41
|
-
if list = extract_list
|
42
|
-
Cell.new(list.children[@indexes.first.value(context)].simplify(context))
|
43
|
-
else
|
44
|
-
self
|
45
|
-
end
|
32
|
+
evaluate(context)
|
46
33
|
end
|
47
34
|
|
48
35
|
def replace(variable, replacement)
|
@@ -52,6 +39,20 @@ module Keisan
|
|
52
39
|
|
53
40
|
private
|
54
41
|
|
42
|
+
def evaluate_list(context)
|
43
|
+
if list = extract_list
|
44
|
+
element = list.children[@indexes.first.value(context)]
|
45
|
+
element.nil? ? AST::Null.new : element
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def evaluate_hash(context)
|
50
|
+
if hash = extract_hash
|
51
|
+
element = hash[@indexes.first.value(context)]
|
52
|
+
element.nil? ? AST::Null.new : element
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
55
56
|
def extract_list
|
56
57
|
if child.is_a?(List)
|
57
58
|
child
|
@@ -61,6 +62,16 @@ module Keisan
|
|
61
62
|
nil
|
62
63
|
end
|
63
64
|
end
|
65
|
+
|
66
|
+
def extract_hash
|
67
|
+
if child.is_a?(AST::Hash)
|
68
|
+
child
|
69
|
+
elsif child.is_a?(Cell) && child.node.is_a?(AST::Hash)
|
70
|
+
child.node
|
71
|
+
else
|
72
|
+
nil
|
73
|
+
end
|
74
|
+
end
|
64
75
|
end
|
65
76
|
end
|
66
77
|
end
|