loxxy 0.2.05 → 0.3.03
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/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
|