loxxy 0.4.09 → 0.4.10
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/.rubocop.yml +32 -304
- data/CHANGELOG.md +3 -0
- data/LICENSE.txt +1 -1
- data/lib/loxxy/ast/ast_builder.rb +1 -1
- data/lib/loxxy/back_end/engine.rb +2 -0
- data/lib/loxxy/back_end/environment.rb +1 -1
- data/lib/loxxy/front_end/scanner.rb +9 -9
- data/lib/loxxy/interpreter.rb +3 -3
- data/lib/loxxy/version.rb +1 -1
- data/loxxy.gemspec +8 -7
- data/spec/back_end/engine_spec.rb +21 -19
- data/spec/back_end/environment_spec.rb +20 -19
- data/spec/back_end/symbol_table_spec.rb +67 -67
- data/spec/back_end/variable_spec.rb +14 -13
- data/spec/datatype/boolean_spec.rb +9 -8
- data/spec/datatype/lx_string_spec.rb +16 -15
- data/spec/datatype/nil_spec.rb +5 -5
- data/spec/datatype/number_spec.rb +18 -17
- data/spec/front_end/parser_spec.rb +110 -110
- data/spec/front_end/raw_parser_spec.rb +25 -25
- data/spec/front_end/scanner_spec.rb +77 -76
- data/spec/interpreter_spec.rb +114 -118
- data/spec/loxxy_spec.rb +1 -1
- metadata +33 -27
@@ -23,26 +23,27 @@ module Loxxy
|
|
23
23
|
end
|
24
24
|
|
25
25
|
let(:sample_text) { 'print "Hello, world";' }
|
26
|
-
|
26
|
+
|
27
|
+
subject(:scanner) { described_class.new }
|
27
28
|
|
28
29
|
context 'Initialization:' do
|
29
30
|
it 'could be initialized with a text to tokenize or...' do
|
30
|
-
expect {
|
31
|
+
expect { described_class.new(sample_text) }.not_to raise_error
|
31
32
|
end
|
32
33
|
|
33
34
|
it 'could be initialized without argument...' do
|
34
|
-
expect {
|
35
|
+
expect { described_class.new }.not_to raise_error
|
35
36
|
end
|
36
37
|
|
37
|
-
it '
|
38
|
-
expect(
|
38
|
+
it 'has its scanner initialized' do
|
39
|
+
expect(scanner.scanner).to be_a(StringScanner)
|
39
40
|
end
|
40
41
|
end # context
|
41
42
|
|
42
43
|
context 'Input tokenization:' do
|
43
|
-
it '
|
44
|
+
it 'recognizes single special character token' do
|
44
45
|
input = '(){},.-+;*/'
|
45
|
-
|
46
|
+
scanner.start_with(input)
|
46
47
|
expectations = [
|
47
48
|
# [token lexeme]
|
48
49
|
%w[LEFT_PAREN (],
|
@@ -57,12 +58,12 @@ module Loxxy
|
|
57
58
|
%w[STAR *],
|
58
59
|
%w[SLASH /]
|
59
60
|
]
|
60
|
-
match_expectations(
|
61
|
+
match_expectations(scanner, expectations)
|
61
62
|
end
|
62
63
|
|
63
|
-
it '
|
64
|
+
it 'recognizes one or two special character tokens' do
|
64
65
|
input = '! != = == > >= < <='
|
65
|
-
|
66
|
+
scanner.start_with(input)
|
66
67
|
expectations = [
|
67
68
|
# [token lexeme]
|
68
69
|
%w[BANG !],
|
@@ -74,15 +75,15 @@ module Loxxy
|
|
74
75
|
%w[LESS <],
|
75
76
|
%w[LESS_EQUAL <=]
|
76
77
|
]
|
77
|
-
match_expectations(
|
78
|
+
match_expectations(scanner, expectations)
|
78
79
|
end
|
79
80
|
|
80
|
-
it '
|
81
|
+
it 'recognizes non-datatype keywords' do
|
81
82
|
keywords = <<-LOX_END
|
82
83
|
and class else fun for if or
|
83
84
|
print return super this var while
|
84
85
|
LOX_END
|
85
|
-
|
86
|
+
scanner.start_with(keywords)
|
86
87
|
expectations = [
|
87
88
|
# [token lexeme]
|
88
89
|
%w[AND and],
|
@@ -99,30 +100,30 @@ LOX_END
|
|
99
100
|
%w[VAR var],
|
100
101
|
%w[WHILE while]
|
101
102
|
]
|
102
|
-
match_expectations(
|
103
|
+
match_expectations(scanner, expectations)
|
103
104
|
end
|
104
105
|
|
105
|
-
it '
|
106
|
-
|
107
|
-
token_false =
|
108
|
-
expect(token_false).to
|
106
|
+
it 'recognizes a false boolean token' do
|
107
|
+
scanner.start_with('false')
|
108
|
+
token_false = scanner.tokens[0]
|
109
|
+
expect(token_false).to be_a(Rley::Lexical::Literal)
|
109
110
|
expect(token_false.terminal).to eq('FALSE')
|
110
111
|
expect(token_false.lexeme).to eq('false')
|
111
|
-
expect(token_false.value).to
|
112
|
+
expect(token_false.value).to be_a(Datatype::False)
|
112
113
|
expect(token_false.value.value).to be_falsy
|
113
114
|
end
|
114
115
|
|
115
|
-
it '
|
116
|
-
|
117
|
-
token_true =
|
118
|
-
expect(token_true).to
|
116
|
+
it 'recognizes a true boolean token' do
|
117
|
+
scanner.start_with('true')
|
118
|
+
token_true = scanner.tokens[0]
|
119
|
+
expect(token_true).to be_a(Rley::Lexical::Literal)
|
119
120
|
expect(token_true.terminal).to eq('TRUE')
|
120
121
|
expect(token_true.lexeme).to eq('true')
|
121
|
-
expect(token_true.value).to
|
122
|
+
expect(token_true.value).to be_a(Datatype::True)
|
122
123
|
expect(token_true.value.value).to be_truthy
|
123
124
|
end
|
124
125
|
|
125
|
-
it '
|
126
|
+
it 'recognizes number values' do
|
126
127
|
input = <<-LOX_END
|
127
128
|
123 987654
|
128
129
|
0 123.456
|
@@ -135,18 +136,18 @@ LOX_END
|
|
135
136
|
['123.456', 123.456]
|
136
137
|
]
|
137
138
|
|
138
|
-
|
139
|
-
|
140
|
-
expect(tok).to
|
139
|
+
scanner.start_with(input)
|
140
|
+
scanner.tokens[0..-2].each_with_index do |tok, i|
|
141
|
+
expect(tok).to be_a(Rley::Lexical::Literal)
|
141
142
|
expect(tok.terminal).to eq('NUMBER')
|
142
143
|
(lexeme, val) = expectations[i]
|
143
144
|
expect(tok.lexeme).to eq(lexeme)
|
144
|
-
expect(tok.value).to
|
145
|
+
expect(tok.value).to be_a(Datatype::Number)
|
145
146
|
expect(tok.value.value).to eq(val)
|
146
147
|
end
|
147
148
|
end
|
148
149
|
|
149
|
-
it '
|
150
|
+
it 'recognizes negative number values' do
|
150
151
|
input = <<-LOX_END
|
151
152
|
-0
|
152
153
|
-0.001
|
@@ -157,8 +158,8 @@ LOX_END
|
|
157
158
|
['-', '0.001']
|
158
159
|
].flatten
|
159
160
|
|
160
|
-
|
161
|
-
tokens =
|
161
|
+
scanner.start_with(input)
|
162
|
+
tokens = scanner.tokens
|
162
163
|
tokens.pop
|
163
164
|
i = 0
|
164
165
|
tokens.each_slice(2) do |(sign, lit)|
|
@@ -170,24 +171,24 @@ LOX_END
|
|
170
171
|
end
|
171
172
|
end
|
172
173
|
|
173
|
-
it '
|
174
|
+
it 'recognizes leading and trailing dots as distinct tokens' do
|
174
175
|
input = '.456 123.'
|
175
176
|
|
176
|
-
|
177
|
-
tokens =
|
178
|
-
expect(tokens[0]).to
|
177
|
+
scanner.start_with(input)
|
178
|
+
tokens = scanner.tokens[0..-2]
|
179
|
+
expect(tokens[0]).to be_a(Rley::Lexical::Token)
|
179
180
|
expect(tokens[0].terminal).to eq('DOT')
|
180
|
-
expect(tokens[1]).to
|
181
|
+
expect(tokens[1]).to be_a(Rley::Lexical::Literal)
|
181
182
|
expect(tokens[1].terminal).to eq('NUMBER')
|
182
183
|
expect(tokens[1].value.value).to eq(456)
|
183
|
-
expect(tokens[2]).to
|
184
|
+
expect(tokens[2]).to be_a(Rley::Lexical::Literal)
|
184
185
|
expect(tokens[2].terminal).to eq('NUMBER')
|
185
186
|
expect(tokens[2].value.value).to eq(123)
|
186
|
-
expect(tokens[3]).to
|
187
|
+
expect(tokens[3]).to be_a(Rley::Lexical::Token)
|
187
188
|
expect(tokens[3].terminal).to eq('DOT')
|
188
189
|
end
|
189
190
|
|
190
|
-
it '
|
191
|
+
it 'recognizes string values' do
|
191
192
|
input = <<-LOX_END
|
192
193
|
""
|
193
194
|
"string"
|
@@ -200,85 +201,85 @@ LOX_END
|
|
200
201
|
'123'
|
201
202
|
]
|
202
203
|
|
203
|
-
|
204
|
-
|
205
|
-
expect(str).to
|
204
|
+
scanner.start_with(input)
|
205
|
+
scanner.tokens[0..-2].each_with_index do |str, i|
|
206
|
+
expect(str).to be_a(Rley::Lexical::Literal)
|
206
207
|
expect(str.terminal).to eq('STRING')
|
207
208
|
val = expectations[i]
|
208
|
-
expect(str.value).to
|
209
|
+
expect(str.value).to be_a(Datatype::LXString)
|
209
210
|
expect(str.value.value).to eq(val)
|
210
211
|
end
|
211
212
|
end
|
212
213
|
|
213
|
-
it '
|
214
|
+
it 'recognizes escaped quotes' do
|
214
215
|
embedded_quotes = %q{"she said: \"Hello\""}
|
215
|
-
|
216
|
-
result =
|
216
|
+
scanner.start_with(embedded_quotes)
|
217
|
+
result = scanner.tokens[0]
|
217
218
|
expect(result.value).to eq('she said: "Hello"')
|
218
219
|
end
|
219
220
|
|
220
|
-
it '
|
221
|
+
it 'recognizes escaped backslash' do
|
221
222
|
embedded_backslash = '"backslash>\\\\"'
|
222
|
-
|
223
|
-
result =
|
223
|
+
scanner.start_with(embedded_backslash)
|
224
|
+
result = scanner.tokens[0]
|
224
225
|
expect(result.value).to eq('backslash>\\')
|
225
226
|
end
|
226
227
|
|
227
228
|
# rubocop: disable Style/StringConcatenation
|
228
|
-
it '
|
229
|
+
it 'recognizes newline escape sequence' do
|
229
230
|
embedded_newline = '"line1\\nline2"'
|
230
|
-
|
231
|
-
result =
|
231
|
+
scanner.start_with(embedded_newline)
|
232
|
+
result = scanner.tokens[0]
|
232
233
|
expect(result.value).to eq('line1' + "\n" + 'line2')
|
233
234
|
end
|
234
235
|
# rubocop: enable Style/StringConcatenation
|
235
236
|
|
236
|
-
it '
|
237
|
-
|
238
|
-
token_nil =
|
239
|
-
expect(token_nil).to
|
237
|
+
it 'recognizes a nil token' do
|
238
|
+
scanner.start_with('nil')
|
239
|
+
token_nil = scanner.tokens[0]
|
240
|
+
expect(token_nil).to be_a(Rley::Lexical::Literal)
|
240
241
|
expect(token_nil.terminal).to eq('NIL')
|
241
242
|
expect(token_nil.lexeme).to eq('nil')
|
242
|
-
expect(token_nil.value).to
|
243
|
+
expect(token_nil.value).to be_a(Datatype::Nil)
|
243
244
|
end
|
244
245
|
|
245
|
-
it '
|
246
|
-
|
247
|
-
similar =
|
246
|
+
it 'differentiates nil from variable spelled same' do
|
247
|
+
scanner.start_with('Nil')
|
248
|
+
similar = scanner.tokens[0]
|
248
249
|
expect(similar.terminal).to eq('IDENTIFIER')
|
249
250
|
expect(similar.lexeme).to eq('Nil')
|
250
251
|
end
|
251
252
|
end # context
|
252
253
|
|
253
254
|
context 'Handling comments:' do
|
254
|
-
it '
|
255
|
-
|
255
|
+
it 'copes with one line comment only' do
|
256
|
+
scanner.start_with('// comment')
|
256
257
|
|
257
258
|
# No token found, except eof marker
|
258
|
-
eof_token =
|
259
|
+
eof_token = scanner.tokens[0]
|
259
260
|
expect(eof_token.terminal).to eq('EOF')
|
260
261
|
end
|
261
262
|
|
262
263
|
# rubocop: disable Lint/PercentStringArray
|
263
|
-
it '
|
264
|
+
it 'skips end of line comments' do
|
264
265
|
input = <<-LOX_END
|
265
266
|
// first comment
|
266
267
|
print "ok"; // second comment
|
267
268
|
// third comment
|
268
269
|
LOX_END
|
269
|
-
|
270
|
+
scanner.start_with(input)
|
270
271
|
expectations = [
|
271
272
|
# [token lexeme]
|
272
273
|
%w[PRINT print],
|
273
274
|
%w[STRING "ok"],
|
274
275
|
%w[SEMICOLON ;]
|
275
276
|
]
|
276
|
-
match_expectations(
|
277
|
+
match_expectations(scanner, expectations)
|
277
278
|
end
|
278
279
|
# rubocop: enable Lint/PercentStringArray
|
279
280
|
|
280
|
-
it '
|
281
|
-
|
281
|
+
it 'copes with single slash (divide) expression' do
|
282
|
+
scanner.start_with('8 / 2')
|
282
283
|
|
283
284
|
expectations = [
|
284
285
|
# [token lexeme]
|
@@ -286,21 +287,21 @@ LOX_END
|
|
286
287
|
%w[SLASH /],
|
287
288
|
%w[NUMBER 2]
|
288
289
|
]
|
289
|
-
match_expectations(
|
290
|
+
match_expectations(scanner, expectations)
|
290
291
|
end
|
291
292
|
|
292
|
-
it '
|
293
|
-
|
293
|
+
it 'complains if it finds an unterminated string' do
|
294
|
+
scanner.start_with('var a = "Unfinished;')
|
294
295
|
err = Loxxy::ScanError
|
295
296
|
err_msg = 'Error: [line 1:9]: Unterminated string.'
|
296
|
-
expect {
|
297
|
+
expect { scanner.tokens }.to raise_error(err, err_msg)
|
297
298
|
end
|
298
299
|
|
299
|
-
it '
|
300
|
-
|
300
|
+
it 'complains if it finds an unexpected character' do
|
301
|
+
scanner.start_with('var a = ?1?;')
|
301
302
|
err = Loxxy::ScanError
|
302
303
|
err_msg = 'Error: [line 1:9]: Unexpected character.'
|
303
|
-
expect {
|
304
|
+
expect { scanner.tokens }.to raise_error(err, err_msg)
|
304
305
|
end
|
305
306
|
end # context
|
306
307
|
end # describe
|