loxxy 0.3.01 → 0.4.01

Sign up to get free protection for your applications and to get access to all the features.
@@ -71,7 +71,6 @@ module Loxxy
71
71
  # @param aClassStmt [AST::LOXClassStmt] the for statement node to visit
72
72
  def visit_class_stmt(aClassStmt)
73
73
  broadcast(:before_class_stmt, aClassStmt)
74
- traverse_subnodes(aClassStmt) # The methods are visited here...
75
74
  broadcast(:after_class_stmt, aClassStmt, self)
76
75
  end
77
76
 
@@ -126,7 +125,6 @@ module Loxxy
126
125
  # @param aSetExpr [AST::LOXGetExpr] the get expression node to visit
127
126
  def visit_set_expr(aSetExpr)
128
127
  broadcast(:before_set_expr, aSetExpr, self)
129
- traverse_subnodes(aSetExpr)
130
128
  broadcast(:after_set_expr, aSetExpr, self)
131
129
  end
132
130
 
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'lox_compound_expr'
3
+ require_relative 'lox_node'
4
4
 
5
5
  module Loxxy
6
6
  module Ast
7
- class LoxClassStmt < LoxCompoundExpr
7
+ # A parse tree node that represents a Lox class declaration.
8
+ class LoxClassStmt < LoxNode
8
9
  # @return [String] the class name
9
10
  attr_reader :name
10
11
 
@@ -14,11 +15,13 @@ module Loxxy
14
15
  # @return [Array<Ast::LoxFunStmt>] the methods
15
16
  attr_reader :body
16
17
 
18
+ # Constructor for a parse node that represents a Lox function declaration
17
19
  # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
18
- # @param condExpr [Loxxy::Ast::LoxNode] iteration condition
19
- # @param theBody [Array<Loxxy::Ast::LoxNode>]
20
+ # @param aName [String] the class name
21
+ # @param aSuperclassName [String] the super class name
22
+ # @param theMethods [Array<Loxxy::Ast::LoxFunStmt>] the methods
20
23
  def initialize(aPosition, aName, aSuperclassName, theMethods)
21
- super(aPosition, [])
24
+ super(aPosition)
22
25
  @name = aName.dup
23
26
  @superclass = aSuperclassName
24
27
  @body = theMethods
@@ -4,17 +4,25 @@ require_relative 'lox_node'
4
4
 
5
5
  module Loxxy
6
6
  module Ast
7
- # rubocop: disable Style/AccessorGrouping
7
+ # A parse tree node that represents a Lox function declaration.
8
8
  class LoxFunStmt < LoxNode
9
+ # @return [String] the function name
9
10
  attr_reader :name
11
+
12
+ # @return [Array<String>] the parameter names
10
13
  attr_reader :params
14
+
15
+ # @return [Ast::LoxBlockStmt] the parse tree representing the function's body
11
16
  attr_reader :body
17
+
18
+ # @return [Boolean] true if the function is a method
12
19
  attr_accessor :is_method
13
20
 
21
+ # Constructor for a parse node that represents a Lox function declaration
14
22
  # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
15
- # @param aName [String]
16
- # @param arguments [Array<String>]
17
- # @param body [Ast::LoxBlockStmt]
23
+ # @param aName [String] the function name
24
+ # @param paramList [Array<String>] the parameter names
25
+ # @param aBody [Ast::LoxBlockStmt] the parse tree representing the function's body
18
26
  def initialize(aPosition, aName, paramList, aBody)
19
27
  super(aPosition)
20
28
  @name = aName.dup
@@ -25,6 +33,5 @@ module Loxxy
25
33
 
26
34
  define_accept # Add `accept` method as found in Visitor design pattern
27
35
  end # class
28
- # rubocop: enable Style/AccessorGrouping
29
36
  end # module
30
37
  end # module
@@ -11,7 +11,7 @@ module Loxxy
11
11
  # Let nodes take `visitee` role as defined in the Visitor design pattern
12
12
  extend ASTVisitee
13
13
 
14
- # return [Rley::Lexical::Position] Position of the entry in the input stream.
14
+ # @return [Rley::Lexical::Position] Position of the entry in the input stream.
15
15
  attr_reader :position
16
16
 
17
17
  # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
@@ -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,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 = stack.pop
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 = 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,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 = stack.pop
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 = stack.pop
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 = stack.last # ToS remains since an assignment produces a 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(aSetExpr, aVisitor)
167
- # Evaluate receiver object part
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, _visitor)
172
- value = stack.pop
173
- 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
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
- stack.push value
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 = stack.pop # only first operand was evaluated
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 = stack.pop
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,14 +255,14 @@ module Loxxy
212
255
  end
213
256
 
214
257
  def after_binary_expr(aBinaryExpr)
215
- operand2 = stack.pop
216
- operand1 = stack.pop
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)
222
- stack.push convert2lox_datatype(result)
265
+ expr_stack.push convert2lox_datatype(result)
223
266
  else
224
267
  msg1 = "`#{op}': Unimplemented operator for a #{operand1.class}."
225
268
  raise StandardError, msg1
@@ -227,13 +270,13 @@ module Loxxy
227
270
  end
228
271
 
229
272
  def after_unary_expr(anUnaryExpr)
230
- operand = stack.pop
273
+ operand = expr_stack.pop
231
274
  op = anUnaryExpr.operator
232
275
  operator = unary_operators[op]
233
276
  operator.validate_operand(operand)
234
277
  if operand.respond_to?(op)
235
278
  result = operand.send(op)
236
- stack.push convert2lox_datatype(result)
279
+ expr_stack.push convert2lox_datatype(result)
237
280
  else
238
281
  msg1 = "`#{op}': Unimplemented operator for a #{operand.class}."
239
282
  raise StandardError, msg1
@@ -243,12 +286,17 @@ module Loxxy
243
286
  def after_call_expr(aCallExpr, aVisitor)
244
287
  # Evaluate callee part
245
288
  aCallExpr.callee.accept(aVisitor)
246
- callee = stack.pop
289
+ callee = expr_stack.pop
290
+ before_size = expr_stack.size
247
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
248
296
 
249
297
  case callee
250
298
  when NativeFunction
251
- stack.push callee.call # Pass arguments
299
+ expr_stack.push callee.call # Pass arguments
252
300
  when LoxFunction, LoxClass
253
301
  arg_count = aCallExpr.arguments.size
254
302
  if arg_count != callee.arity
@@ -263,12 +311,12 @@ module Loxxy
263
311
 
264
312
  def after_get_expr(aGetExpr, aVisitor)
265
313
  aGetExpr.object.accept(aVisitor)
266
- instance = stack.pop
314
+ instance = expr_stack.pop
267
315
  unless instance.kind_of?(LoxInstance)
268
316
  raise Loxxy::RuntimeError, 'Only instances have properties.'
269
317
  end
270
318
 
271
- stack.push instance.get(aGetExpr.property)
319
+ expr_stack.push instance.get(aGetExpr.property)
272
320
  end
273
321
 
274
322
  def after_grouping_expr(_groupingExpr)
@@ -278,14 +326,17 @@ module Loxxy
278
326
  def after_variable_expr(aVarExpr, aVisitor)
279
327
  var_name = aVarExpr.name
280
328
  var = variable_lookup(aVarExpr)
281
- raise Loxxy::RuntimeError, "Undefined variable '#{var_name}'." unless var
329
+ unless var
330
+ pos = "line #{aVarExpr.position.line}:#{aVarExpr.position.column}"
331
+ raise Loxxy::RuntimeError, "[#{pos}] Undefined variable '#{var_name}'."
332
+ end
282
333
 
283
334
  var.value.accept(aVisitor) # Evaluate variable value then push on stack
284
335
  end
285
336
 
286
337
  # @param literalExpr [Ast::LoxLiteralExpr]
287
338
  def before_literal_expr(literalExpr)
288
- stack.push(literalExpr.literal)
339
+ expr_stack.push(literalExpr.literal)
289
340
  end
290
341
 
291
342
  def after_this_expr(aThisExpr, aVisitor)
@@ -304,18 +355,22 @@ module Loxxy
304
355
  raise Loxxy::RuntimeError, "Undefined property '#{aSuperExpr.property}'."
305
356
  end
306
357
 
307
- stack.push method.bind(instance)
358
+ expr_stack.push method.bind(instance)
308
359
  end
309
360
 
310
361
  # @param aValue [Ast::BuiltinDattype] the built-in datatype value
311
362
  def before_visit_builtin(aValue)
312
- stack.push(aValue)
363
+ expr_stack.push(aValue)
364
+ end
365
+
366
+ def before_fun_stmt(_fun_stmt, _visitor)
367
+ reset_expr_stack
313
368
  end
314
369
 
315
370
  def after_fun_stmt(aFunStmt, _visitor)
316
371
  function = LoxFunction.new(aFunStmt.name, aFunStmt.params, aFunStmt.body, self)
317
372
  if aFunStmt.is_method
318
- stack.push function
373
+ expr_stack.push function
319
374
  else
320
375
  new_var = Variable.new(aFunStmt.name, function)
321
376
  symbol_table.insert(new_var)
@@ -337,21 +392,9 @@ module Loxxy
337
392
  env.defns[aVarNode.name]
338
393
  end
339
394
 
340
- NativeFunction = Struct.new(:callable, :interp) do
341
- def accept(_visitor)
342
- interp.stack.push self
343
- end
344
-
345
- def call
346
- callable.call
347
- end
348
-
349
- def to_str
350
- '<native fn>'
351
- end
352
- end
353
-
354
395
  def init_unary_operators
396
+ @unary_operators = {}
397
+
355
398
  negate_op = UnaryOperator.new('-', [Datatype::Number])
356
399
  unary_operators[:-@] = negate_op
357
400
 
@@ -361,6 +404,8 @@ module Loxxy
361
404
  end
362
405
 
363
406
  def init_binary_operators
407
+ @binary_operators = {}
408
+
364
409
  plus_op = BinaryOperator.new('+', [[Datatype::Number, :idem],
365
410
  [Datatype::LXString, :idem]])
366
411
  binary_operators[:+] = plus_op
@@ -395,6 +440,24 @@ module Loxxy
395
440
 
396
441
  def init_globals
397
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
398
461
  end
399
462
 
400
463
  def add_native_fun(aName, aProc)
@@ -411,6 +474,40 @@ module Loxxy
411
474
  end
412
475
  end
413
476
 
477
+ # Read a single character and return the character code as an integer.
478
+ # LoxLox requires the end of input to be a negative number
479
+ def native_getc
480
+ proc do
481
+ ch = @istream.getc
482
+ val = ch ? ch.codepoints[0] : -1
483
+ Datatype::Number.new(val)
484
+ end
485
+ end
486
+
487
+ # chr(ch): Convert given character code number to a single-character string.
488
+ def native_chr
489
+ proc do
490
+ codepoint = stack.pop
491
+ Datatype::LXString.new(codepoint.value.chr)
492
+ end
493
+ end
494
+
495
+ # exit(status): Exit with given status code.
496
+ def native_exit
497
+ proc do
498
+ status = stack.pop
499
+ exit(status.value)
500
+ end
501
+ end
502
+
503
+ # print_error(message): Print message string on stderr.
504
+ def native_print_error
505
+ proc do
506
+ message = stack.pop
507
+ $stderr.print message.value
508
+ end
509
+ end
510
+
414
511
  def convert2lox_datatype(item)
415
512
  case item
416
513
  when TrueClass then Datatype::True.instance