loxxy 0.2.05 → 0.3.03
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +57 -0
- data/README.md +6 -0
- data/lib/loxxy/ast/ast_builder.rb +4 -3
- data/lib/loxxy/ast/ast_visitor.rb +0 -1
- data/lib/loxxy/ast/lox_set_expr.rb +5 -2
- data/lib/loxxy/back_end/engine.rb +158 -52
- 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 +17 -9
- data/lib/loxxy/datatype/lx_string.rb +2 -3
- data/lib/loxxy/datatype/number.rb +19 -4
- data/lib/loxxy/front_end/scanner.rb +59 -35
- 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 +41 -17
- data/spec/interpreter_spec.rb +57 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 734fc040d2487c17abd8d89d4f3422e4d55db226ef79b354e52402fc24cd61fc
|
4
|
+
data.tar.gz: e799c5d1044159e9bfdc1b337c370e95878c85752dcaaf60578e71c4763a7da1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d6d393729d977e6493979f4e89c664a1bba685b08c67a5e9e9b0d12354ac552deadae4e15a213560b752bdb9b44c7a05a5652fc8b4ed6500409fa04fe3cf29c1
|
7
|
+
data.tar.gz: f3b121e4a5ca07b4297aca8c43a3325b00dd30fb131f412a17a5e82d7887e8fc8749a2e1a422c75fe0c1045652970a121a78883b06c6bbc6baa6d896b0fea03f
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,60 @@
|
|
1
|
+
## [0.3.03] - 2021-05-23
|
2
|
+
- Fixes in the location of an undefined variable. Rewrite of the scanning of lox string.
|
3
|
+
|
4
|
+
### Changed
|
5
|
+
- Method `BackEnd::Engine#after_variable_expr` the error message `Undefined variable` nows gives the location of the offending variable.
|
6
|
+
- Class `FrontEnd#Scanner` complete refactoring of String recognition.
|
7
|
+
|
8
|
+
### Fixed
|
9
|
+
- Method `Ast::AstBuilder#reduce_variable_expr` now associates the correct location of the variable.
|
10
|
+
|
11
|
+
## [0.3.02] - 2021-05-22
|
12
|
+
- New built-in expressions `getc`, `chr`, `exit` and `print_eeror` , fixes with deeply nested returns, set expressions
|
13
|
+
|
14
|
+
### New
|
15
|
+
- Class `BackEnd::Engine` new built-in functions `getc`, `chr`, `exit`, and `print_error`
|
16
|
+
|
17
|
+
### Changed
|
18
|
+
- Class `Ast::LoxSetExpr` value to assign is now a distinct atrrbute instead of a subnode
|
19
|
+
- Class `BackEnd::Engine` two distinct stacks: one of expression evaluation, another for argument passing
|
20
|
+
|
21
|
+
### Fixed
|
22
|
+
- Class `BackEnd::LoxClass`: in some contexts the init method returned twice 'this'.
|
23
|
+
- Method `LoxFunction#call` mismatch between deeply nested returns and environments
|
24
|
+
|
25
|
+
|
26
|
+
## [0.3.01] - 2021-05-08
|
27
|
+
- Fix in `Scanner` class, added more tests in test suite.
|
28
|
+
|
29
|
+
### New
|
30
|
+
- Added the new subfolder `extra` under `test_suite`. It will contain tests for non-standard features or tests not covered in standard test suite.
|
31
|
+
|
32
|
+
### Fixed
|
33
|
+
- Class `FrontEnd::Scanner`: Couldn't correctly recognize a plus preceding directly a number literal
|
34
|
+
|
35
|
+
## [0.3.00] - 2021-05-07
|
36
|
+
- Milestone: `Loxxy` passes all reference test suite.
|
37
|
+
|
38
|
+
### Fixed
|
39
|
+
- Method `BackEnd::Resolver#before_variable_expr`: Standard `Lox` allows re-declaration of a variable at top-level scope
|
40
|
+
|
41
|
+
|
42
|
+
## [0.2.06] - 2021-05-04
|
43
|
+
- Nearly passing the 'official' test suite, fixing non-compliant behavior, specialized exceptions for errors
|
44
|
+
|
45
|
+
### New
|
46
|
+
- Module `LoxFileTester` module that hosts methods that simplify the tests of `Lox` source file.
|
47
|
+
|
48
|
+
### Changed
|
49
|
+
- Folder `test_suite` vastly reorganized. Sub-folder `baseline` contains spec files testing the `Lox` files from official implementation
|
50
|
+
- Class `BackEnd::Engine` replaced most `StandardError` by `Loxxy::RuntimeError` exception.
|
51
|
+
- Class `BackEnd::Resolver` replaced most `StandardError` by `Loxxy::RuntimeError` exception.
|
52
|
+
- Method `Datatype::Number#/` now handles correctly expression like `0/0` (integer divide)
|
53
|
+
|
54
|
+
### Fixed
|
55
|
+
- `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`
|
56
|
+
- `FrontEnd::Scanner` now always treats expression like `-123` as the unary or binary minus operator applied to a positive number.
|
57
|
+
|
1
58
|
## [0.2.05] - 2021-04-26
|
2
59
|
- `Loxxy` now transforms for loops into while loops (desugaring), fix in Scanner class
|
3
60
|
|
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
|
|
@@ -318,7 +318,7 @@ module Loxxy
|
|
318
318
|
name_assignee = theChildren[1].token.lexeme.dup
|
319
319
|
if theChildren[0].kind_of?(Ast::LoxSetExpr)
|
320
320
|
theChildren[0].property = name_assignee
|
321
|
-
theChildren[0].
|
321
|
+
theChildren[0].value = theChildren[3]
|
322
322
|
theChildren[0]
|
323
323
|
else
|
324
324
|
Ast::LoxAssignExpr.new(tokens[1].position, name_assignee, theChildren[3])
|
@@ -395,9 +395,10 @@ module Loxxy
|
|
395
395
|
end
|
396
396
|
|
397
397
|
# rule('primary' => 'IDENTIFIER')
|
398
|
-
def reduce_variable_expr(_production, _range,
|
398
|
+
def reduce_variable_expr(_production, _range, _tokens, theChildren)
|
399
399
|
var_name = theChildren[0].token.lexeme
|
400
|
-
|
400
|
+
pos = theChildren[0].token.position
|
401
|
+
LoxVariableExpr.new(pos, var_name)
|
401
402
|
end
|
402
403
|
|
403
404
|
# rule('primary' => 'THIS')
|
@@ -126,7 +126,6 @@ module Loxxy
|
|
126
126
|
# @param aSetExpr [AST::LOXGetExpr] the get expression node to visit
|
127
127
|
def visit_set_expr(aSetExpr)
|
128
128
|
broadcast(:before_set_expr, aSetExpr, self)
|
129
|
-
traverse_subnodes(aSetExpr)
|
130
129
|
broadcast(:after_set_expr, aSetExpr, self)
|
131
130
|
end
|
132
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,10 +83,14 @@ 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
95
|
raise Loxxy::RuntimeError, 'Superclass must be a class.'
|
78
96
|
end
|
@@ -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,9 +138,14 @@ module Loxxy
|
|
116
138
|
before_block_stmt(aForStmt)
|
117
139
|
end
|
118
140
|
|
141
|
+
def before_if_stmt(_if_stmt)
|
142
|
+
reset_expr_stack
|
143
|
+
end
|
144
|
+
|
119
145
|
def after_if_stmt(anIfStmt, aVisitor)
|
120
146
|
# Retrieve the result of the condition evaluation
|
121
|
-
condition = stack.pop
|
147
|
+
# condition = stack.pop
|
148
|
+
condition = expr_stack.pop
|
122
149
|
if condition.truthy?
|
123
150
|
anIfStmt.then_stmt.accept(aVisitor)
|
124
151
|
elsif anIfStmt.else_stmt
|
@@ -126,18 +153,31 @@ module Loxxy
|
|
126
153
|
end
|
127
154
|
end
|
128
155
|
|
156
|
+
def before_print_stmt(_print_stmt)
|
157
|
+
reset_expr_stack
|
158
|
+
end
|
159
|
+
|
129
160
|
def after_print_stmt(_printStmt)
|
130
|
-
tos =
|
161
|
+
tos = expr_stack.pop
|
131
162
|
@ostream.print tos ? tos.to_str : 'nil'
|
132
163
|
end
|
133
164
|
|
165
|
+
def before_return_stmt(_return_stmt)
|
166
|
+
reset_expr_stack
|
167
|
+
end
|
168
|
+
|
134
169
|
def after_return_stmt(_returnStmt, _aVisitor)
|
170
|
+
stack.push(expr_stack.pop)
|
135
171
|
throw(:return)
|
136
172
|
end
|
137
173
|
|
174
|
+
def before_while_stmt(_while_stmt)
|
175
|
+
reset_expr_stack
|
176
|
+
end
|
177
|
+
|
138
178
|
def after_while_stmt(aWhileStmt, aVisitor)
|
139
179
|
loop do
|
140
|
-
condition =
|
180
|
+
condition = expr_stack.pop
|
141
181
|
break unless condition.truthy?
|
142
182
|
|
143
183
|
aWhileStmt.body.accept(aVisitor)
|
@@ -146,6 +186,7 @@ module Loxxy
|
|
146
186
|
end
|
147
187
|
|
148
188
|
def before_block_stmt(_aBlockStmt)
|
189
|
+
reset_expr_stack
|
149
190
|
new_env = Environment.new
|
150
191
|
symbol_table.enter_environment(new_env)
|
151
192
|
end
|
@@ -159,40 +200,42 @@ module Loxxy
|
|
159
200
|
variable = variable_lookup(anAssignExpr)
|
160
201
|
raise Loxxy::RuntimeError, "Undefined variable '#{var_name}'." unless variable
|
161
202
|
|
162
|
-
value =
|
203
|
+
value = expr_stack.last # ToS remains since an assignment produces a value
|
163
204
|
variable.assign(value)
|
164
205
|
end
|
165
206
|
|
166
|
-
def before_set_expr(
|
167
|
-
|
168
|
-
aSetExpr.object.accept(aVisitor)
|
207
|
+
def before_set_expr(_set_expr, _visitor)
|
208
|
+
reset_expr_stack
|
169
209
|
end
|
170
210
|
|
171
|
-
def after_set_expr(aSetExpr,
|
172
|
-
|
173
|
-
|
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
|
174
215
|
unless assignee.kind_of?(LoxInstance)
|
175
216
|
raise Loxxy::RuntimeError, 'Only instances have fields.'
|
176
217
|
end
|
177
218
|
|
219
|
+
aSetExpr.value.accept(aVisitor)
|
220
|
+
value = expr_stack.pop
|
221
|
+
|
178
222
|
assignee.set(aSetExpr.property, value)
|
179
|
-
|
223
|
+
expr_stack.push value
|
180
224
|
end
|
181
225
|
|
182
226
|
def after_logical_expr(aLogicalExpr, visitor)
|
183
227
|
op = aLogicalExpr.operator
|
184
|
-
operand1 =
|
228
|
+
operand1 = expr_stack.pop # only first operand was evaluated
|
185
229
|
result = nil
|
186
230
|
if ((op == :and) && operand1.falsey?) || ((op == :or) && operand1.truthy?)
|
187
231
|
result = operand1
|
188
232
|
else
|
189
233
|
raw_operand2 = aLogicalExpr.subnodes[1]
|
190
234
|
raw_operand2.accept(visitor) # Visit means operand2 is evaluated
|
191
|
-
operand2 =
|
235
|
+
operand2 = expr_stack.pop
|
192
236
|
result = logical_2nd_arg(operand2)
|
193
237
|
end
|
194
|
-
|
195
|
-
stack.push result
|
238
|
+
expr_stack.push result
|
196
239
|
end
|
197
240
|
|
198
241
|
def logical_2nd_arg(operand2)
|
@@ -212,13 +255,14 @@ module Loxxy
|
|
212
255
|
end
|
213
256
|
|
214
257
|
def after_binary_expr(aBinaryExpr)
|
215
|
-
operand2 =
|
216
|
-
operand1 =
|
258
|
+
operand2 = expr_stack.pop
|
259
|
+
operand1 = expr_stack.pop
|
217
260
|
op = aBinaryExpr.operator
|
218
261
|
operator = binary_operators[op]
|
219
262
|
operator.validate_operands(operand1, operand2)
|
220
263
|
if operand1.respond_to?(op)
|
221
|
-
|
264
|
+
result = operand1.send(op, operand2)
|
265
|
+
expr_stack.push convert2lox_datatype(result)
|
222
266
|
else
|
223
267
|
msg1 = "`#{op}': Unimplemented operator for a #{operand1.class}."
|
224
268
|
raise StandardError, msg1
|
@@ -226,12 +270,13 @@ module Loxxy
|
|
226
270
|
end
|
227
271
|
|
228
272
|
def after_unary_expr(anUnaryExpr)
|
229
|
-
operand =
|
273
|
+
operand = expr_stack.pop
|
230
274
|
op = anUnaryExpr.operator
|
231
275
|
operator = unary_operators[op]
|
232
276
|
operator.validate_operand(operand)
|
233
277
|
if operand.respond_to?(op)
|
234
|
-
|
278
|
+
result = operand.send(op)
|
279
|
+
expr_stack.push convert2lox_datatype(result)
|
235
280
|
else
|
236
281
|
msg1 = "`#{op}': Unimplemented operator for a #{operand.class}."
|
237
282
|
raise StandardError, msg1
|
@@ -241,12 +286,17 @@ module Loxxy
|
|
241
286
|
def after_call_expr(aCallExpr, aVisitor)
|
242
287
|
# Evaluate callee part
|
243
288
|
aCallExpr.callee.accept(aVisitor)
|
244
|
-
callee =
|
289
|
+
callee = expr_stack.pop
|
290
|
+
before_size = expr_stack.size
|
245
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
|
246
296
|
|
247
297
|
case callee
|
248
298
|
when NativeFunction
|
249
|
-
|
299
|
+
expr_stack.push callee.call # Pass arguments
|
250
300
|
when LoxFunction, LoxClass
|
251
301
|
arg_count = aCallExpr.arguments.size
|
252
302
|
if arg_count != callee.arity
|
@@ -261,12 +311,12 @@ module Loxxy
|
|
261
311
|
|
262
312
|
def after_get_expr(aGetExpr, aVisitor)
|
263
313
|
aGetExpr.object.accept(aVisitor)
|
264
|
-
instance =
|
314
|
+
instance = expr_stack.pop
|
265
315
|
unless instance.kind_of?(LoxInstance)
|
266
316
|
raise Loxxy::RuntimeError, 'Only instances have properties.'
|
267
317
|
end
|
268
318
|
|
269
|
-
|
319
|
+
expr_stack.push instance.get(aGetExpr.property)
|
270
320
|
end
|
271
321
|
|
272
322
|
def after_grouping_expr(_groupingExpr)
|
@@ -276,14 +326,17 @@ module Loxxy
|
|
276
326
|
def after_variable_expr(aVarExpr, aVisitor)
|
277
327
|
var_name = aVarExpr.name
|
278
328
|
var = variable_lookup(aVarExpr)
|
279
|
-
|
329
|
+
unless var
|
330
|
+
pos = "line #{aVarExpr.position.line}:#{aVarExpr.position.column}"
|
331
|
+
raise Loxxy::RuntimeError, "[#{pos}] Undefined variable '#{var_name}'."
|
332
|
+
end
|
280
333
|
|
281
334
|
var.value.accept(aVisitor) # Evaluate variable value then push on stack
|
282
335
|
end
|
283
336
|
|
284
337
|
# @param literalExpr [Ast::LoxLiteralExpr]
|
285
338
|
def before_literal_expr(literalExpr)
|
286
|
-
|
339
|
+
expr_stack.push(literalExpr.literal)
|
287
340
|
end
|
288
341
|
|
289
342
|
def after_this_expr(aThisExpr, aVisitor)
|
@@ -299,21 +352,25 @@ module Loxxy
|
|
299
352
|
superklass = variable_lookup(aSuperExpr).value.superclass
|
300
353
|
method = superklass.find_method(aSuperExpr.property)
|
301
354
|
unless method
|
302
|
-
raise
|
355
|
+
raise Loxxy::RuntimeError, "Undefined property '#{aSuperExpr.property}'."
|
303
356
|
end
|
304
357
|
|
305
|
-
|
358
|
+
expr_stack.push method.bind(instance)
|
306
359
|
end
|
307
360
|
|
308
361
|
# @param aValue [Ast::BuiltinDattype] the built-in datatype value
|
309
362
|
def before_visit_builtin(aValue)
|
310
|
-
|
363
|
+
expr_stack.push(aValue)
|
364
|
+
end
|
365
|
+
|
366
|
+
def before_fun_stmt(_fun_stmt, _visitor)
|
367
|
+
reset_expr_stack
|
311
368
|
end
|
312
369
|
|
313
370
|
def after_fun_stmt(aFunStmt, _visitor)
|
314
371
|
function = LoxFunction.new(aFunStmt.name, aFunStmt.params, aFunStmt.body, self)
|
315
372
|
if aFunStmt.is_method
|
316
|
-
|
373
|
+
expr_stack.push function
|
317
374
|
else
|
318
375
|
new_var = Variable.new(aFunStmt.name, function)
|
319
376
|
symbol_table.insert(new_var)
|
@@ -335,30 +392,20 @@ module Loxxy
|
|
335
392
|
env.defns[aVarNode.name]
|
336
393
|
end
|
337
394
|
|
338
|
-
NativeFunction = Struct.new(:callable, :interp) do
|
339
|
-
def accept(_visitor)
|
340
|
-
interp.stack.push self
|
341
|
-
end
|
342
|
-
|
343
|
-
def call
|
344
|
-
callable.call
|
345
|
-
end
|
346
|
-
|
347
|
-
def to_str
|
348
|
-
'<native fn>'
|
349
|
-
end
|
350
|
-
end
|
351
|
-
|
352
395
|
def init_unary_operators
|
396
|
+
@unary_operators = {}
|
397
|
+
|
353
398
|
negate_op = UnaryOperator.new('-', [Datatype::Number])
|
354
399
|
unary_operators[:-@] = negate_op
|
355
400
|
|
356
401
|
negation_op = UnaryOperator.new('!', [Datatype::BuiltinDatatype,
|
357
|
-
BackEnd::LoxFunction])
|
402
|
+
BackEnd::LoxInstance, BackEnd::LoxFunction, BackEnd::LoxClass])
|
358
403
|
unary_operators[:!] = negation_op
|
359
404
|
end
|
360
405
|
|
361
406
|
def init_binary_operators
|
407
|
+
@binary_operators = {}
|
408
|
+
|
362
409
|
plus_op = BinaryOperator.new('+', [[Datatype::Number, :idem],
|
363
410
|
[Datatype::LXString, :idem]])
|
364
411
|
binary_operators[:+] = plus_op
|
@@ -393,6 +440,24 @@ module Loxxy
|
|
393
440
|
|
394
441
|
def init_globals
|
395
442
|
add_native_fun('clock', native_clock)
|
443
|
+
add_native_fun('getc', native_getc)
|
444
|
+
add_native_fun('chr', native_chr)
|
445
|
+
add_native_fun('exit', native_exit)
|
446
|
+
add_native_fun('print_error', native_print_error)
|
447
|
+
end
|
448
|
+
|
449
|
+
NativeFunction = Struct.new(:callable, :interp) do
|
450
|
+
def accept(_visitor)
|
451
|
+
interp.expr_stack.push self
|
452
|
+
end
|
453
|
+
|
454
|
+
def call
|
455
|
+
callable.call
|
456
|
+
end
|
457
|
+
|
458
|
+
def to_str
|
459
|
+
'<native fn>'
|
460
|
+
end
|
396
461
|
end
|
397
462
|
|
398
463
|
def add_native_fun(aName, aProc)
|
@@ -408,6 +473,47 @@ module Loxxy
|
|
408
473
|
Datatype::Number.new(now)
|
409
474
|
end
|
410
475
|
end
|
476
|
+
|
477
|
+
# Read a single character and return the character code as an integer.
|
478
|
+
def native_getc
|
479
|
+
proc do
|
480
|
+
ch = @istream.getc
|
481
|
+
Datatype::Number.new(ch.codepoints[0])
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
# chr(ch): Convert given character code number to a single-character string.
|
486
|
+
def native_chr
|
487
|
+
proc do
|
488
|
+
codepoint = stack.pop
|
489
|
+
Datatype::LXString.new(codepoint.value.chr)
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
# exit(status): Exit with given status code.
|
494
|
+
def native_exit
|
495
|
+
proc do
|
496
|
+
status = stack.pop
|
497
|
+
exit(status.value)
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
# print_error(message): Print message string on stderr.
|
502
|
+
def native_print_error
|
503
|
+
proc do
|
504
|
+
message = stack.pop
|
505
|
+
$stderr.print message.value
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
509
|
+
def convert2lox_datatype(item)
|
510
|
+
case item
|
511
|
+
when TrueClass then Datatype::True.instance
|
512
|
+
when FalseClass then Datatype::False.instance
|
513
|
+
else
|
514
|
+
item
|
515
|
+
end
|
516
|
+
end
|
411
517
|
end # class
|
412
518
|
end # module
|
413
519
|
end # module
|