loxxy 0.3.01 → 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: 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