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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d628b0737a29bab0869827af50a363846200bfab7baddc63279e0e3fb904fa75
4
- data.tar.gz: d617939297fe24cf267fb66c198cbc172e05a7df4472eac546bd9b5e27eef0cd
3
+ metadata.gz: a0e398714d64763c288f06c315611c86bf3dae2e3403055ae3ca240a10bd292f
4
+ data.tar.gz: 8db95e837824181e1794277dc3e67e4005432006877124bbd62ffd07def57591
5
5
  SHA512:
6
- metadata.gz: dde10867e0a0243a5c0e2349a37df2ec76178aa2a7da625abd42363175301bf773d8d56aa249dde380361b13af5181c33a8ed65a4cf1bd2bbdaf2c946b534f99
7
- data.tar.gz: 204243d4d781f54820a66d483d791676d144b3a4af7febbb2a1a27041c651d2ea4a0015e2bf3408754c9ea31dc0f66b4c6b0d8ad6175edc5912391331f95e9e1
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, _tokens, theChildren)
229
- for_stmt = theChildren[2]
230
- for_stmt.body_stmt = theChildren[4]
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
- Ast::LoxForStmt.new(tokens[0].position, init, test, update)
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].subnodes << theChildren[3]
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 < LoxCompoundExpr
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 the expression results
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
- stack.empty? ? Datatype::Nil.instance : stack.pop
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 = stack.pop
93
+ parent = expr_stack.pop
76
94
  unless parent.kind_of?(LoxClass)
77
- raise StandardError, 'Superclass must be a class.'
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 = stack.pop
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 = stack.pop
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 after_for_stmt(aForStmt, aVisitor)
120
- iterating = false
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 = stack.pop
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 = stack.pop
180
+ condition = expr_stack.pop
163
181
  break unless condition.truthy?
164
182
 
165
183
  aWhileStmt.body.accept(aVisitor)
166
- aWhileStmt.condition.accept(aVisitor)
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 StandardError, "Unknown variable #{var_name}" unless variable
201
+ raise Loxxy::RuntimeError, "Undefined variable '#{var_name}'." unless variable
183
202
 
184
- value = stack.last # ToS remains since an assignment produces a 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(aSetExpr, aVisitor)
189
- # Evaluate receiver object part
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, _visitor)
194
- value = stack.pop
195
- assignee = stack.pop
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
- stack.push value
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 = stack.pop # only first operand was evaluated
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 = stack.pop
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 = stack.pop
238
- operand1 = stack.pop
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
- stack.push operand1.send(op, operand2)
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 = stack.pop
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
- stack.push operand.send(op)
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 = stack.pop
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
- stack.push callee.call # Pass arguments
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 = stack.pop
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
- stack.push instance.get(aGetExpr.property)
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
- stack.push(literalExpr.literal)
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 StandardError, "Undefined property '#{aSuperExpr.property}'."
352
+ raise Loxxy::RuntimeError, "Undefined property '#{aSuperExpr.property}'."
325
353
  end
326
354
 
327
- stack.push method.bind(instance)
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
- stack.push(aValue)
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
- stack.push function
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