loxxy 0.3.01 → 0.3.02

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f6a77cf0f7f4cd0c445d597c534ecc1de82a31222d54a3d0da30330ccbcb216e
4
- data.tar.gz: c10cab44247a3f7d75382c7a6bbfbc7938bce368ba14ea48878643ea7778aca5
3
+ metadata.gz: a0e398714d64763c288f06c315611c86bf3dae2e3403055ae3ca240a10bd292f
4
+ data.tar.gz: 8db95e837824181e1794277dc3e67e4005432006877124bbd62ffd07def57591
5
5
  SHA512:
6
- metadata.gz: 5f76a5a66ff5ecdc76207802e195c7becdaf214cb687f9a62be1b58dc56d3ea0a9b3320bb2b8a105a72e3ff79801e3065eb3c10cab6246de2a4a5e74f4ebb722
7
- data.tar.gz: eafb0e81fa6c6b5c8c3c2a017907b14e3a4ebadd04ec51cb9d79030905562e86ecf37d9e68511ff52ab4ae45eb598f8c7de4fa6344f90034f21635b8f2c983ff
6
+ metadata.gz: caf2aab9a8c03997fbde67467b7cb66a786c2fb8ef46fc24869c42ac7da76831f5e771a282c938ec83585666de801602ffc9818fc489454ec93dec9bd76bf5a7
7
+ data.tar.gz: 9d98db7d9bddec915929dc1cbaed057248ccb5e0cc63efc3d0d7e99536ea9b330925d91d6b1ffd6805194dc3805b0025228f66d11f11b5455cb8f52d7753e6eb
data/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
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
+
1
16
  ## [0.3.01] - 2021-05-08
2
17
  - Fix in `Scanner` class, added more tests in test suite.
3
18
 
@@ -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].subnodes << theChildren[3]
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])
@@ -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 < 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)
@@ -285,7 +333,7 @@ module Loxxy
285
333
 
286
334
  # @param literalExpr [Ast::LoxLiteralExpr]
287
335
  def before_literal_expr(literalExpr)
288
- stack.push(literalExpr.literal)
336
+ expr_stack.push(literalExpr.literal)
289
337
  end
290
338
 
291
339
  def after_this_expr(aThisExpr, aVisitor)
@@ -304,18 +352,22 @@ module Loxxy
304
352
  raise Loxxy::RuntimeError, "Undefined property '#{aSuperExpr.property}'."
305
353
  end
306
354
 
307
- stack.push method.bind(instance)
355
+ expr_stack.push method.bind(instance)
308
356
  end
309
357
 
310
358
  # @param aValue [Ast::BuiltinDattype] the built-in datatype value
311
359
  def before_visit_builtin(aValue)
312
- stack.push(aValue)
360
+ expr_stack.push(aValue)
361
+ end
362
+
363
+ def before_fun_stmt(_fun_stmt, _visitor)
364
+ reset_expr_stack
313
365
  end
314
366
 
315
367
  def after_fun_stmt(aFunStmt, _visitor)
316
368
  function = LoxFunction.new(aFunStmt.name, aFunStmt.params, aFunStmt.body, self)
317
369
  if aFunStmt.is_method
318
- stack.push function
370
+ expr_stack.push function
319
371
  else
320
372
  new_var = Variable.new(aFunStmt.name, function)
321
373
  symbol_table.insert(new_var)
@@ -337,21 +389,9 @@ module Loxxy
337
389
  env.defns[aVarNode.name]
338
390
  end
339
391
 
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
392
  def init_unary_operators
393
+ @unary_operators = {}
394
+
355
395
  negate_op = UnaryOperator.new('-', [Datatype::Number])
356
396
  unary_operators[:-@] = negate_op
357
397
 
@@ -361,6 +401,8 @@ module Loxxy
361
401
  end
362
402
 
363
403
  def init_binary_operators
404
+ @binary_operators = {}
405
+
364
406
  plus_op = BinaryOperator.new('+', [[Datatype::Number, :idem],
365
407
  [Datatype::LXString, :idem]])
366
408
  binary_operators[:+] = plus_op
@@ -395,6 +437,24 @@ module Loxxy
395
437
 
396
438
  def init_globals
397
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
398
458
  end
399
459
 
400
460
  def add_native_fun(aName, aProc)
@@ -411,6 +471,38 @@ module Loxxy
411
471
  end
412
472
  end
413
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
+
414
506
  def convert2lox_datatype(item)
415
507
  case item
416
508
  when TrueClass then Datatype::True.instance
@@ -25,11 +25,15 @@ module Loxxy
25
25
  # @return [Hash{String => Variable}] Pairs of the kind
26
26
  attr_reader :defns
27
27
 
28
+ # @return [Array<LoxNode>] stack of values needed in evaluating an expression
29
+ attr_reader :expr_stack
30
+
28
31
  # Construct a environment instance.
29
32
  # @param aParent [Environment, NilClass] Parent environment to this one.
30
33
  def initialize(aParent = nil)
31
34
  @enclosing = aParent
32
35
  @defns = {}
36
+ @expr_stack = []
33
37
  end
34
38
 
35
39
  # Add a new variable to the environment.
@@ -7,17 +7,15 @@ module Loxxy
7
7
  module BackEnd
8
8
  # Runtime representation of a Lox class.
9
9
  class LoxClass
10
- # rubocop: disable Style/AccessorGrouping
11
-
12
10
  # @return [String] The name of the class
13
11
  attr_reader :name
14
12
  attr_reader :superclass
15
13
 
16
14
  # @return [Hash{String => LoxFunction}] the list of methods
17
15
  attr_reader :meths
18
- attr_reader :stack
19
16
 
20
- # rubocop: enable Style/AccessorGrouping
17
+ # @return [Loxxy::BackEnd::Engine]
18
+ attr_reader :engine
21
19
 
22
20
  # Create a class with given name
23
21
  # @param aName [String] The name of the class
@@ -28,11 +26,11 @@ module Loxxy
28
26
  theMethods.each do |func|
29
27
  meths[func.name] = func
30
28
  end
31
- @stack = anEngine.stack
29
+ @engine = anEngine
32
30
  end
33
31
 
34
32
  def accept(_visitor)
35
- stack.push self
33
+ engine.expr_stack.push self
36
34
  end
37
35
 
38
36
  def arity
@@ -46,9 +44,9 @@ module Loxxy
46
44
  if initializer
47
45
  constructor = initializer.bind(instance)
48
46
  constructor.call(engine, visitor)
47
+ else
48
+ engine.expr_stack.push(instance)
49
49
  end
50
-
51
- engine.stack.push(instance)
52
50
  end
53
51
 
54
52
  # @param aName [String] the method name to search for
@@ -13,7 +13,7 @@ module Loxxy
13
13
  # @return [Array<>] the parameters
14
14
  attr_reader :parameters
15
15
  attr_reader :body
16
- attr_reader :stack
16
+ attr_reader :engine
17
17
  attr_reader :closure
18
18
  attr_accessor :is_initializer
19
19
 
@@ -23,7 +23,7 @@ module Loxxy
23
23
  @name = aName.dup
24
24
  @parameters = parameterList
25
25
  @body = aBody.kind_of?(Ast::LoxNoopExpr) ? aBody : aBody.subnodes[0]
26
- @stack = anEngine.stack
26
+ @engine = anEngine
27
27
  @closure = anEngine.symbol_table.current_env
28
28
  @is_initializer = false
29
29
  anEngine.symbol_table.current_env.embedding = true
@@ -34,28 +34,34 @@ module Loxxy
34
34
  end
35
35
 
36
36
  def accept(_visitor)
37
- stack.push self
37
+ engine.expr_stack.push self
38
38
  end
39
39
 
40
- def call(engine, aVisitor)
40
+ def call(_engine, aVisitor)
41
41
  new_env = Environment.new(closure)
42
42
  engine.symbol_table.enter_environment(new_env)
43
43
 
44
44
  parameters&.each do |param_name|
45
- local = Variable.new(param_name, stack.pop)
45
+ local = Variable.new(param_name, engine.stack.pop)
46
46
  engine.symbol_table.insert(local)
47
47
  end
48
48
 
49
49
  catch(:return) do
50
- (body.nil? || body.kind_of?(Ast::LoxNoopExpr)) ? Datatype::Nil.instance : body.accept(aVisitor)
51
- throw(:return)
50
+ body.accept(aVisitor) unless body.nil? || body.kind_of?(Ast::LoxNoopExpr)
51
+ # implicit return at end of function...
52
+ engine.stack.push(Datatype::Nil.instance) unless is_initializer
52
53
  end
54
+ # Compensate for deeply nested return
55
+ engine.symbol_table.leave_environment while engine.current_env != new_env
56
+
53
57
  if is_initializer
54
58
  enclosing_env = engine.symbol_table.current_env.enclosing
55
59
  engine.stack.push(enclosing_env.defns['this'].value)
56
60
  end
57
61
 
58
62
  engine.symbol_table.leave_environment
63
+ # engine.expr_stack.clear
64
+ engine.expr_stack.push(engine.stack.pop) unless engine.stack.empty?
59
65
  end
60
66
 
61
67
  def bind(anInstance)
@@ -23,7 +23,7 @@ module Loxxy
23
23
  end
24
24
 
25
25
  def accept(_visitor)
26
- engine.stack.push self
26
+ engine.expr_stack.push self
27
27
  end
28
28
 
29
29
  # Text representation of a Lox instance
@@ -133,6 +133,7 @@ module Loxxy
133
133
  end
134
134
 
135
135
  def after_set_expr(aSetExpr, aVisitor)
136
+ aSetExpr.value.accept(aVisitor)
136
137
  # Evaluate object part
137
138
  aSetExpr.object.accept(aVisitor)
138
139
  end
@@ -91,7 +91,6 @@ module Loxxy
91
91
 
92
92
  private
93
93
 
94
- # rubocop: disable Lint/DuplicateBranch
95
94
  def _next_token
96
95
  skip_intertoken_spaces
97
96
  curr_ch = scanner.peek(1)
@@ -125,7 +124,6 @@ module Loxxy
125
124
 
126
125
  return token
127
126
  end
128
- # rubocop: enable Lint/DuplicateBranch
129
127
 
130
128
  def build_token(aSymbolName, aLexeme)
131
129
  begin
data/lib/loxxy/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Loxxy
4
- VERSION = '0.3.01'
4
+ VERSION = '0.3.02'
5
5
  end
@@ -35,8 +35,8 @@ module Loxxy
35
35
  let(:lit_expr) { Ast::LoxLiteralExpr.new(sample_pos, greeting) }
36
36
 
37
37
  it "should react to 'after_var_stmt' event" do
38
- # Precondition: value to assign is on top of stack
39
- subject.stack.push(greeting)
38
+ # Precondition: value to assign is on top of expr stack
39
+ subject.expr_stack.push(greeting)
40
40
 
41
41
  expect { subject.after_var_stmt(var_decl) }.not_to raise_error
42
42
  current_env = subject.symbol_table.current_env
@@ -46,7 +46,17 @@ module Loxxy
46
46
 
47
47
  it "should react to 'before_literal_expr' event" do
48
48
  expect { subject.before_literal_expr(lit_expr) }.not_to raise_error
49
- expect(subject.stack.pop).to eq(greeting)
49
+ expect(subject.expr_stack.pop).to eq(greeting)
50
+ end
51
+ end
52
+
53
+ context 'Built-in functions:' do
54
+ it 'should provide built-in functions' do
55
+ symb_table = subject.symbol_table
56
+ %w[clock getc chr exit print_error].each do |name|
57
+ fun_var = symb_table.current_env.defns[name]
58
+ expect(fun_var.value).to be_kind_of(BackEnd::Engine::NativeFunction)
59
+ end
50
60
  end
51
61
  end
52
62
  end # describe
@@ -353,8 +353,8 @@ LOX_END
353
353
  expect(expr).to be_kind_of(Ast::LoxSetExpr)
354
354
  expect(expr.object.name).to eq('someObject')
355
355
  expect(expr.property).to eq('someProperty')
356
- expect(expr.subnodes[0]).to be_kind_of(Ast::LoxVariableExpr)
357
- expect(expr.subnodes[0].name).to eq('value')
356
+ expect(expr.value).to be_kind_of(Ast::LoxVariableExpr)
357
+ expect(expr.value.name).to eq('value')
358
358
  end
359
359
 
360
360
  it 'should parse complex set access' do
@@ -372,8 +372,8 @@ LOX_END
372
372
  expr = ptree.root.subnodes[0]
373
373
  expect(expr).to be_kind_of(Ast::LoxSetExpr)
374
374
  expect(expr.property).to eq('meat')
375
- expect(expr.subnodes[0]).to be_kind_of(Ast::LoxVariableExpr)
376
- expect(expr.subnodes[0].name).to eq('ham')
375
+ expect(expr.value).to be_kind_of(Ast::LoxVariableExpr)
376
+ expect(expr.value.name).to eq('ham')
377
377
  expect(expr.object).to be_kind_of(Ast::LoxGetExpr)
378
378
  expect(expr.object.property).to eq('filling')
379
379
  expect(expr.object.object).to be_kind_of(Ast::LoxGetExpr)
@@ -430,6 +430,50 @@ LOX_END
430
430
  expect(sample_cfg[:ostream].string).to eq('<fn foo><native fn>')
431
431
  end
432
432
 
433
+ it "should implement 'getc' function" do
434
+ input_str = 'Abc'
435
+ cfg = { istream: StringIO.new(input_str) }
436
+ interpreter = Loxxy::Interpreter.new(cfg)
437
+ source = 'getc();'
438
+ result = interpreter.evaluate(source)
439
+ expect(result.value).to eq(65) # codepoint for letter 'A'
440
+ end
441
+
442
+ it "should implement 'chr' function" do
443
+ source = 'chr(65); // => "A"'
444
+ result = subject.evaluate(source)
445
+ expect(result.value).to eq('A')
446
+ end
447
+
448
+ # This test is disabled since it causes RSpec to stop immediately
449
+ # it "should implement 'exit' function" do
450
+ # source = 'exit(100); // Process halts with exit code 100'
451
+ # expect { subject.evaluate(source) }.to raise(SystemExit)
452
+ # end
453
+
454
+ it "should implement 'print_error' function" do
455
+ source = 'print_error("Some error"); // => Some error on stderr'
456
+ stderr_backup = $stderr
457
+ $stderr = StringIO.new
458
+ expect { subject.evaluate(source) }.not_to raise_error
459
+ expect($stderr.string).to eq('Some error')
460
+ $stderr = stderr_backup
461
+ end
462
+
463
+ # rubocop: disable Style/StringConcatenation
464
+ it 'should return in absence of explicit return statement' do
465
+ program = <<-LOX_END
466
+ fun foo() {
467
+ print "foo";
468
+ }
469
+
470
+ print foo();
471
+ LOX_END
472
+ expect { subject.evaluate(program) }.not_to raise_error
473
+ expect(sample_cfg[:ostream].string).to eq('foo' + 'nil')
474
+ end
475
+ # rubocop: enable Style/StringConcatenation
476
+
433
477
  it 'should support return statements' do
434
478
  program = <<-LOX_END
435
479
  fun max(a, b) {
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: loxxy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.01
4
+ version: 0.3.02
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dimitri Geshef
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-08 00:00:00.000000000 Z
11
+ date: 2021-05-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rley