loxxy 0.2.04 → 0.3.02
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +64 -0
- data/README.md +6 -0
- data/lib/loxxy/ast/all_lox_nodes.rb +0 -1
- data/lib/loxxy/ast/ast_builder.rb +28 -5
- data/lib/loxxy/ast/ast_visitor.rb +1 -10
- data/lib/loxxy/ast/lox_set_expr.rb +5 -2
- data/lib/loxxy/back_end/engine.rb +155 -74
- data/lib/loxxy/back_end/environment.rb +4 -0
- data/lib/loxxy/back_end/lox_class.rb +6 -8
- data/lib/loxxy/back_end/lox_function.rb +13 -7
- data/lib/loxxy/back_end/lox_instance.rb +1 -1
- data/lib/loxxy/back_end/resolver.rb +18 -21
- data/lib/loxxy/datatype/number.rb +19 -4
- data/lib/loxxy/front_end/scanner.rb +10 -14
- data/lib/loxxy/version.rb +1 -1
- data/spec/back_end/engine_spec.rb +13 -3
- data/spec/back_end/environment_spec.rb +0 -14
- data/spec/back_end/symbol_table_spec.rb +0 -19
- data/spec/back_end/variable_spec.rb +0 -35
- data/spec/front_end/parser_spec.rb +4 -4
- data/spec/front_end/scanner_spec.rb +35 -7
- data/spec/interpreter_spec.rb +57 -0
- metadata +2 -3
- data/lib/loxxy/ast/lox_for_stmt.rb +0 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0e398714d64763c288f06c315611c86bf3dae2e3403055ae3ca240a10bd292f
|
4
|
+
data.tar.gz: 8db95e837824181e1794277dc3e67e4005432006877124bbd62ffd07def57591
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: caf2aab9a8c03997fbde67467b7cb66a786c2fb8ef46fc24869c42ac7da76831f5e771a282c938ec83585666de801602ffc9818fc489454ec93dec9bd76bf5a7
|
7
|
+
data.tar.gz: 9d98db7d9bddec915929dc1cbaed057248ccb5e0cc63efc3d0d7e99536ea9b330925d91d6b1ffd6805194dc3805b0025228f66d11f11b5455cb8f52d7753e6eb
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,67 @@
|
|
1
|
+
## [0.3.02] - 2021-05-22
|
2
|
+
- New built-in expressions `getc`, `chr`, `exit` and `print_eeror` , fixes with deeply nested returns, set expressions
|
3
|
+
|
4
|
+
### New
|
5
|
+
- Class `BackEnd::Engine` new built-in functions `getc`, `chr`, `exit`, and `print_error`
|
6
|
+
|
7
|
+
### Changed
|
8
|
+
- Class `Ast::LoxSetExpr` value to assign is now a distinct atrrbute instead of a subnode
|
9
|
+
- Class `BackEnd::Engine` two distinct stacks: one of expression evaluation, another for argument passing
|
10
|
+
|
11
|
+
### Fixed
|
12
|
+
- Class `BackEnd::LoxClass`: in some contexts the init method returned twice 'this'.
|
13
|
+
- Method `LoxFunction#call` mismatch between deeply nested returns and environments
|
14
|
+
|
15
|
+
|
16
|
+
## [0.3.01] - 2021-05-08
|
17
|
+
- Fix in `Scanner` class, added more tests in test suite.
|
18
|
+
|
19
|
+
### New
|
20
|
+
- Added the new subfolder `extra` under `test_suite`. It will contain tests for non-standard features or tests not covered in standard test suite.
|
21
|
+
|
22
|
+
### Fixed
|
23
|
+
- Class `FrontEnd::Scanner`: Couldn't correctly recognize a plus preceding directly a number literal
|
24
|
+
|
25
|
+
## [0.3.00] - 2021-05-07
|
26
|
+
- Milestone: `Loxxy` passes all reference test suite.
|
27
|
+
|
28
|
+
### Fixed
|
29
|
+
- Method `BackEnd::Resolver#before_variable_expr`: Standard `Lox` allows re-declaration of a variable at top-level scope
|
30
|
+
|
31
|
+
|
32
|
+
## [0.2.06] - 2021-05-04
|
33
|
+
- Nearly passing the 'official' test suite, fixing non-compliant behavior, specialized exceptions for errors
|
34
|
+
|
35
|
+
### New
|
36
|
+
- Module `LoxFileTester` module that hosts methods that simplify the tests of `Lox` source file.
|
37
|
+
|
38
|
+
### Changed
|
39
|
+
- Folder `test_suite` vastly reorganized. Sub-folder `baseline` contains spec files testing the `Lox` files from official implementation
|
40
|
+
- Class `BackEnd::Engine` replaced most `StandardError` by `Loxxy::RuntimeError` exception.
|
41
|
+
- Class `BackEnd::Resolver` replaced most `StandardError` by `Loxxy::RuntimeError` exception.
|
42
|
+
- Method `Datatype::Number#/` now handles correctly expression like `0/0` (integer divide)
|
43
|
+
|
44
|
+
### Fixed
|
45
|
+
- `0/0` expression results in a ZeroDivisionError exception, in Lox this result to a NaN (Not a Number). Now, `Loxxy` is aligned to standard `Lox`
|
46
|
+
- `FrontEnd::Scanner` now always treats expression like `-123` as the unary or binary minus operator applied to a positive number.
|
47
|
+
|
48
|
+
## [0.2.05] - 2021-04-26
|
49
|
+
- `Loxxy` now transforms for loops into while loops (desugaring), fix in Scanner class
|
50
|
+
|
51
|
+
### Changed
|
52
|
+
- Method `Ast::ASTBuilder#reduce_for_stmt` converts 'for' loops into 'while' loops
|
53
|
+
- Method `Ast::ASTBuilder#reduce_for_control takes care of case for(expr1;;expr2) now test expression is set to true
|
54
|
+
|
55
|
+
### Fixed
|
56
|
+
- Method `FrontEnd::Scanner#next_token` keyword recognition was case insensitive
|
57
|
+
|
58
|
+
### Removed
|
59
|
+
- Method `Ast::Visitor#visitor_for_stmt`
|
60
|
+
- Method `BackEnd::Engine#after_for_stmt`
|
61
|
+
- Method `BackEnd::Resolver#before_for_stmt`
|
62
|
+
- Method `BackEnd::Resolver#after_for_stmt`
|
63
|
+
|
64
|
+
|
1
65
|
## [0.2.04] - 2021-04-25
|
2
66
|
- `Loxxy` passes the test suite for `for` statements
|
3
67
|
|
data/README.md
CHANGED
@@ -19,11 +19,17 @@ Although __Lox__ is fairly simple, it is far from being a toy language:
|
|
19
19
|
### Loxxy gem features
|
20
20
|
- Complete tree-walking interpreter including lexer, parser and resolver
|
21
21
|
- 100% pure Ruby with clean design (not a port from some other language)
|
22
|
+
- Passes the `jox` (THE reference `Lox` implementation) test suite
|
22
23
|
- Minimal runtime dependency (Rley gem). Won't drag a bunch of gems...
|
23
24
|
- Ruby API for integrating a Lox interpreter with your code.
|
24
25
|
- A command-line interpreter `loxxy`
|
25
26
|
- Open for your language extensions...
|
26
27
|
|
28
|
+
### Why `Loxxy` ?
|
29
|
+
- If programming languages are one of your subject interest...
|
30
|
+
- ... and you wanted learn how to implement one in Ruby...
|
31
|
+
- ... then `Loxxy` can help to understand and experiment in this rewarding craft.
|
32
|
+
|
27
33
|
## How to start in 1, 2, 3...?
|
28
34
|
... in less than 3 minutes...
|
29
35
|
|
@@ -19,7 +19,6 @@ require_relative 'lox_while_stmt'
|
|
19
19
|
require_relative 'lox_return_stmt'
|
20
20
|
require_relative 'lox_print_stmt'
|
21
21
|
require_relative 'lox_if_stmt'
|
22
|
-
require_relative 'lox_for_stmt'
|
23
22
|
require_relative 'lox_var_stmt'
|
24
23
|
require_relative 'lox_class_stmt'
|
25
24
|
require_relative 'lox_seq_decl'
|
@@ -225,16 +225,39 @@ module Loxxy
|
|
225
225
|
end
|
226
226
|
|
227
227
|
# rule('forStmt' => 'FOR LEFT_PAREN forControl RIGHT_PAREN statement')
|
228
|
-
def reduce_for_stmt(_production, _range,
|
229
|
-
|
230
|
-
|
228
|
+
def reduce_for_stmt(_production, _range, tokens, theChildren)
|
229
|
+
# Following 'Crafting Interpreters', we replace the for statement by a while loop
|
230
|
+
return theChildren[4] if theChildren[2].compact.empty? # for(;;) => execute body once
|
231
|
+
|
232
|
+
(init, test, update) = theChildren[2]
|
233
|
+
if update
|
234
|
+
new_body = LoxSeqDecl.new(tokens[0].position, [theChildren[4], update])
|
235
|
+
stmt = Ast::LoxBlockStmt.new(tokens[1].position, new_body)
|
236
|
+
else
|
237
|
+
stmt = theChildren[4]
|
238
|
+
end
|
239
|
+
while_stmt = Ast::LoxWhileStmt.new(tokens[0].position, test, stmt)
|
240
|
+
|
241
|
+
if init
|
242
|
+
block_body = LoxSeqDecl.new(tokens[0].position, [init, while_stmt])
|
243
|
+
for_stmt = Ast::LoxBlockStmt.new(tokens[1].position, block_body)
|
244
|
+
else
|
245
|
+
for_stmt = while_stmt
|
246
|
+
end
|
247
|
+
|
231
248
|
for_stmt
|
232
249
|
end
|
233
250
|
|
234
251
|
# rule('forControl' => 'forInitialization forTest forUpdate')
|
235
252
|
def reduce_for_control(_production, _range, tokens, theChildren)
|
236
253
|
(init, test, update) = theChildren
|
237
|
-
|
254
|
+
if test.nil? && update
|
255
|
+
# when test expr is nil but update expr is not, then force test to be true
|
256
|
+
test = LoxLiteralExpr.new(tokens[0].position, Datatype::True.instance)
|
257
|
+
[init, test, update]
|
258
|
+
else
|
259
|
+
theChildren
|
260
|
+
end
|
238
261
|
end
|
239
262
|
|
240
263
|
# rule('forInitialization' => 'SEMICOLON')
|
@@ -295,7 +318,7 @@ module Loxxy
|
|
295
318
|
name_assignee = theChildren[1].token.lexeme.dup
|
296
319
|
if theChildren[0].kind_of?(Ast::LoxSetExpr)
|
297
320
|
theChildren[0].property = name_assignee
|
298
|
-
theChildren[0].
|
321
|
+
theChildren[0].value = theChildren[3]
|
299
322
|
theChildren[0]
|
300
323
|
else
|
301
324
|
Ast::LoxAssignExpr.new(tokens[1].position, name_assignee, theChildren[3])
|
@@ -75,14 +75,6 @@ module Loxxy
|
|
75
75
|
broadcast(:after_class_stmt, aClassStmt, self)
|
76
76
|
end
|
77
77
|
|
78
|
-
# Visit event. The visitor is about to visit a for statement.
|
79
|
-
# @param aForStmt [AST::LOXForStmt] the for statement node to visit
|
80
|
-
def visit_for_stmt(aForStmt)
|
81
|
-
broadcast(:before_for_stmt, aForStmt)
|
82
|
-
traverse_subnodes(aForStmt) # The condition is visited/evaluated here...
|
83
|
-
broadcast(:after_for_stmt, aForStmt, self)
|
84
|
-
end
|
85
|
-
|
86
78
|
# Visit event. The visitor is about to visit a if statement.
|
87
79
|
# @param anIfStmt [AST::LOXIfStmt] the if statement node to visit
|
88
80
|
def visit_if_stmt(anIfStmt)
|
@@ -111,7 +103,7 @@ module Loxxy
|
|
111
103
|
# @param aWhileStmt [AST::LOXWhileStmt] the while statement node to visit
|
112
104
|
def visit_while_stmt(aWhileStmt)
|
113
105
|
broadcast(:before_while_stmt, aWhileStmt)
|
114
|
-
traverse_subnodes(aWhileStmt) # The condition is visited/evaluated here...
|
106
|
+
traverse_subnodes(aWhileStmt) if aWhileStmt.condition # The condition is visited/evaluated here...
|
115
107
|
broadcast(:after_while_stmt, aWhileStmt, self)
|
116
108
|
end
|
117
109
|
|
@@ -134,7 +126,6 @@ module Loxxy
|
|
134
126
|
# @param aSetExpr [AST::LOXGetExpr] the get expression node to visit
|
135
127
|
def visit_set_expr(aSetExpr)
|
136
128
|
broadcast(:before_set_expr, aSetExpr, self)
|
137
|
-
traverse_subnodes(aSetExpr)
|
138
129
|
broadcast(:after_set_expr, aSetExpr, self)
|
139
130
|
end
|
140
131
|
|
@@ -4,17 +4,20 @@ require_relative 'lox_compound_expr'
|
|
4
4
|
|
5
5
|
module Loxxy
|
6
6
|
module Ast
|
7
|
-
class LoxSetExpr <
|
7
|
+
class LoxSetExpr < LoxNode
|
8
8
|
# @return [Ast::LoxNode] the object to which the property belongs to
|
9
9
|
attr_reader :object
|
10
10
|
|
11
11
|
# @return [String] Name of an object property
|
12
12
|
attr_accessor :property
|
13
13
|
|
14
|
+
# @return [LoxNode, Datatype] value to assign
|
15
|
+
attr_accessor :value
|
16
|
+
|
14
17
|
# @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
|
15
18
|
# @param anObject [Ast::LoxNode] The object which the given property is being set
|
16
19
|
def initialize(aPosition, anObject)
|
17
|
-
super(aPosition
|
20
|
+
super(aPosition)
|
18
21
|
@object = anObject
|
19
22
|
end
|
20
23
|
|
@@ -20,7 +20,7 @@ module Loxxy
|
|
20
20
|
# @return [BackEnd::SymbolTable]
|
21
21
|
attr_reader :symbol_table
|
22
22
|
|
23
|
-
# @return [Array<Datatype::BuiltinDatatype>] Data stack for
|
23
|
+
# @return [Array<Datatype::BuiltinDatatype>] Data stack for arguments and return results
|
24
24
|
attr_reader :stack
|
25
25
|
|
26
26
|
# @return [Hash { Symbol => UnaryOperator}]
|
@@ -35,17 +35,31 @@ module Loxxy
|
|
35
35
|
# @param theOptions [Hash]
|
36
36
|
def initialize(theOptions)
|
37
37
|
@config = theOptions
|
38
|
+
@istream = config.include?(:istream) ? config[:istream] : $stdin
|
38
39
|
@ostream = config.include?(:ostream) ? config[:ostream] : $stdout
|
39
40
|
@symbol_table = SymbolTable.new
|
40
41
|
@stack = []
|
41
|
-
@unary_operators = {}
|
42
|
-
@binary_operators = {}
|
43
42
|
|
43
|
+
reset_expr_stack
|
44
44
|
init_unary_operators
|
45
45
|
init_binary_operators
|
46
46
|
init_globals
|
47
47
|
end
|
48
48
|
|
49
|
+
# Returns the current environment
|
50
|
+
# @return [Loxxy::BackEnd::Environment]
|
51
|
+
def current_env
|
52
|
+
symbol_table.current_env
|
53
|
+
end
|
54
|
+
|
55
|
+
def expr_stack
|
56
|
+
current_env.expr_stack
|
57
|
+
end
|
58
|
+
|
59
|
+
def reset_expr_stack
|
60
|
+
current_env.expr_stack.clear
|
61
|
+
end
|
62
|
+
|
49
63
|
# Given an abstract syntax parse tree visitor, launch the visit
|
50
64
|
# and execute the visit events in the output stream.
|
51
65
|
# @param aVisitor [AST::ASTVisitor]
|
@@ -58,7 +72,7 @@ module Loxxy
|
|
58
72
|
aVisitor.subscribe(self)
|
59
73
|
aVisitor.start
|
60
74
|
aVisitor.unsubscribe(self)
|
61
|
-
|
75
|
+
expr_stack.empty? ? Datatype::Nil.instance : expr_stack.pop
|
62
76
|
end
|
63
77
|
|
64
78
|
##########################################################################
|
@@ -69,12 +83,16 @@ module Loxxy
|
|
69
83
|
# Do nothing, subnodes were already evaluated
|
70
84
|
end
|
71
85
|
|
86
|
+
def before_class_stmt(_class_stmt)
|
87
|
+
reset_expr_stack
|
88
|
+
end
|
89
|
+
|
72
90
|
def after_class_stmt(aClassStmt, aVisitor)
|
73
91
|
if aClassStmt.superclass
|
74
92
|
aClassStmt.superclass.accept(aVisitor)
|
75
|
-
parent =
|
93
|
+
parent = expr_stack.pop
|
76
94
|
unless parent.kind_of?(LoxClass)
|
77
|
-
raise
|
95
|
+
raise Loxxy::RuntimeError, 'Superclass must be a class.'
|
78
96
|
end
|
79
97
|
else
|
80
98
|
parent = nil
|
@@ -89,7 +107,7 @@ module Loxxy
|
|
89
107
|
meths = aClassStmt.body.map do |func_node|
|
90
108
|
func_node.is_method = true
|
91
109
|
func_node.accept(aVisitor)
|
92
|
-
mth =
|
110
|
+
mth = expr_stack.pop
|
93
111
|
mth.is_initializer = true if mth.name == 'init'
|
94
112
|
mth
|
95
113
|
end
|
@@ -104,11 +122,15 @@ module Loxxy
|
|
104
122
|
symbol_table.insert(new_var)
|
105
123
|
end
|
106
124
|
|
125
|
+
def before_var_stmt(_var_stmt)
|
126
|
+
reset_expr_stack
|
127
|
+
end
|
128
|
+
|
107
129
|
def after_var_stmt(aVarStmt)
|
108
130
|
new_var = Variable.new(aVarStmt.name, Datatype::Nil.instance)
|
109
131
|
symbol_table.insert(new_var)
|
110
132
|
|
111
|
-
value =
|
133
|
+
value = expr_stack.pop
|
112
134
|
new_var.assign(value)
|
113
135
|
end
|
114
136
|
|
@@ -116,31 +138,14 @@ module Loxxy
|
|
116
138
|
before_block_stmt(aForStmt)
|
117
139
|
end
|
118
140
|
|
119
|
-
def
|
120
|
-
|
121
|
-
|
122
|
-
loop do
|
123
|
-
if aForStmt.test_expr
|
124
|
-
aForStmt.test_expr.accept(aVisitor)
|
125
|
-
condition = stack.pop
|
126
|
-
break unless condition.truthy?
|
127
|
-
elsif iterating
|
128
|
-
# when both test and update expressions are nil => execute body once
|
129
|
-
break unless aForStmt.update_expr
|
130
|
-
else
|
131
|
-
iterating = true
|
132
|
-
end
|
133
|
-
|
134
|
-
aForStmt.body_stmt.accept(aVisitor)
|
135
|
-
aForStmt.update_expr&.accept(aVisitor)
|
136
|
-
stack.pop
|
137
|
-
end
|
138
|
-
after_block_stmt(aForStmt)
|
141
|
+
def before_if_stmt(_if_stmt)
|
142
|
+
reset_expr_stack
|
139
143
|
end
|
140
144
|
|
141
145
|
def after_if_stmt(anIfStmt, aVisitor)
|
142
146
|
# Retrieve the result of the condition evaluation
|
143
|
-
condition = stack.pop
|
147
|
+
# condition = stack.pop
|
148
|
+
condition = expr_stack.pop
|
144
149
|
if condition.truthy?
|
145
150
|
anIfStmt.then_stmt.accept(aVisitor)
|
146
151
|
elsif anIfStmt.else_stmt
|
@@ -148,26 +153,40 @@ module Loxxy
|
|
148
153
|
end
|
149
154
|
end
|
150
155
|
|
156
|
+
def before_print_stmt(_print_stmt)
|
157
|
+
reset_expr_stack
|
158
|
+
end
|
159
|
+
|
151
160
|
def after_print_stmt(_printStmt)
|
152
|
-
tos =
|
161
|
+
tos = expr_stack.pop
|
153
162
|
@ostream.print tos ? tos.to_str : 'nil'
|
154
163
|
end
|
155
164
|
|
165
|
+
def before_return_stmt(_return_stmt)
|
166
|
+
reset_expr_stack
|
167
|
+
end
|
168
|
+
|
156
169
|
def after_return_stmt(_returnStmt, _aVisitor)
|
170
|
+
stack.push(expr_stack.pop)
|
157
171
|
throw(:return)
|
158
172
|
end
|
159
173
|
|
174
|
+
def before_while_stmt(_while_stmt)
|
175
|
+
reset_expr_stack
|
176
|
+
end
|
177
|
+
|
160
178
|
def after_while_stmt(aWhileStmt, aVisitor)
|
161
179
|
loop do
|
162
|
-
condition =
|
180
|
+
condition = expr_stack.pop
|
163
181
|
break unless condition.truthy?
|
164
182
|
|
165
183
|
aWhileStmt.body.accept(aVisitor)
|
166
|
-
aWhileStmt.condition
|
184
|
+
aWhileStmt.condition&.accept(aVisitor)
|
167
185
|
end
|
168
186
|
end
|
169
187
|
|
170
188
|
def before_block_stmt(_aBlockStmt)
|
189
|
+
reset_expr_stack
|
171
190
|
new_env = Environment.new
|
172
191
|
symbol_table.enter_environment(new_env)
|
173
192
|
end
|
@@ -179,42 +198,44 @@ module Loxxy
|
|
179
198
|
def after_assign_expr(anAssignExpr, _visitor)
|
180
199
|
var_name = anAssignExpr.name
|
181
200
|
variable = variable_lookup(anAssignExpr)
|
182
|
-
raise
|
201
|
+
raise Loxxy::RuntimeError, "Undefined variable '#{var_name}'." unless variable
|
183
202
|
|
184
|
-
value =
|
203
|
+
value = expr_stack.last # ToS remains since an assignment produces a value
|
185
204
|
variable.assign(value)
|
186
205
|
end
|
187
206
|
|
188
|
-
def before_set_expr(
|
189
|
-
|
190
|
-
aSetExpr.object.accept(aVisitor)
|
207
|
+
def before_set_expr(_set_expr, _visitor)
|
208
|
+
reset_expr_stack
|
191
209
|
end
|
192
210
|
|
193
|
-
def after_set_expr(aSetExpr,
|
194
|
-
|
195
|
-
|
211
|
+
def after_set_expr(aSetExpr, aVisitor)
|
212
|
+
# Evaluate receiver object part (i.e. 'this')
|
213
|
+
aSetExpr.object.accept(aVisitor)
|
214
|
+
assignee = expr_stack.pop
|
196
215
|
unless assignee.kind_of?(LoxInstance)
|
197
216
|
raise Loxxy::RuntimeError, 'Only instances have fields.'
|
198
217
|
end
|
199
218
|
|
219
|
+
aSetExpr.value.accept(aVisitor)
|
220
|
+
value = expr_stack.pop
|
221
|
+
|
200
222
|
assignee.set(aSetExpr.property, value)
|
201
|
-
|
223
|
+
expr_stack.push value
|
202
224
|
end
|
203
225
|
|
204
226
|
def after_logical_expr(aLogicalExpr, visitor)
|
205
227
|
op = aLogicalExpr.operator
|
206
|
-
operand1 =
|
228
|
+
operand1 = expr_stack.pop # only first operand was evaluated
|
207
229
|
result = nil
|
208
230
|
if ((op == :and) && operand1.falsey?) || ((op == :or) && operand1.truthy?)
|
209
231
|
result = operand1
|
210
232
|
else
|
211
233
|
raw_operand2 = aLogicalExpr.subnodes[1]
|
212
234
|
raw_operand2.accept(visitor) # Visit means operand2 is evaluated
|
213
|
-
operand2 =
|
235
|
+
operand2 = expr_stack.pop
|
214
236
|
result = logical_2nd_arg(operand2)
|
215
237
|
end
|
216
|
-
|
217
|
-
stack.push result
|
238
|
+
expr_stack.push result
|
218
239
|
end
|
219
240
|
|
220
241
|
def logical_2nd_arg(operand2)
|
@@ -234,13 +255,14 @@ module Loxxy
|
|
234
255
|
end
|
235
256
|
|
236
257
|
def after_binary_expr(aBinaryExpr)
|
237
|
-
operand2 =
|
238
|
-
operand1 =
|
258
|
+
operand2 = expr_stack.pop
|
259
|
+
operand1 = expr_stack.pop
|
239
260
|
op = aBinaryExpr.operator
|
240
261
|
operator = binary_operators[op]
|
241
262
|
operator.validate_operands(operand1, operand2)
|
242
263
|
if operand1.respond_to?(op)
|
243
|
-
|
264
|
+
result = operand1.send(op, operand2)
|
265
|
+
expr_stack.push convert2lox_datatype(result)
|
244
266
|
else
|
245
267
|
msg1 = "`#{op}': Unimplemented operator for a #{operand1.class}."
|
246
268
|
raise StandardError, msg1
|
@@ -248,12 +270,13 @@ module Loxxy
|
|
248
270
|
end
|
249
271
|
|
250
272
|
def after_unary_expr(anUnaryExpr)
|
251
|
-
operand =
|
273
|
+
operand = expr_stack.pop
|
252
274
|
op = anUnaryExpr.operator
|
253
275
|
operator = unary_operators[op]
|
254
276
|
operator.validate_operand(operand)
|
255
277
|
if operand.respond_to?(op)
|
256
|
-
|
278
|
+
result = operand.send(op)
|
279
|
+
expr_stack.push convert2lox_datatype(result)
|
257
280
|
else
|
258
281
|
msg1 = "`#{op}': Unimplemented operator for a #{operand.class}."
|
259
282
|
raise StandardError, msg1
|
@@ -263,12 +286,17 @@ module Loxxy
|
|
263
286
|
def after_call_expr(aCallExpr, aVisitor)
|
264
287
|
# Evaluate callee part
|
265
288
|
aCallExpr.callee.accept(aVisitor)
|
266
|
-
callee =
|
289
|
+
callee = expr_stack.pop
|
290
|
+
before_size = expr_stack.size
|
267
291
|
aCallExpr.arguments.reverse_each { |arg| arg.accept(aVisitor) }
|
292
|
+
after_size = expr_stack.size
|
293
|
+
if after_size > before_size
|
294
|
+
stack.concat(expr_stack.pop(after_size - before_size))
|
295
|
+
end
|
268
296
|
|
269
297
|
case callee
|
270
298
|
when NativeFunction
|
271
|
-
|
299
|
+
expr_stack.push callee.call # Pass arguments
|
272
300
|
when LoxFunction, LoxClass
|
273
301
|
arg_count = aCallExpr.arguments.size
|
274
302
|
if arg_count != callee.arity
|
@@ -283,12 +311,12 @@ module Loxxy
|
|
283
311
|
|
284
312
|
def after_get_expr(aGetExpr, aVisitor)
|
285
313
|
aGetExpr.object.accept(aVisitor)
|
286
|
-
instance =
|
314
|
+
instance = expr_stack.pop
|
287
315
|
unless instance.kind_of?(LoxInstance)
|
288
316
|
raise Loxxy::RuntimeError, 'Only instances have properties.'
|
289
317
|
end
|
290
318
|
|
291
|
-
|
319
|
+
expr_stack.push instance.get(aGetExpr.property)
|
292
320
|
end
|
293
321
|
|
294
322
|
def after_grouping_expr(_groupingExpr)
|
@@ -305,7 +333,7 @@ module Loxxy
|
|
305
333
|
|
306
334
|
# @param literalExpr [Ast::LoxLiteralExpr]
|
307
335
|
def before_literal_expr(literalExpr)
|
308
|
-
|
336
|
+
expr_stack.push(literalExpr.literal)
|
309
337
|
end
|
310
338
|
|
311
339
|
def after_this_expr(aThisExpr, aVisitor)
|
@@ -321,21 +349,25 @@ module Loxxy
|
|
321
349
|
superklass = variable_lookup(aSuperExpr).value.superclass
|
322
350
|
method = superklass.find_method(aSuperExpr.property)
|
323
351
|
unless method
|
324
|
-
raise
|
352
|
+
raise Loxxy::RuntimeError, "Undefined property '#{aSuperExpr.property}'."
|
325
353
|
end
|
326
354
|
|
327
|
-
|
355
|
+
expr_stack.push method.bind(instance)
|
328
356
|
end
|
329
357
|
|
330
358
|
# @param aValue [Ast::BuiltinDattype] the built-in datatype value
|
331
359
|
def before_visit_builtin(aValue)
|
332
|
-
|
360
|
+
expr_stack.push(aValue)
|
361
|
+
end
|
362
|
+
|
363
|
+
def before_fun_stmt(_fun_stmt, _visitor)
|
364
|
+
reset_expr_stack
|
333
365
|
end
|
334
366
|
|
335
367
|
def after_fun_stmt(aFunStmt, _visitor)
|
336
368
|
function = LoxFunction.new(aFunStmt.name, aFunStmt.params, aFunStmt.body, self)
|
337
369
|
if aFunStmt.is_method
|
338
|
-
|
370
|
+
expr_stack.push function
|
339
371
|
else
|
340
372
|
new_var = Variable.new(aFunStmt.name, function)
|
341
373
|
symbol_table.insert(new_var)
|
@@ -357,30 +389,20 @@ module Loxxy
|
|
357
389
|
env.defns[aVarNode.name]
|
358
390
|
end
|
359
391
|
|
360
|
-
NativeFunction = Struct.new(:callable, :interp) do
|
361
|
-
def accept(_visitor)
|
362
|
-
interp.stack.push self
|
363
|
-
end
|
364
|
-
|
365
|
-
def call
|
366
|
-
callable.call
|
367
|
-
end
|
368
|
-
|
369
|
-
def to_str
|
370
|
-
'<native fn>'
|
371
|
-
end
|
372
|
-
end
|
373
|
-
|
374
392
|
def init_unary_operators
|
393
|
+
@unary_operators = {}
|
394
|
+
|
375
395
|
negate_op = UnaryOperator.new('-', [Datatype::Number])
|
376
396
|
unary_operators[:-@] = negate_op
|
377
397
|
|
378
398
|
negation_op = UnaryOperator.new('!', [Datatype::BuiltinDatatype,
|
379
|
-
BackEnd::LoxFunction])
|
399
|
+
BackEnd::LoxInstance, BackEnd::LoxFunction, BackEnd::LoxClass])
|
380
400
|
unary_operators[:!] = negation_op
|
381
401
|
end
|
382
402
|
|
383
403
|
def init_binary_operators
|
404
|
+
@binary_operators = {}
|
405
|
+
|
384
406
|
plus_op = BinaryOperator.new('+', [[Datatype::Number, :idem],
|
385
407
|
[Datatype::LXString, :idem]])
|
386
408
|
binary_operators[:+] = plus_op
|
@@ -415,6 +437,24 @@ module Loxxy
|
|
415
437
|
|
416
438
|
def init_globals
|
417
439
|
add_native_fun('clock', native_clock)
|
440
|
+
add_native_fun('getc', native_getc)
|
441
|
+
add_native_fun('chr', native_chr)
|
442
|
+
add_native_fun('exit', native_exit)
|
443
|
+
add_native_fun('print_error', native_print_error)
|
444
|
+
end
|
445
|
+
|
446
|
+
NativeFunction = Struct.new(:callable, :interp) do
|
447
|
+
def accept(_visitor)
|
448
|
+
interp.expr_stack.push self
|
449
|
+
end
|
450
|
+
|
451
|
+
def call
|
452
|
+
callable.call
|
453
|
+
end
|
454
|
+
|
455
|
+
def to_str
|
456
|
+
'<native fn>'
|
457
|
+
end
|
418
458
|
end
|
419
459
|
|
420
460
|
def add_native_fun(aName, aProc)
|
@@ -430,6 +470,47 @@ module Loxxy
|
|
430
470
|
Datatype::Number.new(now)
|
431
471
|
end
|
432
472
|
end
|
473
|
+
|
474
|
+
# Read a single character and return the character code as an integer.
|
475
|
+
def native_getc
|
476
|
+
proc do
|
477
|
+
ch = @istream.getc
|
478
|
+
Datatype::Number.new(ch.codepoints[0])
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
# chr(ch): Convert given character code number to a single-character string.
|
483
|
+
def native_chr
|
484
|
+
proc do
|
485
|
+
codepoint = stack.pop
|
486
|
+
Datatype::LXString.new(codepoint.value.chr)
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
# exit(status): Exit with given status code.
|
491
|
+
def native_exit
|
492
|
+
proc do
|
493
|
+
status = stack.pop
|
494
|
+
exit(status.value)
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
# print_error(message): Print message string on stderr.
|
499
|
+
def native_print_error
|
500
|
+
proc do
|
501
|
+
message = stack.pop
|
502
|
+
$stderr.print message.value
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
def convert2lox_datatype(item)
|
507
|
+
case item
|
508
|
+
when TrueClass then Datatype::True.instance
|
509
|
+
when FalseClass then Datatype::False.instance
|
510
|
+
else
|
511
|
+
item
|
512
|
+
end
|
513
|
+
end
|
433
514
|
end # class
|
434
515
|
end # module
|
435
516
|
end # module
|