loxxy 0.2.03 → 0.3.01

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: eb84858746525e4b5f6821e8168066f4af75b639e8320710cd36213ac72a65ed
4
- data.tar.gz: 6ef7b9c8edcfdd1cda3aadfedeff6fef99c1194e92782c3dfe1b1abcc8052e96
3
+ metadata.gz: f6a77cf0f7f4cd0c445d597c534ecc1de82a31222d54a3d0da30330ccbcb216e
4
+ data.tar.gz: c10cab44247a3f7d75382c7a6bbfbc7938bce368ba14ea48878643ea7778aca5
5
5
  SHA512:
6
- metadata.gz: 901731bc9670cab315c53e864430636c14c70b9c15d2218d81ae5657b40e5261b79d7ae7ff7937a638931a0c89af8d75f99a65973dd4cbd9a3418588f6388e87
7
- data.tar.gz: fdedef4073b56af0e5bd2f348b15149dd80ab96d8814b5f0f114529372594ae661203509306603b6be8773976f577aaf73466d6a778a4596f11fd0d383c88dd6
6
+ metadata.gz: 5f76a5a66ff5ecdc76207802e195c7becdaf214cb687f9a62be1b58dc56d3ea0a9b3320bb2b8a105a72e3ff79801e3065eb3c10cab6246de2a4a5e74f4ebb722
7
+ data.tar.gz: eafb0e81fa6c6b5c8c3c2a017907b14e3a4ebadd04ec51cb9d79030905562e86ecf37d9e68511ff52ab4ae45eb598f8c7de4fa6344f90034f21635b8f2c983ff
data/CHANGELOG.md CHANGED
@@ -1,3 +1,71 @@
1
+ ## [0.3.01] - 2021-05-08
2
+ - Fix in `Scanner` class, added more tests in test suite.
3
+
4
+ ### New
5
+ - Added the new subfolder `extra` under `test_suite`. It will contain tests for non-standard features or tests not covered in standard test suite.
6
+
7
+ ### Fixed
8
+ - Class `FrontEnd::Scanner`: Couldn't correctly recognize a plus preceding directly a number literal
9
+
10
+ ## [0.3.00] - 2021-05-07
11
+ - Milestone: `Loxxy` passes all reference test suite.
12
+
13
+ ### Fixed
14
+ - Method `BackEnd::Resolver#before_variable_expr`: Standard `Lox` allows re-declaration of a variable at top-level scope
15
+
16
+
17
+ ## [0.2.06] - 2021-05-04
18
+ - Nearly passing the 'official' test suite, fixing non-compliant behavior, specialized exceptions for errors
19
+
20
+ ### New
21
+ - Module `LoxFileTester` module that hosts methods that simplify the tests of `Lox` source file.
22
+
23
+ ### Changed
24
+ - Folder `test_suite` vastly reorganized. Sub-folder `baseline` contains spec files testing the `Lox` files from official implementation
25
+ - Class `BackEnd::Engine` replaced most `StandardError` by `Loxxy::RuntimeError` exception.
26
+ - Class `BackEnd::Resolver` replaced most `StandardError` by `Loxxy::RuntimeError` exception.
27
+ - Method `Datatype::Number#/` now handles correctly expression like `0/0` (integer divide)
28
+
29
+ ### Fixed
30
+ - `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`
31
+ - `FrontEnd::Scanner` now always treats expression like `-123` as the unary or binary minus operator applied to a positive number.
32
+
33
+ ## [0.2.05] - 2021-04-26
34
+ - `Loxxy` now transforms for loops into while loops (desugaring), fix in Scanner class
35
+
36
+ ### Changed
37
+ - Method `Ast::ASTBuilder#reduce_for_stmt` converts 'for' loops into 'while' loops
38
+ - Method `Ast::ASTBuilder#reduce_for_control takes care of case for(expr1;;expr2) now test expression is set to true
39
+
40
+ ### Fixed
41
+ - Method `FrontEnd::Scanner#next_token` keyword recognition was case insensitive
42
+
43
+ ### Removed
44
+ - Method `Ast::Visitor#visitor_for_stmt`
45
+ - Method `BackEnd::Engine#after_for_stmt`
46
+ - Method `BackEnd::Resolver#before_for_stmt`
47
+ - Method `BackEnd::Resolver#after_for_stmt`
48
+
49
+
50
+ ## [0.2.04] - 2021-04-25
51
+ - `Loxxy` passes the test suite for `for` statements
52
+
53
+ ### Fixed
54
+ - Method `BackEnd::Engine#after_for_stmt` now a for(;;) executes the body once; a for without test will execute forever
55
+ - Method `BackEnd::Resolver#after_for_stmt now accepts nil test expression
56
+
57
+ ## [0.2.03] - 2021-04-24
58
+ - Fixes for the set (field) expressions, `accept` methods for AST nodes are meta-programmed
59
+
60
+ ### New
61
+ - Module `Ast::Visitee` provides the `define_accept` method that generate `accept` method with meta-programming
62
+
63
+ ### Fixed
64
+ - Method `BackEnd::Engine#before_set_expr` methos method that ensure that the receiver is evaluated first, then the assigned value
65
+ - Method `BackEnd::Engine#after_set_expr` now pushes the value assigned to the field also onto the stack
66
+ - Class `BackEnd::Engine` a number of StnadardError exceptions are replaced by Loxxy::RuntimeError
67
+
68
+
1
69
  ## [0.2.02] - 2021-04-21
2
70
  - Improvements in the scanner class (escape sequence for quotes and newlines), error messages closer to jlox.
3
71
 
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')
@@ -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
 
@@ -74,7 +74,7 @@ module Loxxy
74
74
  aClassStmt.superclass.accept(aVisitor)
75
75
  parent = stack.pop
76
76
  unless parent.kind_of?(LoxClass)
77
- raise StandardError, 'Superclass must be a class.'
77
+ raise Loxxy::RuntimeError, 'Superclass must be a class.'
78
78
  end
79
79
  else
80
80
  parent = nil
@@ -116,19 +116,6 @@ module Loxxy
116
116
  before_block_stmt(aForStmt)
117
117
  end
118
118
 
119
- def after_for_stmt(aForStmt, aVisitor)
120
- loop do
121
- aForStmt.test_expr.accept(aVisitor)
122
- condition = stack.pop
123
- break unless condition.truthy?
124
-
125
- aForStmt.body_stmt.accept(aVisitor)
126
- aForStmt.update_expr&.accept(aVisitor)
127
- stack.pop
128
- end
129
- after_block_stmt(aForStmt)
130
- end
131
-
132
119
  def after_if_stmt(anIfStmt, aVisitor)
133
120
  # Retrieve the result of the condition evaluation
134
121
  condition = stack.pop
@@ -154,7 +141,7 @@ module Loxxy
154
141
  break unless condition.truthy?
155
142
 
156
143
  aWhileStmt.body.accept(aVisitor)
157
- aWhileStmt.condition.accept(aVisitor)
144
+ aWhileStmt.condition&.accept(aVisitor)
158
145
  end
159
146
  end
160
147
 
@@ -170,7 +157,7 @@ module Loxxy
170
157
  def after_assign_expr(anAssignExpr, _visitor)
171
158
  var_name = anAssignExpr.name
172
159
  variable = variable_lookup(anAssignExpr)
173
- raise StandardError, "Unknown variable #{var_name}" unless variable
160
+ raise Loxxy::RuntimeError, "Undefined variable '#{var_name}'." unless variable
174
161
 
175
162
  value = stack.last # ToS remains since an assignment produces a value
176
163
  variable.assign(value)
@@ -231,7 +218,8 @@ module Loxxy
231
218
  operator = binary_operators[op]
232
219
  operator.validate_operands(operand1, operand2)
233
220
  if operand1.respond_to?(op)
234
- stack.push operand1.send(op, operand2)
221
+ result = operand1.send(op, operand2)
222
+ stack.push convert2lox_datatype(result)
235
223
  else
236
224
  msg1 = "`#{op}': Unimplemented operator for a #{operand1.class}."
237
225
  raise StandardError, msg1
@@ -244,7 +232,8 @@ module Loxxy
244
232
  operator = unary_operators[op]
245
233
  operator.validate_operand(operand)
246
234
  if operand.respond_to?(op)
247
- stack.push operand.send(op)
235
+ result = operand.send(op)
236
+ stack.push convert2lox_datatype(result)
248
237
  else
249
238
  msg1 = "`#{op}': Unimplemented operator for a #{operand.class}."
250
239
  raise StandardError, msg1
@@ -312,7 +301,7 @@ module Loxxy
312
301
  superklass = variable_lookup(aSuperExpr).value.superclass
313
302
  method = superklass.find_method(aSuperExpr.property)
314
303
  unless method
315
- raise StandardError, "Undefined property '#{aSuperExpr.property}'."
304
+ raise Loxxy::RuntimeError, "Undefined property '#{aSuperExpr.property}'."
316
305
  end
317
306
 
318
307
  stack.push method.bind(instance)
@@ -367,7 +356,7 @@ module Loxxy
367
356
  unary_operators[:-@] = negate_op
368
357
 
369
358
  negation_op = UnaryOperator.new('!', [Datatype::BuiltinDatatype,
370
- BackEnd::LoxFunction])
359
+ BackEnd::LoxInstance, BackEnd::LoxFunction, BackEnd::LoxClass])
371
360
  unary_operators[:!] = negation_op
372
361
  end
373
362
 
@@ -421,6 +410,15 @@ module Loxxy
421
410
  Datatype::Number.new(now)
422
411
  end
423
412
  end
413
+
414
+ def convert2lox_datatype(item)
415
+ case item
416
+ when TrueClass then Datatype::True.instance
417
+ when FalseClass then Datatype::False.instance
418
+ else
419
+ item
420
+ end
421
+ end
424
422
  end # class
425
423
  end # module
426
424
  end # module
@@ -69,7 +69,7 @@ module Loxxy
69
69
  define(aClassStmt.name)
70
70
  if aClassStmt.superclass
71
71
  if aClassStmt.name == aClassStmt.superclass.name
72
- raise StandardError, "'A class can't inherit from itself."
72
+ raise Loxxy::RuntimeError, "'A class can't inherit from itself."
73
73
  end
74
74
 
75
75
  @current_class = :subclass
@@ -88,17 +88,6 @@ module Loxxy
88
88
  @current_class = previous_class
89
89
  end
90
90
 
91
- def before_for_stmt(aForStmt)
92
- before_block_stmt(aForStmt)
93
- end
94
-
95
- def after_for_stmt(aForStmt, aVisitor)
96
- aForStmt.test_expr.accept(aVisitor)
97
- aForStmt.body_stmt.accept(aVisitor)
98
- aForStmt.update_expr&.accept(aVisitor)
99
- after_block_stmt(aForStmt)
100
- end
101
-
102
91
  def after_if_stmt(anIfStmt, aVisitor)
103
92
  anIfStmt.then_stmt.accept(aVisitor)
104
93
  anIfStmt.else_stmt&.accept(aVisitor)
@@ -107,27 +96,30 @@ module Loxxy
107
96
  def before_return_stmt(returnStmt)
108
97
  if scopes.size < 2
109
98
  msg = "Error at 'return': Can't return from top-level code."
110
- raise StandardError, msg
99
+ raise Loxxy::RuntimeError, msg
111
100
  end
112
101
 
113
102
  if current_function == :none
114
103
  msg = "Error at 'return': Can't return from outside a function."
115
- raise StandardError, msg
104
+ raise Loxxy::RuntimeError, msg
116
105
  end
117
106
 
118
107
  if current_function == :initializer
119
108
  msg = "Error at 'return': Can't return a value from an initializer."
120
- raise StandardError, msg unless returnStmt.subnodes[0].kind_of?(Datatype::Nil)
109
+ raise Loxxy::RuntimeError, msg unless returnStmt.subnodes[0].kind_of?(Datatype::Nil)
121
110
  end
122
111
  end
123
112
 
124
113
  def after_while_stmt(aWhileStmt, aVisitor)
125
114
  aWhileStmt.body.accept(aVisitor)
126
- aWhileStmt.condition.accept(aVisitor)
115
+ aWhileStmt.condition&.accept(aVisitor)
127
116
  end
128
117
 
129
118
  # A variable declaration adds a new variable to current scope
130
119
  def before_var_stmt(aVarStmt)
120
+ # Oddly enough, Lox allows the re-definition of a variable at top-level scope
121
+ return if scopes.size == 1 && scopes.last[aVarStmt.name]
122
+
131
123
  declare(aVarStmt.name)
132
124
  end
133
125
 
@@ -149,7 +141,7 @@ module Loxxy
149
141
  def before_variable_expr(aVarExpr)
150
142
  var_name = aVarExpr.name
151
143
  if !scopes.empty? && (scopes.last[var_name] == false)
152
- raise StandardError, "Can't read variable #{var_name} in its own initializer"
144
+ raise Loxxy::RuntimeError, "Can't read variable #{var_name} in its own initializer"
153
145
  end
154
146
  end
155
147
 
@@ -171,7 +163,7 @@ module Loxxy
171
163
  def before_this_expr(_thisExpr)
172
164
  if current_class == :none
173
165
  msg = "Error at 'this': Can't use 'this' outside of a class."
174
- raise StandardError, msg
166
+ raise Loxxy::RuntimeError, msg
175
167
  end
176
168
  end
177
169
 
@@ -186,11 +178,11 @@ module Loxxy
186
178
  msg_prefix = "Error at 'super': Can't use 'super' "
187
179
  if current_class == :none
188
180
  err_msg = msg_prefix + 'outside of a class.'
189
- raise StandardError, err_msg
181
+ raise Loxxy::RuntimeError, err_msg
190
182
 
191
183
  elsif current_class == :class
192
184
  err_msg = msg_prefix + 'in a class without superclass.'
193
- raise StandardError, err_msg
185
+ raise Loxxy::RuntimeError, err_msg
194
186
 
195
187
  end
196
188
  # 'super' behaves closely to a local variable
@@ -220,9 +212,13 @@ module Loxxy
220
212
  return if scopes.empty?
221
213
 
222
214
  curr_scope = scopes.last
215
+ # Oddly enough, Lox allows variable re-declaration at top-level
223
216
  if curr_scope.include?(aVarName)
224
217
  msg = "Error at '#{aVarName}': Already variable with this name in this scope."
225
- raise StandardError, msg
218
+ raise Loxxy::RuntimeError, msg
219
+ elsif curr_scope.size == 255 && current_function != :none
220
+ msg = "Error at '#{aVarName}': Too many local variables in function."
221
+ raise Loxxy::RuntimeError, msg
226
222
  end
227
223
 
228
224
  # The initializer is not yet processed.
@@ -7,6 +7,10 @@ module Loxxy
7
7
  module Datatype
8
8
  # Class for representing a Lox numeric value.
9
9
  class Number < BuiltinDatatype
10
+ def zero?
11
+ value.zero?
12
+ end
13
+
10
14
  # Perform the addition of two Lox numbers or
11
15
  # one Lox number and a Ruby Numeric
12
16
  # @param other [Loxxy::Datatype::Number, Numeric]
@@ -59,17 +63,28 @@ module Loxxy
59
63
  # one Lox number and a Ruby Numeric
60
64
  # @param other [Loxxy::Datatype::Number, Numeric]
61
65
  # @return [Loxxy::Datatype::Number]
66
+ # rubocop: disable Lint/BinaryOperatorWithIdenticalOperands
62
67
  def /(other)
63
68
  case other
64
- when Number
65
- self.class.new(value / other.value)
66
- when Numeric
67
- self.class.new(value / other)
69
+ when Number, Numeric
70
+ if other.zero?
71
+ if zero?
72
+ # NaN case detected
73
+ self.class.new(0.0 / 0.0)
74
+ else
75
+ raise ZeroDivisionError
76
+ end
77
+ elsif other.kind_of?(Number)
78
+ self.class.new(value / other.value)
79
+ else
80
+ self.class.new(value / other)
81
+ end
68
82
  else
69
83
  err_msg = "'/': Operands must be numbers."
70
84
  raise TypeError, err_msg
71
85
  end
72
86
  end
87
+ # rubocop: enable Lint/BinaryOperatorWithIdenticalOperands
73
88
 
74
89
  # Unary minus (return value with changed sign)
75
90
  # @return [Loxxy::Datatype::Number]
@@ -46,7 +46,7 @@ module Loxxy
46
46
  # Stop if the parse failed...
47
47
  line1 = "Parsing failed\n"
48
48
  line2 = "Reason: #{result.failure_reason.message}"
49
- raise StandardError, line1 + line2
49
+ raise SyntaxError, line1 + line2
50
50
  end
51
51
 
52
52
  return engine.convert(result) # engine.to_ptree(result)
@@ -54,11 +54,11 @@ module Loxxy
54
54
  '<=' => 'LESS_EQUAL'
55
55
  }.freeze
56
56
 
57
- # Here are all the implemented Lox keywords (in uppercase)
57
+ # Here are all the implemented Lox keywords
58
58
  # These are enumerated in section 4.2.1 Token type
59
59
  @@keywords = %w[
60
- AND CLASS ELSE FALSE FUN FOR IF NIL OR
61
- PRINT RETURN SUPER THIS TRUE VAR WHILE
60
+ and class else false fun for if nil or
61
+ print return super this true var while
62
62
  ].map { |x| [x, x] }.to_h
63
63
 
64
64
  # Constructor. Initialize a tokenizer for Lox input.
@@ -84,7 +84,7 @@ module Loxxy
84
84
  token = _next_token
85
85
  tok_sequence << token unless token.nil?
86
86
  end
87
- tok_sequence << build_token('EOF', '')
87
+ tok_sequence << build_token('EOF', nil)
88
88
 
89
89
  return tok_sequence
90
90
  end
@@ -99,22 +99,19 @@ module Loxxy
99
99
 
100
100
  token = nil
101
101
 
102
- if '(){},.;/*'.include? curr_ch
102
+ if '(){},.;+-/*'.include? curr_ch
103
103
  # Single delimiter or separator character
104
104
  token = build_token(@@lexeme2name[curr_ch], scanner.getch)
105
- elsif (lexeme = scanner.scan(/[+\-](?!\d)/))
106
- # Minus or plus character not preceding a digit
107
- token = build_token(@@lexeme2name[lexeme], lexeme)
108
105
  elsif (lexeme = scanner.scan(/[!=><]=?/))
109
106
  # One or two special character tokens
110
107
  token = build_token(@@lexeme2name[lexeme], lexeme)
111
- elsif (lexeme = scanner.scan(/-?\d+(?:\.\d+)?/))
108
+ elsif (lexeme = scanner.scan(/\d+(?:\.\d+)?/))
112
109
  token = build_token('NUMBER', lexeme)
113
110
  elsif (lexeme = scanner.scan(/"(?:\\"|[^"])*"/))
114
111
  token = build_token('STRING', lexeme)
115
112
  elsif (lexeme = scanner.scan(/[a-zA-Z_][a-zA-Z_0-9]*/))
116
- keyw = @@keywords[lexeme.upcase]
117
- tok_type = keyw || 'IDENTIFIER'
113
+ keyw = @@keywords[lexeme]
114
+ tok_type = keyw ? keyw.upcase : 'IDENTIFIER'
118
115
  token = build_token(tok_type, lexeme)
119
116
  elsif scanner.scan(/"(?:\\"|[^"])*\z/)
120
117
  # Error: unterminated string...
@@ -133,7 +130,8 @@ module Loxxy
133
130
  def build_token(aSymbolName, aLexeme)
134
131
  begin
135
132
  (value, symb) = convert_to(aLexeme, aSymbolName)
136
- col = scanner.pos - aLexeme.size - @line_start + 1
133
+ lex_length = aLexeme ? aLexeme.size : 0
134
+ col = scanner.pos - lex_length - @line_start + 1
137
135
  pos = Rley::Lexical::Position.new(@lineno, col)
138
136
  if value
139
137
  token = Literal.new(value, aLexeme.dup, symb, pos)
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.2.03'
4
+ VERSION = '0.3.01'
5
5
  end
@@ -54,20 +54,6 @@ module Loxxy
54
54
  expect(var_b).to be_kind_of(Variable)
55
55
  expect(var_b.name).to eq('b')
56
56
  end
57
-
58
- # it 'should set the suffix of just created variable' do
59
- # subject.insert(var('a'))
60
- # var_a = subject.defns['a']
61
- # expect(var_a.suffix).to eq("_#{subject.object_id.to_s(16)}")
62
- # end
63
-
64
- # it 'should complain when variable names collide' do
65
- # subject.insert(var('c'))
66
- # expect(subject.defns['c']).to be_kind_of(Datatype::Variable)
67
- # err = StandardError
68
- # err_msg = "Variable with name 'c' already exists."
69
- # expect { subject.insert(var('c')) }.to raise_error(err, err_msg)
70
- # end
71
57
  end # context
72
58
  end # describe
73
59
  end # module
@@ -106,25 +106,6 @@ module Loxxy
106
106
  expect(subject.lookup('q')).to eq(subject.current_env.defns['q'])
107
107
  end
108
108
 
109
- # it 'should allow the search of an entry based on its i_name' do
110
- # subject.insert(var('q'))
111
- # i_name_x = subject.insert(var('x'))
112
- # subject.enter_environment(BackEnd::Environment.new)
113
- # i_name_q2 = subject.insert(var('q'))
114
- # i_name_y = subject.insert(var('y'))
115
-
116
- # # Search for unknown i_name
117
- # expect(subject.lookup_i_name('dummy')).to be_nil
118
-
119
- # curr_scope = subject.current_env
120
- # # # Search for existing unique names
121
- # expect(subject.lookup_i_name(i_name_y)).to eq(curr_scope.defns['y'])
122
- # expect(subject.lookup_i_name(i_name_x)).to eq(subject.root.defns['x'])
123
-
124
- # # Search for redefined name
125
- # expect(subject.lookup_i_name(i_name_q2)).to eq(curr_scope.defns['q'])
126
- # end
127
-
128
109
  it 'should list all the variables defined in all the szcope chain' do
129
110
  subject.insert(var('q'))
130
111
  subject.enter_environment(BackEnd::Environment.new)
@@ -38,41 +38,6 @@ module Loxxy
38
38
  instance = Variable.new(sample_name)
39
39
  expect(instance.value).to eq(Datatype::Nil.instance)
40
40
  end
41
-
42
- # it 'should know its default internal name' do
43
- # # By default: internal name == label
44
- # expect(subject.i_name).to eq(subject.label)
45
- # end
46
-
47
- # it 'should have a nil suffix' do
48
- # expect(subject.suffix).to be_nil
49
- # end
50
- end # context
51
-
52
- context 'Provided service:' do
53
- let(:sample_suffix) { 'sample-suffix' }
54
- it 'should have a label equal to its user-defined name' do
55
- # expect(subject.label).to eq(subject.name)
56
- end
57
-
58
- it 'should accept a suffix' do
59
- # expect { subject.suffix = sample_suffix }.not_to raise_error
60
- # expect(subject.suffix).to eq(sample_suffix)
61
- end
62
-
63
- it 'should calculate its internal name' do
64
- # # Rule: empty suffix => internal name == label
65
- # subject.suffix = ''
66
- # expect(subject.i_name).to eq(subject.label)
67
-
68
- # # Rule: suffix starting underscore: internal name = label + suffix
69
- # subject.suffix = '_10'
70
- # expect(subject.i_name).to eq(subject.label + subject.suffix)
71
-
72
- # # Rule: ... otherwise: internal name == suffix
73
- # subject.suffix = sample_suffix
74
- # expect(subject.i_name).to eq(subject.suffix)
75
- end
76
41
  end # context
77
42
  end # describe
78
43
  end # module
@@ -124,18 +124,15 @@ LOX_END
124
124
 
125
125
  it 'should recognize number values' do
126
126
  input = <<-LOX_END
127
- 123 987654
128
- 0 -0
129
- 123.456 -0.001
130
- LOX_END
127
+ 123 987654
128
+ 0 123.456
129
+ LOX_END
131
130
 
132
131
  expectations = [
133
132
  ['123', 123],
134
133
  ['987654', 987654],
135
134
  ['0', 0],
136
- ['-0', 0],
137
- ['123.456', 123.456],
138
- ['-0.001', -0.001]
135
+ ['123.456', 123.456]
139
136
  ]
140
137
 
141
138
  subject.start_with(input)
@@ -149,6 +146,30 @@ LOX_END
149
146
  end
150
147
  end
151
148
 
149
+ it 'should recognize negative number values' do
150
+ input = <<-LOX_END
151
+ -0
152
+ -0.001
153
+ LOX_END
154
+
155
+ expectations = [
156
+ ['-', '0'],
157
+ ['-', '0.001']
158
+ ].flatten
159
+
160
+ subject.start_with(input)
161
+ tokens = subject.tokens
162
+ tokens.pop
163
+ i = 0
164
+ tokens.each_slice(2) do |(sign, lit)|
165
+ expect(sign.terminal).to eq('MINUS')
166
+ expect(sign.lexeme).to eq(expectations[i])
167
+ expect(lit.terminal).to eq('NUMBER')
168
+ expect(lit.lexeme).to eq(expectations[i + 1])
169
+ i += 2
170
+ end
171
+ end
172
+
152
173
  it 'should recognize leading and trailing dots as distinct tokens' do
153
174
  input = '.456 123.'
154
175
 
@@ -217,6 +238,13 @@ LOX_END
217
238
  expect(token_nil.lexeme).to eq('nil')
218
239
  expect(token_nil.value).to be_kind_of(Datatype::Nil)
219
240
  end
241
+
242
+ it 'should differentiate nil from variable spelled same' do
243
+ subject.start_with('Nil')
244
+ similar = subject.tokens[0]
245
+ expect(similar.terminal).to eq('IDENTIFIER')
246
+ expect(similar.lexeme).to eq('Nil')
247
+ end
220
248
  end # context
221
249
 
222
250
  context 'Handling comments:' do
@@ -148,6 +148,19 @@ module Loxxy
148
148
  end
149
149
  end
150
150
 
151
+ it 'should ignore spaces surrounding minus in subtraction of two numbers' do
152
+ [
153
+ ['1 - 1;', 0],
154
+ ['1 -1;', 0],
155
+ ['1- 1;', 0],
156
+ ['1-1;', 0]
157
+ ].each do |(source, predicted)|
158
+ lox = Loxxy::Interpreter.new
159
+ result = lox.evaluate(source)
160
+ expect(result.value == predicted).to be_truthy
161
+ end
162
+ end
163
+
151
164
  it 'should evaluate the negation of an object' do
152
165
  [
153
166
  ['!true;', false],
@@ -431,6 +444,17 @@ LOX_END
431
444
  expect(result).to eq(3)
432
445
  end
433
446
 
447
+ it 'should support return within statements inside a function' do
448
+ program = <<-LOX_END
449
+ fun foo() {
450
+ for (;;) return "done";
451
+ }
452
+ print foo(); // output: done
453
+ LOX_END
454
+ expect { subject.evaluate(program) }.not_to raise_error
455
+ expect(sample_cfg[:ostream].string).to eq('done')
456
+ end
457
+
434
458
  # rubocop: disable Style/StringConcatenation
435
459
  it 'should support local functions and closures' do
436
460
  program = <<-LOX_END
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.2.03
4
+ version: 0.3.01
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-04-24 00:00:00.000000000 Z
11
+ date: 2021-05-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rley
@@ -99,7 +99,6 @@ files:
99
99
  - lib/loxxy/ast/lox_call_expr.rb
100
100
  - lib/loxxy/ast/lox_class_stmt.rb
101
101
  - lib/loxxy/ast/lox_compound_expr.rb
102
- - lib/loxxy/ast/lox_for_stmt.rb
103
102
  - lib/loxxy/ast/lox_fun_stmt.rb
104
103
  - lib/loxxy/ast/lox_get_expr.rb
105
104
  - lib/loxxy/ast/lox_grouping_expr.rb
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'lox_compound_expr'
4
-
5
- module Loxxy
6
- module Ast
7
- class LoxForStmt < LoxCompoundExpr
8
- # @return [LoxNode] test expression
9
- attr_reader :test_expr
10
-
11
- # @return [LoxNode] update expression
12
- attr_reader :update_expr
13
-
14
- # @return [LoxNode] body statement
15
- attr_accessor :body_stmt
16
-
17
- # @param aPosition [Rley::Lexical::Position] Position of the entry in the input stream.
18
- # @param initialization [Loxxy::Ast::LoxNode]
19
- # @param testExpr [Loxxy::Ast::LoxNode]
20
- # @param updateExpr [Loxxy::Ast::LoxNode]
21
- def initialize(aPosition, initialization, testExpr, updateExpr)
22
- child = initialization ? [initialization] : []
23
- super(aPosition, child)
24
- @test_expr = testExpr
25
- @update_expr = updateExpr
26
- end
27
-
28
- # Accessor to the condition expression
29
- # @return [LoxNode]
30
- def condition
31
- subnodes[0]
32
- end
33
-
34
- define_accept # Add `accept` method as found in Visitor design pattern
35
- end # class
36
- end # module
37
- end # module