keisan 0.8.8 → 0.8.13
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 +4 -4
- data/README.md +12 -0
- data/lib/keisan/ast/assignment.rb +11 -0
- data/lib/keisan/ast/cell.rb +1 -1
- data/lib/keisan/ast/cell_assignment.rb +5 -1
- data/lib/keisan/ast/constant_literal.rb +164 -0
- data/lib/keisan/ast/function.rb +7 -0
- data/lib/keisan/ast/hash.rb +5 -0
- data/lib/keisan/ast/list.rb +1 -0
- data/lib/keisan/ast/list_assignment.rb +38 -0
- data/lib/keisan/ast/logical_and.rb +1 -1
- data/lib/keisan/ast/logical_or.rb +1 -1
- data/lib/keisan/ast/node.rb +4 -0
- data/lib/keisan/ast/number.rb +5 -0
- data/lib/keisan/ast/parent.rb +4 -0
- data/lib/keisan/functions/enumerable_function.rb +1 -1
- data/lib/keisan/functions/filter.rb +2 -2
- data/lib/keisan/functions/if.rb +6 -3
- data/lib/keisan/functions/while.rb +37 -14
- data/lib/keisan/variables/registry.rb +3 -1
- data/lib/keisan/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '05596336b8e5f1de3f7787b6a36120152f4e13c8caa822da225fe989f6af3c72'
|
4
|
+
data.tar.gz: 9a4d79fbc8ed709ec7d0f9b90d0e31f4f3e65c7e43b067800b66a0ea7c568ffb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d040a4d12325c74a8775d7a87a7d450fd0218de3e639b7ac43fe53bbb1310000cd53207e98727266f59535c5f7347d74392b941b66ad10359d97c515d5e02f75
|
7
|
+
data.tar.gz: 81ce4fe88b6d215a194bd8cf45388957689353c275cb8e60ec921a26eac8cb86d10c067ed34e09f6f303482f2b913005bc07c3448aed413d291b3beb66ecbf4f
|
data/README.md
CHANGED
@@ -83,6 +83,18 @@ calculator.evaluate("3*x + 1")
|
|
83
83
|
#=> 61
|
84
84
|
```
|
85
85
|
|
86
|
+
To perform multiple assignments, lists can be used
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
calculator = Keisan::Calculator.new
|
90
|
+
calculator.evaluate("x = [1, 2]")
|
91
|
+
calculator.evaluate("[x[1], y] = [11, 22]")
|
92
|
+
calculator.evaluate("x")
|
93
|
+
#=> [1, 11]
|
94
|
+
calculator.evaluate("y")
|
95
|
+
#=> 22
|
96
|
+
```
|
97
|
+
|
86
98
|
|
87
99
|
##### Specifying functions
|
88
100
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require_relative "variable_assignment"
|
2
2
|
require_relative "function_assignment"
|
3
|
+
require_relative "list_assignment"
|
3
4
|
require_relative "cell_assignment"
|
4
5
|
|
5
6
|
module Keisan
|
@@ -31,6 +32,8 @@ module Keisan
|
|
31
32
|
evaluate_variable_assignment(context, lhs, rhs)
|
32
33
|
elsif is_function_definition?
|
33
34
|
evaluate_function_assignment(context, lhs, rhs)
|
35
|
+
elsif is_list_assignment?
|
36
|
+
evaluate_list_assignment(context, lhs, rhs)
|
34
37
|
else
|
35
38
|
# Try cell assignment
|
36
39
|
evaluate_cell_assignment(context, lhs, rhs)
|
@@ -71,6 +74,10 @@ module Keisan
|
|
71
74
|
children.first.is_a?(Function)
|
72
75
|
end
|
73
76
|
|
77
|
+
def is_list_assignment?
|
78
|
+
children.first.is_a?(List)
|
79
|
+
end
|
80
|
+
|
74
81
|
private
|
75
82
|
|
76
83
|
def evaluate_variable_assignment(context, lhs, rhs)
|
@@ -82,6 +89,10 @@ module Keisan
|
|
82
89
|
FunctionAssignment.new(context, lhs, rhs, local).evaluate
|
83
90
|
end
|
84
91
|
|
92
|
+
def evaluate_list_assignment(context, lhs, rhs)
|
93
|
+
ListAssignment.new(self, context, lhs, rhs).evaluate
|
94
|
+
end
|
95
|
+
|
85
96
|
def evaluate_cell_assignment(context, lhs, rhs)
|
86
97
|
CellAssignment.new(self, context, lhs, rhs).evaluate
|
87
98
|
end
|
data/lib/keisan/ast/cell.rb
CHANGED
@@ -30,7 +30,11 @@ module Keisan
|
|
30
30
|
private
|
31
31
|
|
32
32
|
def lhs_evaluate_and_check_modifiable
|
33
|
-
lhs.evaluate(context)
|
33
|
+
res = lhs.evaluate(context)
|
34
|
+
if res.frozen?
|
35
|
+
raise Exceptions::UnmodifiableError.new("Cannot modify frozen variables")
|
36
|
+
end
|
37
|
+
res
|
34
38
|
rescue RuntimeError => e
|
35
39
|
raise Exceptions::UnmodifiableError.new("Cannot modify frozen variables") if e.message =~ /can't modify frozen/
|
36
40
|
raise
|
@@ -22,6 +22,170 @@ module Keisan
|
|
22
22
|
value.to_s
|
23
23
|
end
|
24
24
|
end
|
25
|
+
|
26
|
+
def is_constant?
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
def +(other)
|
31
|
+
if other.is_constant?
|
32
|
+
raise Keisan::Exceptions::InvalidExpression.new("Cannot add #{self.class} to #{other.class}")
|
33
|
+
else
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def -(other)
|
39
|
+
if other.is_constant?
|
40
|
+
raise Keisan::Exceptions::InvalidExpression.new("Cannot subtract #{self.class} from #{other.class}")
|
41
|
+
else
|
42
|
+
super
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def *(other)
|
47
|
+
if other.is_constant?
|
48
|
+
raise Keisan::Exceptions::InvalidExpression.new("Cannot multiply #{self.class} and #{other.class}")
|
49
|
+
else
|
50
|
+
super
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def /(other)
|
55
|
+
if other.is_constant?
|
56
|
+
raise Keisan::Exceptions::InvalidExpression.new("Cannot divide #{self.class} and #{other.class}")
|
57
|
+
else
|
58
|
+
super
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def %(other)
|
63
|
+
if other.is_constant?
|
64
|
+
raise Keisan::Exceptions::InvalidExpression.new("Cannot modulo #{self.class} and #{other.class}")
|
65
|
+
else
|
66
|
+
super
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def !
|
71
|
+
raise Keisan::Exceptions::InvalidExpression.new("Cannot take logical not of #{self.class}")
|
72
|
+
end
|
73
|
+
|
74
|
+
def ~
|
75
|
+
raise Keisan::Exceptions::InvalidExpression.new("Cannot take bitwise not of #{self.class}")
|
76
|
+
end
|
77
|
+
|
78
|
+
def +@
|
79
|
+
raise Keisan::Exceptions::InvalidExpression.new("Cannot take unary plus of #{self.class}")
|
80
|
+
end
|
81
|
+
|
82
|
+
def -@
|
83
|
+
raise Keisan::Exceptions::InvalidExpression.new("Cannot take unary minus of #{self.class}")
|
84
|
+
end
|
85
|
+
|
86
|
+
def **(other)
|
87
|
+
if other.is_constant?
|
88
|
+
raise Keisan::Exceptions::InvalidExpression.new("Cannot exponentiate #{self.class} and #{other.class}")
|
89
|
+
else
|
90
|
+
super
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def &(other)
|
95
|
+
if other.is_constant?
|
96
|
+
raise Keisan::Exceptions::InvalidExpression.new("Cannot bitwise and #{self.class} and #{other.class}")
|
97
|
+
else
|
98
|
+
super
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def ^(other)
|
103
|
+
if other.is_constant?
|
104
|
+
raise Keisan::Exceptions::InvalidExpression.new("Cannot bitwise xor #{self.class} and #{other.class}")
|
105
|
+
else
|
106
|
+
super
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def |(other)
|
111
|
+
if other.is_constant?
|
112
|
+
raise Keisan::Exceptions::InvalidExpression.new("Cannot bitwise or #{self.class} and #{other.class}")
|
113
|
+
else
|
114
|
+
super
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def <<(other)
|
119
|
+
if other.is_constant?
|
120
|
+
raise Keisan::Exceptions::InvalidExpression.new("Cannot bitwise left shift #{self.class} and #{other.class}")
|
121
|
+
else
|
122
|
+
super
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def >>(other)
|
127
|
+
if other.is_constant?
|
128
|
+
raise Keisan::Exceptions::InvalidExpression.new("Cannot bitwise right shift #{self.class} and #{other.class}")
|
129
|
+
else
|
130
|
+
super
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def >(other)
|
135
|
+
if other.is_constant?
|
136
|
+
raise Keisan::Exceptions::InvalidExpression.new("Cannot compute #{self.class} > #{other.class}")
|
137
|
+
else
|
138
|
+
super
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def >=(other)
|
143
|
+
if other.is_constant?
|
144
|
+
raise Keisan::Exceptions::InvalidExpression.new("Cannot compute #{self.class} >= #{other.class}")
|
145
|
+
else
|
146
|
+
super
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def <(other)
|
151
|
+
if other.is_constant?
|
152
|
+
raise Keisan::Exceptions::InvalidExpression.new("Cannot compute #{self.class} < #{other.class}")
|
153
|
+
else
|
154
|
+
super
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def <=(other)
|
159
|
+
if other.is_constant?
|
160
|
+
raise Keisan::Exceptions::InvalidExpression.new("Cannot compute #{self.class} <= #{other.class}")
|
161
|
+
else
|
162
|
+
super
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def equal(other)
|
167
|
+
other.is_constant? ? Boolean.new(false) : super
|
168
|
+
end
|
169
|
+
|
170
|
+
def not_equal(other)
|
171
|
+
other.is_constant? ? Boolean.new(true) : super
|
172
|
+
end
|
173
|
+
|
174
|
+
def and(other)
|
175
|
+
if other.is_constant?
|
176
|
+
raise Keisan::Exceptions::InvalidExpression.new("Cannot logical and #{self.class} and #{other.class}")
|
177
|
+
else
|
178
|
+
super
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def or(other)
|
183
|
+
if other.is_constant?
|
184
|
+
raise Keisan::Exceptions::InvalidExpression.new("Cannot logical or #{self.class} and #{other.class}")
|
185
|
+
else
|
186
|
+
super
|
187
|
+
end
|
188
|
+
end
|
25
189
|
end
|
26
190
|
end
|
27
191
|
end
|
data/lib/keisan/ast/function.rb
CHANGED
@@ -91,6 +91,13 @@ module Keisan
|
|
91
91
|
|
92
92
|
self.class.new([self, variable], "diff")
|
93
93
|
end
|
94
|
+
|
95
|
+
# Functions cannot be guaranteed to be constant even if the arguments
|
96
|
+
# are constants, because there might be randomness involved in the
|
97
|
+
# outputs.
|
98
|
+
def is_constant?
|
99
|
+
false
|
100
|
+
end
|
94
101
|
end
|
95
102
|
end
|
96
103
|
end
|
data/lib/keisan/ast/hash.rb
CHANGED
@@ -34,6 +34,7 @@ module Keisan
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def evaluate(context = nil)
|
37
|
+
return self if frozen?
|
37
38
|
context ||= Context.new
|
38
39
|
|
39
40
|
@hash = ::Hash[
|
@@ -89,6 +90,10 @@ module Keisan
|
|
89
90
|
])
|
90
91
|
AST::Cell.new(h)
|
91
92
|
end
|
93
|
+
|
94
|
+
def is_constant?
|
95
|
+
@hash.all? {|k,v| v.is_constant?}
|
96
|
+
end
|
92
97
|
end
|
93
98
|
end
|
94
99
|
end
|
data/lib/keisan/ast/list.rb
CHANGED
@@ -0,0 +1,38 @@
|
|
1
|
+
module Keisan
|
2
|
+
module AST
|
3
|
+
class ListAssignment
|
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
|
+
rhs = @rhs.evaluate(context)
|
15
|
+
|
16
|
+
if !rhs.is_a?(List)
|
17
|
+
raise Exceptions::InvalidExpression.new("To do multiple assignment, RHS must be a list")
|
18
|
+
end
|
19
|
+
if lhs.children.size != rhs.children.size
|
20
|
+
raise Exceptions::InvalidExpression.new("To do multiple assignment, RHS list must have same length as LHS list")
|
21
|
+
end
|
22
|
+
|
23
|
+
i = 0
|
24
|
+
while i < lhs.children.size
|
25
|
+
lhs_variable = lhs.children[i]
|
26
|
+
rhs_assignment = rhs.children[i]
|
27
|
+
individual_assignment = Assignment.new(
|
28
|
+
children = [lhs_variable, rhs_assignment],
|
29
|
+
local: assignment.local,
|
30
|
+
compound_operator: assignment.compound_operator
|
31
|
+
)
|
32
|
+
individual_assignment.evaluate(context)
|
33
|
+
i += 1
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -26,7 +26,7 @@ module Keisan
|
|
26
26
|
|
27
27
|
def short_circuit_do(method, context)
|
28
28
|
context ||= Context.new
|
29
|
-
lhs = children[0].send(method, context)
|
29
|
+
lhs = children[0].send(method, context).to_node
|
30
30
|
case lhs
|
31
31
|
when AST::Boolean
|
32
32
|
lhs.false? ? AST::Boolean.new(false) : children[1].send(method, context)
|
@@ -26,7 +26,7 @@ module Keisan
|
|
26
26
|
|
27
27
|
def short_circuit_do(method, context)
|
28
28
|
context ||= Context.new
|
29
|
-
lhs = children[0].send(method, context)
|
29
|
+
lhs = children[0].send(method, context).to_node
|
30
30
|
case lhs
|
31
31
|
when AST::Boolean
|
32
32
|
lhs.true? ? AST::Boolean.new(true) : children[1].send(method, context)
|
data/lib/keisan/ast/node.rb
CHANGED
data/lib/keisan/ast/number.rb
CHANGED
data/lib/keisan/ast/parent.rb
CHANGED
@@ -29,7 +29,7 @@ module Keisan
|
|
29
29
|
AST::List.new(
|
30
30
|
list.children.select do |element|
|
31
31
|
local.register_variable!(variable, element)
|
32
|
-
result = expression.evaluated(local)
|
32
|
+
result = expression.evaluated(local).to_node
|
33
33
|
|
34
34
|
case result
|
35
35
|
when AST::Boolean
|
@@ -54,7 +54,7 @@ module Keisan
|
|
54
54
|
hash.select do |cur_key, cur_value|
|
55
55
|
local.register_variable!(key, cur_key)
|
56
56
|
local.register_variable!(value, cur_value)
|
57
|
-
result = expression.evaluated(local)
|
57
|
+
result = expression.evaluated(local).to_node
|
58
58
|
|
59
59
|
case result
|
60
60
|
when AST::Boolean
|
data/lib/keisan/functions/if.rb
CHANGED
@@ -13,12 +13,13 @@ module Keisan
|
|
13
13
|
def evaluate(ast_function, context = nil)
|
14
14
|
validate_arguments!(ast_function.children.count)
|
15
15
|
context ||= Context.new
|
16
|
-
|
17
|
-
bool = ast_function.children[0].evaluate(context)
|
16
|
+
bool = ast_function.children[0].evaluate(context).to_node
|
18
17
|
|
19
18
|
if bool.is_a?(AST::Boolean)
|
20
19
|
node = bool.value ? ast_function.children[1] : ast_function.children[2]
|
21
20
|
node.to_node.evaluate(context)
|
21
|
+
elsif bool.is_constant?
|
22
|
+
raise Keisan::Exceptions::InvalidFunctionError.new("if statement must work on booleans, other constants are not supported")
|
22
23
|
else
|
23
24
|
ast_function
|
24
25
|
end
|
@@ -27,7 +28,7 @@ module Keisan
|
|
27
28
|
def simplify(ast_function, context = nil)
|
28
29
|
validate_arguments!(ast_function.children.count)
|
29
30
|
context ||= Context.new
|
30
|
-
bool = ast_function.children[0].simplify(context)
|
31
|
+
bool = ast_function.children[0].simplify(context).to_node
|
31
32
|
|
32
33
|
if bool.is_a?(AST::Boolean)
|
33
34
|
if bool.value
|
@@ -37,6 +38,8 @@ module Keisan
|
|
37
38
|
else
|
38
39
|
Keisan::AST::Null.new
|
39
40
|
end
|
41
|
+
elsif bool.is_constant?
|
42
|
+
raise Keisan::Exceptions::InvalidFunctionError.new("if statement must work on booleans, other constants are not supported")
|
40
43
|
else
|
41
44
|
ast_function
|
42
45
|
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
module Keisan
|
2
2
|
module Functions
|
3
3
|
class While < Keisan::Function
|
4
|
+
class WhileLogicalNodeIsNotConstant < Keisan::Exceptions::StandardError; end
|
5
|
+
class WhileLogicalNodeIsNonBoolConstant < Keisan::Exceptions::StandardError; end
|
6
|
+
|
4
7
|
def initialize
|
5
8
|
super("while", 2)
|
6
9
|
end
|
@@ -13,27 +16,43 @@ module Keisan
|
|
13
16
|
def evaluate(ast_function, context = nil)
|
14
17
|
validate_arguments!(ast_function.children.count)
|
15
18
|
context ||= Keisan::Context.new
|
16
|
-
|
19
|
+
while_loop(ast_function, context, simplify: false)
|
17
20
|
end
|
18
21
|
|
19
22
|
def simplify(ast_function, context = nil)
|
20
23
|
validate_arguments!(ast_function.children.count)
|
21
24
|
context ||= Context.new
|
22
|
-
while_loop(ast_function
|
25
|
+
while_loop(ast_function, context, simplify: true)
|
23
26
|
end
|
24
27
|
|
25
28
|
private
|
26
29
|
|
27
|
-
def while_loop(
|
30
|
+
def while_loop(ast_function, context, simplify: true)
|
31
|
+
logical_node, body_node = ast_function.children[0], ast_function.children[1]
|
28
32
|
current = Keisan::AST::Null.new
|
29
33
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
begin
|
35
|
+
while logical_node_evaluates_to_true(logical_node, context)
|
36
|
+
begin
|
37
|
+
current = body_node.evaluated(context)
|
38
|
+
rescue Exceptions::BreakError
|
39
|
+
break
|
40
|
+
rescue Exceptions::ContinueError
|
41
|
+
next
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# While loops should work on booleans, not other types of constants
|
46
|
+
rescue WhileLogicalNodeIsNonBoolConstant
|
47
|
+
raise Keisan::Exceptions::InvalidFunctionError.new("while condition must evaluate to a boolean")
|
48
|
+
|
49
|
+
# If the logical expression is not constant (e.g. boolean), then we
|
50
|
+
# cannot simplify the while loop, and an evaluate should raise an error.
|
51
|
+
rescue WhileLogicalNodeIsNotConstant
|
52
|
+
if simplify
|
53
|
+
return ast_function
|
54
|
+
else
|
55
|
+
raise Keisan::Exceptions::InvalidFunctionError.new("while condition must evaluate to a boolean")
|
37
56
|
end
|
38
57
|
end
|
39
58
|
|
@@ -41,11 +60,15 @@ module Keisan
|
|
41
60
|
end
|
42
61
|
|
43
62
|
def logical_node_evaluates_to_true(logical_node, context)
|
44
|
-
bool = logical_node.evaluated(context)
|
45
|
-
|
46
|
-
|
63
|
+
bool = logical_node.evaluated(context).to_node
|
64
|
+
|
65
|
+
if bool.is_a?(AST::Boolean)
|
66
|
+
bool.value(context)
|
67
|
+
elsif bool.is_constant?
|
68
|
+
raise WhileLogicalNodeIsNonBoolConstant.new
|
69
|
+
else
|
70
|
+
raise WhileLogicalNodeIsNotConstant.new
|
47
71
|
end
|
48
|
-
bool.value(context)
|
49
72
|
end
|
50
73
|
end
|
51
74
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require "set"
|
2
|
+
|
1
3
|
module Keisan
|
2
4
|
module Variables
|
3
5
|
class Registry
|
@@ -5,7 +7,7 @@ module Keisan
|
|
5
7
|
|
6
8
|
def initialize(variables: {}, shadowed: [], parent: nil, use_defaults: true, force: false)
|
7
9
|
@hash = {}
|
8
|
-
@shadowed = Set.new(shadowed.map(&:to_s))
|
10
|
+
@shadowed = ::Set.new(shadowed.map(&:to_s))
|
9
11
|
@parent = parent
|
10
12
|
@use_defaults = use_defaults
|
11
13
|
|
data/lib/keisan/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: keisan
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.13
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Christopher Locke
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-05-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cmath
|
@@ -151,6 +151,7 @@ files:
|
|
151
151
|
- lib/keisan/ast/indexing.rb
|
152
152
|
- lib/keisan/ast/line_builder.rb
|
153
153
|
- lib/keisan/ast/list.rb
|
154
|
+
- lib/keisan/ast/list_assignment.rb
|
154
155
|
- lib/keisan/ast/literal.rb
|
155
156
|
- lib/keisan/ast/logical_and.rb
|
156
157
|
- lib/keisan/ast/logical_equal.rb
|