loxxy 0.2.05 → 0.2.06
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 +4 -4
- data/CHANGELOG.md +16 -0
- data/lib/loxxy/back_end/engine.rb +15 -4
- data/lib/loxxy/back_end/resolver.rb +15 -10
- data/lib/loxxy/datatype/number.rb +19 -4
- data/lib/loxxy/front_end/scanner.rb +6 -5
- data/lib/loxxy/version.rb +1 -1
- data/spec/front_end/scanner_spec.rb +28 -7
- data/spec/interpreter_spec.rb +13 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cbb2f24e245891baabf20458e5b663784ba7f82ff902ab7892007d9b53d99347
|
|
4
|
+
data.tar.gz: eab4ede6f48470449f731e3074b2ac2fe72b53449c9471231ea64d0007a873ae
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8037d3c239e39d47554c4e7f7698e5908cdaa5e460c8972fe8dc5b58ee5fd59647c413db95250ff739c08f9ca891c8f576eb8d78aa63cf40039e8d1b3328e9a2
|
|
7
|
+
data.tar.gz: 795face50a76140ba2f6be2e6e6c44e671992baaea2ecade980acf1baed173dc59cbe7cd2ce419b509e8593068ffc3e637e0678dab2db72a96250d4e53ef0338
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
## [0.2.06] - 2021-05-04
|
|
2
|
+
- Nearly passing the 'official' test suite, fixing non-compliant behavior, specialized exceptions for errors
|
|
3
|
+
|
|
4
|
+
### New
|
|
5
|
+
- Module `LoxFileTester` module that hosts methods that simplify the tests of `Lox` source file.
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- Folder `test_suite` vastly reorganized. Sub-folder `baseline` contains spec files testing the `Lox` files from official implementation
|
|
9
|
+
- Class `BackEnd::Engine` replaced most `StandardError` by `Loxxy::RuntimeError` exception.
|
|
10
|
+
- Class `BackEnd::Resolver` replaced most `StandardError` by `Loxxy::RuntimeError` exception.
|
|
11
|
+
- Method `Datatype::Number#/` now handles correctly expression like `0/0` (integer divide)
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- `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`
|
|
15
|
+
- `FrontEnd::Scanner` now always treats expression like `-123` as the unary or binary minus operator applied to a positive number.
|
|
16
|
+
|
|
1
17
|
## [0.2.05] - 2021-04-26
|
|
2
18
|
- `Loxxy` now transforms for loops into while loops (desugaring), fix in Scanner class
|
|
3
19
|
|
|
@@ -218,7 +218,8 @@ module Loxxy
|
|
|
218
218
|
operator = binary_operators[op]
|
|
219
219
|
operator.validate_operands(operand1, operand2)
|
|
220
220
|
if operand1.respond_to?(op)
|
|
221
|
-
|
|
221
|
+
result = operand1.send(op, operand2)
|
|
222
|
+
stack.push convert2lox_datatype(result)
|
|
222
223
|
else
|
|
223
224
|
msg1 = "`#{op}': Unimplemented operator for a #{operand1.class}."
|
|
224
225
|
raise StandardError, msg1
|
|
@@ -231,7 +232,8 @@ module Loxxy
|
|
|
231
232
|
operator = unary_operators[op]
|
|
232
233
|
operator.validate_operand(operand)
|
|
233
234
|
if operand.respond_to?(op)
|
|
234
|
-
|
|
235
|
+
result = operand.send(op)
|
|
236
|
+
stack.push convert2lox_datatype(result)
|
|
235
237
|
else
|
|
236
238
|
msg1 = "`#{op}': Unimplemented operator for a #{operand.class}."
|
|
237
239
|
raise StandardError, msg1
|
|
@@ -299,7 +301,7 @@ module Loxxy
|
|
|
299
301
|
superklass = variable_lookup(aSuperExpr).value.superclass
|
|
300
302
|
method = superklass.find_method(aSuperExpr.property)
|
|
301
303
|
unless method
|
|
302
|
-
raise
|
|
304
|
+
raise Loxxy::RuntimeError, "Undefined property '#{aSuperExpr.property}'."
|
|
303
305
|
end
|
|
304
306
|
|
|
305
307
|
stack.push method.bind(instance)
|
|
@@ -354,7 +356,7 @@ module Loxxy
|
|
|
354
356
|
unary_operators[:-@] = negate_op
|
|
355
357
|
|
|
356
358
|
negation_op = UnaryOperator.new('!', [Datatype::BuiltinDatatype,
|
|
357
|
-
BackEnd::LoxFunction])
|
|
359
|
+
BackEnd::LoxInstance, BackEnd::LoxFunction, BackEnd::LoxClass])
|
|
358
360
|
unary_operators[:!] = negation_op
|
|
359
361
|
end
|
|
360
362
|
|
|
@@ -408,6 +410,15 @@ module Loxxy
|
|
|
408
410
|
Datatype::Number.new(now)
|
|
409
411
|
end
|
|
410
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
|
|
411
422
|
end # class
|
|
412
423
|
end # module
|
|
413
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
|
|
72
|
+
raise Loxxy::RuntimeError, "'A class can't inherit from itself."
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
@current_class = :subclass
|
|
@@ -96,17 +96,17 @@ module Loxxy
|
|
|
96
96
|
def before_return_stmt(returnStmt)
|
|
97
97
|
if scopes.size < 2
|
|
98
98
|
msg = "Error at 'return': Can't return from top-level code."
|
|
99
|
-
raise
|
|
99
|
+
raise Loxxy::RuntimeError, msg
|
|
100
100
|
end
|
|
101
101
|
|
|
102
102
|
if current_function == :none
|
|
103
103
|
msg = "Error at 'return': Can't return from outside a function."
|
|
104
|
-
raise
|
|
104
|
+
raise Loxxy::RuntimeError, msg
|
|
105
105
|
end
|
|
106
106
|
|
|
107
107
|
if current_function == :initializer
|
|
108
108
|
msg = "Error at 'return': Can't return a value from an initializer."
|
|
109
|
-
raise
|
|
109
|
+
raise Loxxy::RuntimeError, msg unless returnStmt.subnodes[0].kind_of?(Datatype::Nil)
|
|
110
110
|
end
|
|
111
111
|
end
|
|
112
112
|
|
|
@@ -160,7 +160,7 @@ module Loxxy
|
|
|
160
160
|
def before_this_expr(_thisExpr)
|
|
161
161
|
if current_class == :none
|
|
162
162
|
msg = "Error at 'this': Can't use 'this' outside of a class."
|
|
163
|
-
raise
|
|
163
|
+
raise Loxxy::RuntimeError, msg
|
|
164
164
|
end
|
|
165
165
|
end
|
|
166
166
|
|
|
@@ -175,11 +175,11 @@ module Loxxy
|
|
|
175
175
|
msg_prefix = "Error at 'super': Can't use 'super' "
|
|
176
176
|
if current_class == :none
|
|
177
177
|
err_msg = msg_prefix + 'outside of a class.'
|
|
178
|
-
raise
|
|
178
|
+
raise Loxxy::RuntimeError, err_msg
|
|
179
179
|
|
|
180
180
|
elsif current_class == :class
|
|
181
181
|
err_msg = msg_prefix + 'in a class without superclass.'
|
|
182
|
-
raise
|
|
182
|
+
raise Loxxy::RuntimeError, err_msg
|
|
183
183
|
|
|
184
184
|
end
|
|
185
185
|
# 'super' behaves closely to a local variable
|
|
@@ -205,14 +205,19 @@ module Loxxy
|
|
|
205
205
|
scopes.pop
|
|
206
206
|
end
|
|
207
207
|
|
|
208
|
+
# rubocop: disable Style/SoleNestedConditional
|
|
208
209
|
def declare(aVarName)
|
|
209
210
|
return if scopes.empty?
|
|
210
211
|
|
|
211
212
|
curr_scope = scopes.last
|
|
212
|
-
if
|
|
213
|
-
|
|
214
|
-
|
|
213
|
+
if scopes.size > 1 # Not at top-level?
|
|
214
|
+
# Oddly enough, Lox allows variable re-declaration at top-level
|
|
215
|
+
if curr_scope.include?(aVarName)
|
|
216
|
+
msg = "Error at '#{aVarName}': Already variable with this name in this scope."
|
|
217
|
+
raise Loxxy::RuntimeError, msg
|
|
218
|
+
end
|
|
215
219
|
end
|
|
220
|
+
# rubocop: enable Style/SoleNestedConditional
|
|
216
221
|
|
|
217
222
|
# The initializer is not yet processed.
|
|
218
223
|
# Mark the variable as 'not yet ready' = exists but may not be referenced yet
|
|
@@ -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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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]
|
|
@@ -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,16 +99,16 @@ module Loxxy
|
|
|
99
99
|
|
|
100
100
|
token = nil
|
|
101
101
|
|
|
102
|
-
if '(){}
|
|
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(
|
|
105
|
+
elsif (lexeme = scanner.scan(/\+(?!\d)/))
|
|
106
106
|
# Minus or plus character not preceding a digit
|
|
107
107
|
token = build_token(@@lexeme2name[lexeme], lexeme)
|
|
108
108
|
elsif (lexeme = scanner.scan(/[!=><]=?/))
|
|
109
109
|
# One or two special character tokens
|
|
110
110
|
token = build_token(@@lexeme2name[lexeme], lexeme)
|
|
111
|
-
elsif (lexeme = scanner.scan(
|
|
111
|
+
elsif (lexeme = scanner.scan(/\d+(?:\.\d+)?/))
|
|
112
112
|
token = build_token('NUMBER', lexeme)
|
|
113
113
|
elsif (lexeme = scanner.scan(/"(?:\\"|[^"])*"/))
|
|
114
114
|
token = build_token('STRING', lexeme)
|
|
@@ -133,7 +133,8 @@ module Loxxy
|
|
|
133
133
|
def build_token(aSymbolName, aLexeme)
|
|
134
134
|
begin
|
|
135
135
|
(value, symb) = convert_to(aLexeme, aSymbolName)
|
|
136
|
-
|
|
136
|
+
lex_length = aLexeme ? aLexeme.size : 0
|
|
137
|
+
col = scanner.pos - lex_length - @line_start + 1
|
|
137
138
|
pos = Rley::Lexical::Position.new(@lineno, col)
|
|
138
139
|
if value
|
|
139
140
|
token = Literal.new(value, aLexeme.dup, symb, pos)
|
data/lib/loxxy/version.rb
CHANGED
|
@@ -124,18 +124,15 @@ LOX_END
|
|
|
124
124
|
|
|
125
125
|
it 'should recognize number values' do
|
|
126
126
|
input = <<-LOX_END
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
['
|
|
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
|
|
data/spec/interpreter_spec.rb
CHANGED
|
@@ -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],
|
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.
|
|
4
|
+
version: 0.2.06
|
|
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
|
|
11
|
+
date: 2021-05-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rley
|