violet 0.0.1
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.
- data/bin/violet +34 -0
- data/lib/violet.rb +22 -0
- data/lib/violet/lexer.rb +671 -0
- data/lib/violet/parser.rb +1440 -0
- data/lib/violet/token.rb +78 -0
- data/test/test_assertions.rb +550 -0
- data/test/test_violet.rb +36 -0
- metadata +90 -0
@@ -0,0 +1,1440 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module Violet
|
4
|
+
# Internal: Records errors emitted by the parser.
|
5
|
+
class ParserError < Error
|
6
|
+
# Public: Gets the malformed `Token` associated with this error.
|
7
|
+
attr_reader :token
|
8
|
+
|
9
|
+
# Public: Creates a new `ParserError`.
|
10
|
+
#
|
11
|
+
# message - The error message.
|
12
|
+
# token - The malformed `Token`.
|
13
|
+
def initialize(message, token)
|
14
|
+
@token = token
|
15
|
+
super message
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Public: Parses a JavaScript source string.
|
20
|
+
class Parser
|
21
|
+
# Public: Parses a string of JavaScript source code.
|
22
|
+
#
|
23
|
+
# source - The source `String`.
|
24
|
+
#
|
25
|
+
# Returns the resulting `Token` stream as an `Array`.
|
26
|
+
def self.parse(source)
|
27
|
+
new(source).parse
|
28
|
+
end
|
29
|
+
|
30
|
+
# Public: The future reserved words specified in section 7.6.1.2.
|
31
|
+
FUTURE_RESERVED_WORDS = %w( class const enum export extends import super )
|
32
|
+
|
33
|
+
# Public: The reserved keywords specified in section 7.6.1.1, excluding
|
34
|
+
# `this` and the four unary operators.
|
35
|
+
KEYWORDS = %w( break case catch continue debugger default do else finally for function if in instanceof return switch throw try var while with )
|
36
|
+
|
37
|
+
# Public: The `true`, `false`, and `null` literals specified in section 7.8.
|
38
|
+
# The `this` keyword is grouped with the literals for convenience.
|
39
|
+
LITERALS = %w( this null true false )
|
40
|
+
|
41
|
+
# Public: The four unary keyword operators.
|
42
|
+
UNARY_KEYWORDS = %w( delete void typeof new )
|
43
|
+
|
44
|
+
# Public: A list of reserved words that may not be used as labels or
|
45
|
+
# function, argument, or variable names. Comprises the future reserved
|
46
|
+
# words, keywords, and literals.
|
47
|
+
KEYWORD_OR_RESERVED = FUTURE_RESERVED_WORDS.dup.push(*KEYWORDS, *LITERALS, *UNARY_KEYWORDS)
|
48
|
+
|
49
|
+
# Public: Matches valid left-hand side start operators.
|
50
|
+
LHS_START = /[-+~!({\[]/
|
51
|
+
|
52
|
+
# Public: Matches unary operators.
|
53
|
+
UNARY_OPERATORS = /[-+~!]/
|
54
|
+
|
55
|
+
# Public: Matches assignment operators.
|
56
|
+
ASSIGNMENTS = /^[\+\-\*\%\&\|\^\/]?=$|^\<\<\=$|^\>{2,3}\=$/
|
57
|
+
|
58
|
+
# Public: Matches binary operators.
|
59
|
+
BINARY_OPERATORS = /^[\+\-\*\%\|\^\&\?\/]$|^[\<\>]\=?$|^[\=\!]\=\=?$|^\<\<|\>\>\>?$|^\&\&$|^\|\|$/
|
60
|
+
|
61
|
+
# Internal: The error message produced when a statement cannot be parsed.
|
62
|
+
STATEMENT_ERROR = "Expected a statement."
|
63
|
+
|
64
|
+
# Public: Gets the `Lexer` instance associated with this parser.
|
65
|
+
attr_reader :lexer
|
66
|
+
|
67
|
+
# Public: Creates a new `Parser` with a source string.
|
68
|
+
#
|
69
|
+
# source - The source `String`.
|
70
|
+
def initialize(source)
|
71
|
+
@lexer = Lexer.new(source)
|
72
|
+
@tokens = []
|
73
|
+
@exception = nil
|
74
|
+
@try = false
|
75
|
+
end
|
76
|
+
|
77
|
+
# Public: Parses the JavaScript source string associated with this parser
|
78
|
+
# instance.
|
79
|
+
#
|
80
|
+
# Returns the token stream as an `Array`.
|
81
|
+
def parse
|
82
|
+
continue false
|
83
|
+
@tokens
|
84
|
+
end
|
85
|
+
|
86
|
+
# Internal: Stores the token in the parser token stream.
|
87
|
+
#
|
88
|
+
# token - The token to store.
|
89
|
+
#
|
90
|
+
# Returns nothing.
|
91
|
+
def save(token)
|
92
|
+
@tokens << token
|
93
|
+
nil
|
94
|
+
end
|
95
|
+
|
96
|
+
# Internal: Lexes the next non-whitespace token.
|
97
|
+
#
|
98
|
+
# pattern - If the token is `/` or `/=`, specifies whether it may be lexed
|
99
|
+
# as part of a regular expression. If `false`, the token will be lexed as
|
100
|
+
# a division operator instead (default: false).
|
101
|
+
#
|
102
|
+
# Returns the lexed `Token`.
|
103
|
+
def get(pattern = false)
|
104
|
+
# If the parser encountered an exception, the `@exception` instance
|
105
|
+
# variable will be set to the token that triggered the error.
|
106
|
+
if @exception
|
107
|
+
token, @exception = @exception, nil
|
108
|
+
return token
|
109
|
+
end
|
110
|
+
|
111
|
+
# Mark lines for automatic semicolon insertion.
|
112
|
+
multiline = false
|
113
|
+
|
114
|
+
# Consume tokens until a non-whitespace token is encountered.
|
115
|
+
loop do
|
116
|
+
token = lexer.lex pattern
|
117
|
+
# The end-of-file token is not stored in the syntax tree.
|
118
|
+
return token if token[:name] == 12
|
119
|
+
multiline ||= true if token[:lines] && token[:lines] > 0
|
120
|
+
|
121
|
+
# If the lexed token is a whitespace token, save it and continue
|
122
|
+
# consuming tokens. Otherwise, break and return the token. Note
|
123
|
+
# that the non-whitespace token is **not** automatically saved.
|
124
|
+
if token[:isWhite]
|
125
|
+
save token
|
126
|
+
else
|
127
|
+
break
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# If multiple lines were lexed, mark the token for automatic semicolon
|
132
|
+
# insertion.
|
133
|
+
token[:asi] = true if multiline
|
134
|
+
token
|
135
|
+
end
|
136
|
+
|
137
|
+
# Internal: Recursively parses tokens until the source string is consumed.
|
138
|
+
#
|
139
|
+
# token - The current token.
|
140
|
+
#
|
141
|
+
# Returns the token immediately before the end-of-file mark.
|
142
|
+
def continue(token)
|
143
|
+
save token if token
|
144
|
+
# Parse as many tokens as possible. `token` should be the end-of-file mark
|
145
|
+
# following the call to `parse_source_elements`. If it is not, the source
|
146
|
+
# is malformed and requires additional parsing.
|
147
|
+
token = parse_source_elements get(:pattern)
|
148
|
+
parsed = false
|
149
|
+
loop do
|
150
|
+
unless token[:name] == 12
|
151
|
+
# Add a generic error to the token stream unless the current token
|
152
|
+
# already contains an error.
|
153
|
+
unless token[:name] == 14
|
154
|
+
fail "Unexpected token.", token
|
155
|
+
end
|
156
|
+
# Save the malformed token as-is and continue parsing.
|
157
|
+
save token
|
158
|
+
token = get :pattern
|
159
|
+
parsed = true
|
160
|
+
end
|
161
|
+
break unless token[:name] == 14
|
162
|
+
end
|
163
|
+
# Recursively parse the source until the end-of-file mark is reached.
|
164
|
+
token = continue(token) if token[:name] != 12 && parsed
|
165
|
+
# If an error occured at the end of the file, add it to the token stream.
|
166
|
+
if @exception
|
167
|
+
save @exception
|
168
|
+
@exception = nil
|
169
|
+
end
|
170
|
+
token
|
171
|
+
end
|
172
|
+
|
173
|
+
# Internal: Parses a function declaration.
|
174
|
+
#
|
175
|
+
# token - The current token.
|
176
|
+
#
|
177
|
+
# Returns the token immediately following the function body.
|
178
|
+
def parse_function_declaration(token)
|
179
|
+
save token
|
180
|
+
token = get :pattern
|
181
|
+
if token[:name] != 2
|
182
|
+
token = warn "Function declarations cannot be anonymous.", token
|
183
|
+
elsif KEYWORD_OR_RESERVED.include? token[:value]
|
184
|
+
token = warn "Function names may not be keywords or future reserved words.", token
|
185
|
+
end
|
186
|
+
# The subsequent token marks the beginning of the argument list.
|
187
|
+
save token
|
188
|
+
token = get :pattern
|
189
|
+
# Parse the function arguments and body.
|
190
|
+
parse_function_arguments(token, :pattern)
|
191
|
+
end
|
192
|
+
|
193
|
+
# Internal: Parses an arguments list.
|
194
|
+
#
|
195
|
+
# token - The current token.
|
196
|
+
# pattern - If the `/` or `/=` token immediately follows the function body,
|
197
|
+
# this boolean argument specifies whether it may be lexed as part of a
|
198
|
+
# regular expression (default: false).
|
199
|
+
#
|
200
|
+
# Returns the token immediately following the arguments list.
|
201
|
+
def parse_function_arguments(token, pattern = false)
|
202
|
+
unless token[:value] == ?(
|
203
|
+
token = warn "Expected to see an opening parenthesis after the function name.", token
|
204
|
+
end
|
205
|
+
save token
|
206
|
+
token = get :pattern
|
207
|
+
|
208
|
+
if token[:name] == 2
|
209
|
+
if KEYWORD_OR_RESERVED.include? token[:value]
|
210
|
+
fail "Function argument names may not be keywords or future reserved words.", token
|
211
|
+
end
|
212
|
+
save token
|
213
|
+
token = get :pattern
|
214
|
+
|
215
|
+
# Parse the argument list.
|
216
|
+
while token[:value] == ?,
|
217
|
+
save token
|
218
|
+
token = get :pattern
|
219
|
+
if token[:name] != 2
|
220
|
+
fail "Function argument names must be identifiers.", token
|
221
|
+
elsif KEYWORD_OR_RESERVED.include? token[:value]
|
222
|
+
fail "Function argument names may not be keywords or future reserved words.", token
|
223
|
+
end
|
224
|
+
save token
|
225
|
+
token = get :pattern
|
226
|
+
end
|
227
|
+
end
|
228
|
+
unless token[:value] == ?)
|
229
|
+
token = warn "Expected to see a closing parenthesis after the list of arguments.", token
|
230
|
+
end
|
231
|
+
save token
|
232
|
+
token = get :pattern
|
233
|
+
parse_function_body(token, pattern)
|
234
|
+
end
|
235
|
+
|
236
|
+
# Internal: Parses a function body.
|
237
|
+
#
|
238
|
+
# token - The current token.
|
239
|
+
# pattern - If the `/` or `/=` token immediately follows the function body,
|
240
|
+
# this boolean argument specifies whether it may be lexed as part of a
|
241
|
+
# regular expression (default: false).
|
242
|
+
#
|
243
|
+
# Returns the token immediately following the function body.
|
244
|
+
def parse_function_body(token, pattern = false)
|
245
|
+
unless token[:value] == ?{
|
246
|
+
token = warn "Expected `{` after the function header.", token
|
247
|
+
end
|
248
|
+
save token
|
249
|
+
token = get :pattern
|
250
|
+
token = parse_source_elements token
|
251
|
+
unless token[:value] == ?}
|
252
|
+
token = warn "Expected `}` after the function body.", token
|
253
|
+
end
|
254
|
+
save token
|
255
|
+
get pattern
|
256
|
+
end
|
257
|
+
|
258
|
+
# Internal: A `Hash` containing the default options for the `expressions`
|
259
|
+
# method.
|
260
|
+
PARSE_EXPRESSIONS_DEFAULTS = {
|
261
|
+
# If the two initial tokens comprise an identifier followed by a colon
|
262
|
+
# and this option is set to `true`, the parser will treat the lexed
|
263
|
+
# token as a labeled statement. The identifier must not be a reserved
|
264
|
+
# word.
|
265
|
+
:label => false,
|
266
|
+
# Parses a single assignment expression if set to `true`.
|
267
|
+
:single => false,
|
268
|
+
# Parses the expression as part of a `for` loop header. If set to `true`,
|
269
|
+
# the `in` operator is disallowed.
|
270
|
+
:header => false,
|
271
|
+
# Parses the expression as an identifier following a `break` or
|
272
|
+
# `continue` statement.
|
273
|
+
:identifier => false
|
274
|
+
}
|
275
|
+
|
276
|
+
# Internal: Parses one or more assignment expressions or a labeled
|
277
|
+
# statement.
|
278
|
+
#
|
279
|
+
# token - The current token.
|
280
|
+
# options - The expression parsing options.
|
281
|
+
#
|
282
|
+
# Returns the token immediately following the last expression.
|
283
|
+
def parse_assignment_expressions(token, options = {})
|
284
|
+
# @kitcambridge: This method should be broken up into several smaller
|
285
|
+
# helper methods.
|
286
|
+
options = PARSE_EXPRESSIONS_DEFAULTS.merge(options)
|
287
|
+
initial = true
|
288
|
+
|
289
|
+
loop do
|
290
|
+
# Specifies whether the current expression is a reference. Once a non-
|
291
|
+
# reference expression is encountered, subsequent expressions may not
|
292
|
+
# contain assignments.
|
293
|
+
reference = true
|
294
|
+
unless initial
|
295
|
+
save token
|
296
|
+
token = get :pattern
|
297
|
+
# A left-hand side start expression must follow the initial
|
298
|
+
# expression.
|
299
|
+
unless token[:name] <= 6 || LHS_START.match(token[:value])
|
300
|
+
token = warn "Expected a left-hand side expression following `,`.", token
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
# The following loop begins by parsing the left-hand side expression.
|
305
|
+
# If an identifier is encountered, an assignment operator is parsed.
|
306
|
+
# Otherwise, unary expressions are consumed until a binary operator
|
307
|
+
# is encountered. If a `new` or `()` expression was parsed, assignments
|
308
|
+
# are permitted. The right-hand side is then parsed.
|
309
|
+
continue = true
|
310
|
+
while continue
|
311
|
+
unary = false
|
312
|
+
# A unary operator cannot be used as an identifier following a
|
313
|
+
# `break` or `continue` statement, as it is reserved.
|
314
|
+
unless options[:identifier]
|
315
|
+
loop do
|
316
|
+
unary = token[:value] && UNARY_KEYWORDS.include?(token[:value])
|
317
|
+
# Break if the token is not a unary keyword or operator.
|
318
|
+
break unless unary || token[:name] == 11 && UNARY_OPERATORS.match(token[:value])
|
319
|
+
token[:isUnaryOp] = true if unary
|
320
|
+
save token
|
321
|
+
token = get :pattern
|
322
|
+
# A left-hand side expression must follow a unary operator.
|
323
|
+
unless token[:name] <= 6 || LHS_START.match(token[:value])
|
324
|
+
token = warn "Expected a left-hand side expression.", token
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
# If a unary operator was encountered, subsequent expressions may not
|
329
|
+
# be parsed as labeled statements.
|
330
|
+
options[:label] = false if unary
|
331
|
+
# Specifies whether an assignment may follow a parsed expression.
|
332
|
+
assignment_accepted = false
|
333
|
+
|
334
|
+
case token[:value]
|
335
|
+
# Parse a grouped expression.
|
336
|
+
# ---------------------------
|
337
|
+
when ?(
|
338
|
+
save token
|
339
|
+
token = get :pattern
|
340
|
+
unless token[:name] <= 6 || LHS_START.match(token[:value])
|
341
|
+
token = warn "A grouped expression should start with a left-hand-side expression.", token
|
342
|
+
end
|
343
|
+
# Recursively consume comma-separated expressions. The final token
|
344
|
+
# should close the group.
|
345
|
+
token = parse_assignment_expressions token
|
346
|
+
unless token[:value] == ?)
|
347
|
+
token = warn "Unterminated grouped expression.", token
|
348
|
+
end
|
349
|
+
save token
|
350
|
+
# In this context, `/` and `/=` are interpreted as division
|
351
|
+
# operators, rather than regular expressions.
|
352
|
+
token = get
|
353
|
+
# If the grouped expression contains a valid reference and excludes
|
354
|
+
# the comma operator, it may precede an assignment. `(b) = 1` is
|
355
|
+
# valid; `(a, b) = 1` is not.
|
356
|
+
assignment_accepted = true
|
357
|
+
# Parse an array literal.
|
358
|
+
# -----------------------
|
359
|
+
when ?[
|
360
|
+
save token
|
361
|
+
token = get :pattern
|
362
|
+
# Leading commas are treated as elisions.
|
363
|
+
while token[:value] == ?,
|
364
|
+
save token
|
365
|
+
token = get :pattern
|
366
|
+
end
|
367
|
+
# Parse the contents of the array literal. `token` should contain
|
368
|
+
# the closing array punctuator at the end of the following loop.
|
369
|
+
until token[:value] == ?]
|
370
|
+
unless token[:name] <= 6 || LHS_START.match(token[:value]) || token[:name] == 14
|
371
|
+
token = warn "An array literal should start with a left-hand side expression.", token
|
372
|
+
end
|
373
|
+
# Recursively parse each expression stored in the array.
|
374
|
+
# Consecutive commas are treated as elisions.
|
375
|
+
token = parse_assignment_expressions token, :single => true
|
376
|
+
while token[:value] == ?,
|
377
|
+
save token
|
378
|
+
token = get :pattern
|
379
|
+
end
|
380
|
+
end
|
381
|
+
unless token[:value] == ?]
|
382
|
+
token = warn "Unterminated array literal.", token
|
383
|
+
end
|
384
|
+
save token
|
385
|
+
# Treat `/` and `/=` as the division and division assignment
|
386
|
+
# operators, respectively.
|
387
|
+
token = get
|
388
|
+
# The postfix increment and decrement operators cannot follow
|
389
|
+
# values that are not references.
|
390
|
+
while %w(++ --).include? token[:value]
|
391
|
+
fail "The unary increment (`++`) and decrement (`--`) operators cannot be applied to an array literal.", token
|
392
|
+
save token
|
393
|
+
token = get :pattern
|
394
|
+
end
|
395
|
+
# Parse an object literal.
|
396
|
+
# ------------------------
|
397
|
+
when ?{
|
398
|
+
save token
|
399
|
+
token = get :pattern
|
400
|
+
# Unexpected end-of-file mark encountered.
|
401
|
+
if token[:name] == 12
|
402
|
+
token = warn "A colon should precede every object property value.", token
|
403
|
+
end
|
404
|
+
# Parse object members until either the closing brace or a parse
|
405
|
+
# error is encountered.
|
406
|
+
until token[:value] == ?} || token[:name] == 14
|
407
|
+
unless token[:isNumber] || token[:isString] || token[:name] == 2
|
408
|
+
token = warn "Object literal property names can only be strings, numbers, or identifiers.", token
|
409
|
+
end
|
410
|
+
# The `accessor` token references the property name.
|
411
|
+
accessor = token[:value]
|
412
|
+
save token
|
413
|
+
token = get :pattern
|
414
|
+
# Parse attribute accessors-getters and setters.
|
415
|
+
case accessor
|
416
|
+
when "get"
|
417
|
+
# If the current token is the `:` delimiter, the object member
|
418
|
+
# is a standard property, not a getter.
|
419
|
+
if token[:value] == ?:
|
420
|
+
token = parse_object_literal_member token
|
421
|
+
else
|
422
|
+
# Parse the getter as a function.
|
423
|
+
unless token[:isNumber] || token[:isString] || token[:name] == 2
|
424
|
+
# @kitcambridge: This error token is not added to the token
|
425
|
+
# stream in Peter's original implementation.
|
426
|
+
token = warn "Getter names can only be strings, numbers, or identifiers.", token
|
427
|
+
end
|
428
|
+
save token
|
429
|
+
token = get :pattern
|
430
|
+
# Getters cannot accept arguments.
|
431
|
+
unless token[:value] == ?(
|
432
|
+
token = warn "The name of the getter should be followed by the opening parenthesis.", token
|
433
|
+
end
|
434
|
+
save token
|
435
|
+
token = get :pattern
|
436
|
+
unless token[:value] == ?)
|
437
|
+
token = warn "A getter cannot accept arguments.", token
|
438
|
+
end
|
439
|
+
save token
|
440
|
+
token = get :pattern
|
441
|
+
token = parse_function_body(token, :pattern)
|
442
|
+
end
|
443
|
+
when "set"
|
444
|
+
if token[:value] == ?:
|
445
|
+
# The member is a standard property, not a setter.
|
446
|
+
token = parse_object_literal_member token
|
447
|
+
else
|
448
|
+
unless token[:isNumber] || token[:isString] || token[:name] == 2
|
449
|
+
# @kitcambridge: This error token *is* added in Peter's
|
450
|
+
# implementation.
|
451
|
+
token = warn "Setter names can only be strings, numbers, or identifiers.", token
|
452
|
+
end
|
453
|
+
save token
|
454
|
+
token = get :pattern
|
455
|
+
unless token[:value] == ?(
|
456
|
+
token = warn "The name of the setter should be followed by the opening parenthesis.", token
|
457
|
+
end
|
458
|
+
save token
|
459
|
+
token = get :pattern
|
460
|
+
# Setters can accept only one argument.
|
461
|
+
unless token[:name] == 2
|
462
|
+
if token[:value] == ?)
|
463
|
+
token = warn "Setter functions must accept only one argument.", token
|
464
|
+
else
|
465
|
+
token = warn "The setter argument name must be an identifier.", token
|
466
|
+
end
|
467
|
+
end
|
468
|
+
save token
|
469
|
+
token = get :pattern
|
470
|
+
unless token[:value] == ?)
|
471
|
+
if token[:value] == ?,
|
472
|
+
token = warn "Setters may not accept multiple arguments.", token
|
473
|
+
else
|
474
|
+
token = warn "A closing parenthesis should follow the setter argument.", token
|
475
|
+
end
|
476
|
+
end
|
477
|
+
save token
|
478
|
+
token = get :pattern
|
479
|
+
token = parse_function_body(token, :pattern)
|
480
|
+
end
|
481
|
+
else
|
482
|
+
# Standard object member.
|
483
|
+
token = parse_object_literal_member token
|
484
|
+
end
|
485
|
+
# A single trailing comma is permitted.
|
486
|
+
if token[:value] == ?,
|
487
|
+
save token
|
488
|
+
token = get :pattern
|
489
|
+
if token[:value] == ?,
|
490
|
+
token = warn "Multiple trailing commas in object literals are not permitted.", token
|
491
|
+
end
|
492
|
+
elsif token[:value] != ?}
|
493
|
+
token = warn "Expected `,` or `}`. Unexpected member delimiter.", token
|
494
|
+
end
|
495
|
+
end
|
496
|
+
save token
|
497
|
+
# `/` and `/=` are treated as division operators.
|
498
|
+
token = get
|
499
|
+
# Object literals are not references.
|
500
|
+
while %w(++ --).include? token[:value]
|
501
|
+
fail "The unary increment (`++`) and decrement (`--`) operators cannot be applied to an object literal.", token
|
502
|
+
save token
|
503
|
+
token = get :pattern
|
504
|
+
end
|
505
|
+
# Parse a function expression.
|
506
|
+
# ----------------------------
|
507
|
+
when "function"
|
508
|
+
save token
|
509
|
+
token = get :pattern
|
510
|
+
# A label may precede a function expression or declaration.
|
511
|
+
if options[:label] && token[:value] == ?:
|
512
|
+
token = warn "Labels may not be keywords or future reserved words.", token
|
513
|
+
end
|
514
|
+
# Validate the function name, if one is found. Anonymous function
|
515
|
+
# expressions will omit the name.
|
516
|
+
if token[:name] == 2
|
517
|
+
if KEYWORD_OR_RESERVED.include?(token[:value])
|
518
|
+
token = warn "Function names may not be keywords or future reserved words.", token
|
519
|
+
end
|
520
|
+
save token
|
521
|
+
token = get :pattern
|
522
|
+
end
|
523
|
+
# Parse the function arguments and body. The returned token should
|
524
|
+
# be the token immediately following the closing brace of the body.
|
525
|
+
token = parse_function_arguments token
|
526
|
+
while %w(++ --).include? token[:value]
|
527
|
+
fail "The unary increment (`++`) and decrement (`--`) operators cannot be applied to a function.", token
|
528
|
+
save token
|
529
|
+
token = get :pattern
|
530
|
+
end
|
531
|
+
# Parse labels and error cases.
|
532
|
+
# -----------------------------
|
533
|
+
else
|
534
|
+
# Parse labels for right-hand side expressions.
|
535
|
+
if token[:name] <= 6
|
536
|
+
# Save the current token, which may be a label.
|
537
|
+
possible_label = token
|
538
|
+
|
539
|
+
# Validate the label identifier. This is not optional if the
|
540
|
+
# expression should be parsed as an identifier following a
|
541
|
+
# `break` or `continue` statement.
|
542
|
+
if token[:name] == 2
|
543
|
+
# @kitcambridge: Peter's original parser allows `LITERALS` to
|
544
|
+
# be used as label names; all popular implementations disagree.
|
545
|
+
# Removing this causes an unexpected token error to be emitted
|
546
|
+
# for standalone literals. @qfox/ZeParser#9.
|
547
|
+
if !LITERALS.include?(token[:value]) && KEYWORD_OR_RESERVED.include?(token[:value])
|
548
|
+
# A statement label is an identifier, which may not be a
|
549
|
+
# reserved word.
|
550
|
+
if options[:identifier]
|
551
|
+
fail "Statement labels passed to `break` and `continue` must be identifiers.", token
|
552
|
+
elsif token[:value] == "else"
|
553
|
+
fail "Dangling `else`.", token
|
554
|
+
else
|
555
|
+
# @kitcambridge: Peter is considering adding a lookahead to
|
556
|
+
# check for a trailing colon and emit a malformed label
|
557
|
+
# error. This may also solve issue #9 above.
|
558
|
+
fail "Unexpected token.", token
|
559
|
+
end
|
560
|
+
end
|
561
|
+
# Assignments are only accepted after member expressions:
|
562
|
+
# either an identifier or square brackets.
|
563
|
+
assignment_accepted = true
|
564
|
+
elsif options[:identifier]
|
565
|
+
# Malformed label.
|
566
|
+
token = warn "Statement labels passed to `break` and `continue` must be identifiers.", token
|
567
|
+
end
|
568
|
+
save token
|
569
|
+
# `/` and `/=` are treated as division operators.
|
570
|
+
token = get
|
571
|
+
# Check for a labeled statement. If the `label` option was
|
572
|
+
# specified and the current token is the `:` delimiter,
|
573
|
+
# the previous `possible_label` token must be an
|
574
|
+
# identifier (validation occured above; this routine merely
|
575
|
+
# catches malformed labels).
|
576
|
+
if options[:label] && token[:value] == ?:
|
577
|
+
unless possible_label[:name] == 2
|
578
|
+
fail "Label names must be identifiers.", token
|
579
|
+
end
|
580
|
+
save token
|
581
|
+
token = get :pattern
|
582
|
+
possible_label[:isLabel] = true
|
583
|
+
# If the label is valid, the subsequent token marks the
|
584
|
+
# beginning of the labeled statement. `token` should
|
585
|
+
# reference the first token following the statement.
|
586
|
+
token = parse_statement token
|
587
|
+
# If the statement could not be parsed, correct the error to
|
588
|
+
# account for the label.
|
589
|
+
if token[:error] && token[:error].message == STATEMENT_ERROR
|
590
|
+
token[:error] = ParserError.new("Expected a statement after the label.", token)
|
591
|
+
end
|
592
|
+
token[:wasLabel] = true
|
593
|
+
return token
|
594
|
+
end
|
595
|
+
options[:label] = false
|
596
|
+
# Lexer Errors.
|
597
|
+
# -------------
|
598
|
+
elsif token[:name] == 14
|
599
|
+
loop do
|
600
|
+
if token[:tokenError]
|
601
|
+
error = Token.new(lexer, :error, token[:start]...token[:start])
|
602
|
+
error[:error] = ParserError.new("Lexer Error: #{token[:error].message}", token)
|
603
|
+
save error
|
604
|
+
lexer.insert_before(error, token)
|
605
|
+
end
|
606
|
+
save token
|
607
|
+
token = get :pattern
|
608
|
+
break unless token[:name] == 14
|
609
|
+
end
|
610
|
+
# Unexpected End-of-File.
|
611
|
+
# -----------------------
|
612
|
+
elsif token[:name] == 12
|
613
|
+
return token
|
614
|
+
# Fail.
|
615
|
+
# -----
|
616
|
+
# If the token value is the closing curly brace (`}`), it is
|
617
|
+
# ignored. According to Peter, automatic semicolon insertion may be
|
618
|
+
# applied; alternatively, this may be part of an object literal.
|
619
|
+
# The other parsing routines will attempt to handle it.
|
620
|
+
elsif token[:value] != ?}
|
621
|
+
fail "Unexpected token.", token
|
622
|
+
save token
|
623
|
+
token = get :pattern
|
624
|
+
end
|
625
|
+
end
|
626
|
+
# Property Access and Call Expressions.
|
627
|
+
# -------------------------------------
|
628
|
+
while token[:value] == ?. || token[:value] == ?[ || token[:value] == ?(
|
629
|
+
# None of the characters may occur in an identifier.
|
630
|
+
if options[:identifier]
|
631
|
+
token = warn "Statement labels passed to `break` and `continue` must be identifiers.", token
|
632
|
+
end
|
633
|
+
case token[:value]
|
634
|
+
# Dot Member Operator.
|
635
|
+
# --------------------
|
636
|
+
when ?.
|
637
|
+
save token
|
638
|
+
token = get :pattern
|
639
|
+
# The referenced property name must be an identifier. ES 5 allows
|
640
|
+
# the use of reserved words as property names, so no additional
|
641
|
+
# checks are performed.
|
642
|
+
unless token[:name] == 2
|
643
|
+
fail "Property names following the dot member operator must be identifiers.", token
|
644
|
+
end
|
645
|
+
save token
|
646
|
+
# `/` and `/=` are treated as division operators.
|
647
|
+
token = get
|
648
|
+
# The result of a member access operation may be assigned to.
|
649
|
+
assignment_accepted = true
|
650
|
+
# Square Bracket Member Operator.
|
651
|
+
# -------------------------------
|
652
|
+
when ?[
|
653
|
+
save token
|
654
|
+
token = get :pattern
|
655
|
+
# The brackets must contain at least one left-hand side start
|
656
|
+
# expression.
|
657
|
+
unless token[:name] <= 6 || LHS_START.match(token[:value])
|
658
|
+
token = warn "Square brackets must contain an expression.", token
|
659
|
+
end
|
660
|
+
# Recursively parse assignment expressions. The final token should
|
661
|
+
# be the terminating bracket.
|
662
|
+
token = parse_assignment_expressions token
|
663
|
+
unless token[:value] == ?]
|
664
|
+
token = warn "Unterminated square bracket member accessor.", token
|
665
|
+
end
|
666
|
+
save token
|
667
|
+
# `/` and `/=` are treated as division operators.
|
668
|
+
token = get
|
669
|
+
assignment_accepted = true
|
670
|
+
# Call Expression.
|
671
|
+
# ----------------
|
672
|
+
when ?(
|
673
|
+
save token
|
674
|
+
token = get :pattern
|
675
|
+
# The current token marks either the closing parenthesis of an
|
676
|
+
# empty argument list, or the first argument, which must be a
|
677
|
+
# left-hand side start expression.
|
678
|
+
if token[:name] <= 6 || LHS_START.match(token[:value])
|
679
|
+
# Recursively parse the arguments, which may be assignment
|
680
|
+
# expressions. The comma is used as a separator, not as an
|
681
|
+
# operator.
|
682
|
+
token = parse_assignment_expressions token
|
683
|
+
end
|
684
|
+
unless token[:value] == ?)
|
685
|
+
token = warn "Unterminated parentheses following the call expression.", token
|
686
|
+
end
|
687
|
+
save token
|
688
|
+
# `/` and `/=` are treated as division operators.
|
689
|
+
token = get
|
690
|
+
# The result of a function call may not be assigned to.
|
691
|
+
assignment_accepted = false
|
692
|
+
end
|
693
|
+
end
|
694
|
+
# Postfix unary increment and decrement operators.
|
695
|
+
if (token[:value] == "++" || token[:value] == "--") && !token[:asi]
|
696
|
+
if options[:identifier]
|
697
|
+
token = warn "Statement labels passed to `break` and `continue` must be identifiers.", token
|
698
|
+
end
|
699
|
+
save token
|
700
|
+
# `/` and `/=` are treated as division operators.
|
701
|
+
token = get
|
702
|
+
end
|
703
|
+
|
704
|
+
# Parse any subsequent operators.
|
705
|
+
# -------------------------------
|
706
|
+
loop do
|
707
|
+
conditional = false
|
708
|
+
# Parse the `instanceof` operator, assignment operators (+=, /=,
|
709
|
+
# >>=, etc.), and binary expression operators. The `in` operator
|
710
|
+
# is parsed only if the `header` option is set-this specifies
|
711
|
+
# that the expression should be treated as part of a `for` loop
|
712
|
+
# header.
|
713
|
+
if !options[:header] && token[:value] == "in" || token[:value] == "instanceof" || (token[:name] == 11 && (token[:isAssignment] = !!ASSIGNMENTS.match(token[:value])) || BINARY_OPERATORS.match(token[:value]))
|
714
|
+
if token[:isAssignment]
|
715
|
+
# Emit an error if an expression may not be assigned to or is
|
716
|
+
# not a valid reference.
|
717
|
+
case false
|
718
|
+
when assignment_accepted
|
719
|
+
fail "The left-hand side of the assignment expression is not a reference.", token
|
720
|
+
when reference
|
721
|
+
fail "An assignment expression may not follow a non-assignment operator.", token
|
722
|
+
end
|
723
|
+
end
|
724
|
+
# Labels may not share operator names.
|
725
|
+
if options[:identifier]
|
726
|
+
token = warn "Statement labels passed to `break` and `continue` must be identifiers.", token
|
727
|
+
end
|
728
|
+
# If a non-assignment operator was parsed, the remainder of the
|
729
|
+
# expression may not contain assignments.
|
730
|
+
reference = false unless token[:isAssignment]
|
731
|
+
|
732
|
+
# Conditional (Ternary) Operator.
|
733
|
+
# -------------------------------
|
734
|
+
conditional = token[:value] == ??
|
735
|
+
save token
|
736
|
+
token = get :pattern
|
737
|
+
if conditional
|
738
|
+
# The `true` condition should begin with a left-hand side
|
739
|
+
# expression.
|
740
|
+
unless token[:name] <= 6 || LHS_START.match(token[:value])
|
741
|
+
fail "Invalid conditional expression.", token
|
742
|
+
end
|
743
|
+
# Recursively parse the `true` assignment expression.
|
744
|
+
token = parse_assignment_expressions token, :single => true, :header => options[:header]
|
745
|
+
unless token[:value] == ?:
|
746
|
+
if token[:value] == ?,
|
747
|
+
token = warn "The comma operator may only be used as part of a nested expression.", token
|
748
|
+
else
|
749
|
+
token = warn "Missing second conditional expression.", token
|
750
|
+
end
|
751
|
+
end
|
752
|
+
save token
|
753
|
+
token = get :pattern
|
754
|
+
# Parse the `false` assignment expression.
|
755
|
+
token = parse_assignment_expressions token, :single => true, :header => options[:header]
|
756
|
+
end
|
757
|
+
else
|
758
|
+
# The operator is invalid.
|
759
|
+
continue = false
|
760
|
+
end
|
761
|
+
# If a ternary expression was parsed, verify if the next token is a
|
762
|
+
# binary operator.
|
763
|
+
break unless conditional
|
764
|
+
end
|
765
|
+
# Parse the next component. `token` marks the end of the right-hand
|
766
|
+
# side for the current left-hand side expression, and the left-hand
|
767
|
+
# side for the next.
|
768
|
+
if continue && !(token[:name] <= 6 || LHS_START.match(token[:value]))
|
769
|
+
fail "Expected a right-hand side expression following the assignment operator.", token
|
770
|
+
end
|
771
|
+
end
|
772
|
+
|
773
|
+
# Cleanup
|
774
|
+
# -------
|
775
|
+
# Subsequent tokens may not be parsed as labeled statements, and do not
|
776
|
+
# require an initial left-hand side expression.
|
777
|
+
options[:label] = initial = false
|
778
|
+
# Continue parsing expressions unless the `single` option was explicitly
|
779
|
+
# specified or the current token is the comma operator.
|
780
|
+
break unless !options[:single] && token[:value] == ?,
|
781
|
+
end
|
782
|
+
token
|
783
|
+
end
|
784
|
+
|
785
|
+
# Internal: Parses an object literal member.
|
786
|
+
#
|
787
|
+
# token - The current token.
|
788
|
+
#
|
789
|
+
# Returns the token immediately following the member.
|
790
|
+
def parse_object_literal_member(token)
|
791
|
+
unless token[:value] == ?:
|
792
|
+
token = warn "A colon should follow every property name.", token
|
793
|
+
end
|
794
|
+
save token
|
795
|
+
token = get :pattern
|
796
|
+
previous_token = token
|
797
|
+
token = parse_assignment_expressions token, :single => true
|
798
|
+
if previous_token == token
|
799
|
+
token = warn "Missing object literal property value.", token
|
800
|
+
end
|
801
|
+
token
|
802
|
+
end
|
803
|
+
|
804
|
+
# Internal: Parses a semicolon.
|
805
|
+
#
|
806
|
+
# token - The current token.
|
807
|
+
#
|
808
|
+
# Returns the token immediately adjacent to the semicolon.
|
809
|
+
def parse_semicolon(token)
|
810
|
+
if token[:value] == ?;
|
811
|
+
save token
|
812
|
+
token = get :pattern
|
813
|
+
else
|
814
|
+
# Automatic semicolon insertion cannot be applied if the end-of-file
|
815
|
+
# mark has not been reached and the current token either is a semicolon,
|
816
|
+
# or does not follow at least one line terminator and is not a closing
|
817
|
+
# curly brace, and is not preceded by a line terminator or is not a
|
818
|
+
# unary increment or decrement operator.
|
819
|
+
if token[:name] != 12 && (token[:semicolon] || !(token[:asi] || token[:value] == ?})) && !(token[:asi] && %w(++ --).include?(token[:value]))
|
820
|
+
fail "Expected `;`. Automatic semicolon insertion cannot be applied.", token
|
821
|
+
else
|
822
|
+
# @kitcambridge: This is an inversion of Peter's code, which checks
|
823
|
+
# for the affirmative condition. Peter also notes that this method
|
824
|
+
# does not check for restricted productions (`break`, `continue`, and
|
825
|
+
# `return`), if the subsequent line is a regular expression, or the
|
826
|
+
# semicolon is part of a `for` loop header. He notes that the parser
|
827
|
+
# is designed to automatically catch the latter two exceptions.
|
828
|
+
#
|
829
|
+
# The current token is the token that occurs *after* the insertion,
|
830
|
+
# not before; hence, its position is the *beginning* of the current
|
831
|
+
# token.
|
832
|
+
semicolon = Token.new(lexer, :asi, token[:start]...token[:start])
|
833
|
+
save semicolon
|
834
|
+
lexer.insert_before(semicolon, token)
|
835
|
+
end
|
836
|
+
end
|
837
|
+
token[:semicolon] = true
|
838
|
+
token
|
839
|
+
end
|
840
|
+
|
841
|
+
# Internal: Parses a statement.
|
842
|
+
#
|
843
|
+
# token - The current token.
|
844
|
+
#
|
845
|
+
# Returns the token immediately adjacent to the statement.
|
846
|
+
def parse_statement(token, optional = false)
|
847
|
+
# @kitcambridge: Peter's parser returns the value of the `token` parameter
|
848
|
+
# if it is falsy and the `optional` option is set. Because Ruby enforces
|
849
|
+
# method arity and none of the operations may produce a `nil` token, this
|
850
|
+
# seems redundant.
|
851
|
+
if token[:name] == 2
|
852
|
+
# If the token is an identifier, determine if it can be parsed as a
|
853
|
+
# statement.
|
854
|
+
if %w( var if do while for continue break return throw switch try debugger with ).include? token[:value]
|
855
|
+
token = send "parse_#{token[:value]}_statement", token
|
856
|
+
elsif token[:value] == "function"
|
857
|
+
fail "Function statements are an extension of ECMAScript semantics. Their use is discouraged.", token
|
858
|
+
token = parse_function_declaration token
|
859
|
+
else
|
860
|
+
token = parse_expression_or_label token
|
861
|
+
end
|
862
|
+
elsif token[:value] == ?{
|
863
|
+
# Blocks are parsed before expressions.
|
864
|
+
token = parse_block token
|
865
|
+
elsif token[:isString] || token[:isNumber] || token[:name] == 1 || LHS_START.match(token[:value])
|
866
|
+
# Parse expressions (strings, numbers, RegExps, and left-hand side start
|
867
|
+
# values). Each expression should be followed by a semicolon, whether
|
868
|
+
# explicit or implicit through ASI.
|
869
|
+
token = parse_assignment_expressions token
|
870
|
+
token = parse_semicolon token
|
871
|
+
elsif token[:value] == ?;
|
872
|
+
# The empty statement. Parse the current token as a semicolon.
|
873
|
+
token[:emptyStatement] = true
|
874
|
+
token = parse_semicolon token
|
875
|
+
elsif !optional
|
876
|
+
token = warn STATEMENT_ERROR, token
|
877
|
+
end
|
878
|
+
token
|
879
|
+
end
|
880
|
+
|
881
|
+
# Internal: Parses a block.
|
882
|
+
#
|
883
|
+
# token - The current token.
|
884
|
+
#
|
885
|
+
# Returns the token immediately adjacent to the block.
|
886
|
+
def parse_block(token)
|
887
|
+
save token
|
888
|
+
token = get :pattern
|
889
|
+
unless token[:value] == ?}
|
890
|
+
token = parse_statements token
|
891
|
+
end
|
892
|
+
unless token[:value] == ?}
|
893
|
+
token = warn "Unterminated block. Expected `}`.", token
|
894
|
+
end
|
895
|
+
save token
|
896
|
+
get :pattern
|
897
|
+
end
|
898
|
+
|
899
|
+
# Internal: Parses an expression or labeled statement.
|
900
|
+
#
|
901
|
+
# token - The current token.
|
902
|
+
#
|
903
|
+
# Returns the token immediately following the result.
|
904
|
+
def parse_expression_or_label(token)
|
905
|
+
token = parse_assignment_expressions token, :label => true
|
906
|
+
# Only parse a semicolon if a label was not parsed.
|
907
|
+
unless token[:wasLabel]
|
908
|
+
token = parse_semicolon token
|
909
|
+
end
|
910
|
+
token
|
911
|
+
end
|
912
|
+
|
913
|
+
# Internal: Continuously parses statements.
|
914
|
+
#
|
915
|
+
# token - The current token.
|
916
|
+
# allow_functions - If this option is set to `true`, function declarations
|
917
|
+
# may be parsed. Statements should not begin with functions.
|
918
|
+
#
|
919
|
+
# Returns the token immediately following the result.
|
920
|
+
def parse_tokens(token, allow_functions = false)
|
921
|
+
# @kitcambridge: Peter notes that detecting the beginning of a statement
|
922
|
+
# is difficult. The parser consumes statements until the current token is
|
923
|
+
# identical to the previous, which occurs if a statement is optional.
|
924
|
+
previous_token = token
|
925
|
+
loop do
|
926
|
+
token = if allow_functions && previous_token[:value] == "function"
|
927
|
+
parse_function_declaration previous_token
|
928
|
+
else
|
929
|
+
parse_statement(previous_token, :optional)
|
930
|
+
end
|
931
|
+
break if previous_token == token
|
932
|
+
previous_token = token
|
933
|
+
end
|
934
|
+
token
|
935
|
+
end
|
936
|
+
|
937
|
+
# Internal: Continuously parses statements.
|
938
|
+
def parse_statements(token)
|
939
|
+
parse_tokens(token)
|
940
|
+
end
|
941
|
+
|
942
|
+
# Internal: Continuously parses source elements.
|
943
|
+
def parse_source_elements(token)
|
944
|
+
parse_tokens(token, :functions)
|
945
|
+
end
|
946
|
+
|
947
|
+
# Internal.
|
948
|
+
def parse_var_statement(token)
|
949
|
+
token = parse_variable_declarations token
|
950
|
+
parse_semicolon token
|
951
|
+
end
|
952
|
+
|
953
|
+
# Internal.
|
954
|
+
def parse_variable_declarations(token, header = false)
|
955
|
+
initial = true
|
956
|
+
loop do
|
957
|
+
save token
|
958
|
+
# `token` references the `var` statement on the first iteration, and
|
959
|
+
# the comma separator for all subsequent iterations.
|
960
|
+
token = get :pattern
|
961
|
+
# The subsequent token should be an identifier that specifies the
|
962
|
+
# variable name.
|
963
|
+
if token[:name] == 12
|
964
|
+
# @kitcambridge: Peter proposes returning if an illegal trailing comma
|
965
|
+
# is encountered.
|
966
|
+
token = warn(initial ? "A `var` declaration should be followed by the variable name." : "Illegal trailing comma.", token)
|
967
|
+
elsif token[:name] != 2
|
968
|
+
token = warn "Variable names can only be identifiers.", token
|
969
|
+
elsif KEYWORD_OR_RESERVED.include? token[:value]
|
970
|
+
token = warn "Variable names may not be keywords or future reserved words.", token
|
971
|
+
end
|
972
|
+
save token
|
973
|
+
# The next token should be either `=`, `;`, or `,`.
|
974
|
+
token = get :pattern
|
975
|
+
if token[:value] == ?=
|
976
|
+
# Parse a single assignment expression and an optional trailing comma.
|
977
|
+
token[:initializer] = true
|
978
|
+
save token
|
979
|
+
token = get :pattern
|
980
|
+
unless token[:name] <= 6 || token[:name] == 14 || LHS_START.match(token[:value])
|
981
|
+
token = warn "The variable value must be an expression that does not contain a comma.", token
|
982
|
+
end
|
983
|
+
token = parse_assignment_expressions token, :single => true, :header => header
|
984
|
+
end
|
985
|
+
initial = false
|
986
|
+
# Loop until all trailing commas have been consumed.
|
987
|
+
break unless token[:value] == ?,
|
988
|
+
end
|
989
|
+
token
|
990
|
+
end
|
991
|
+
|
992
|
+
# Internal.
|
993
|
+
def parse_if_statement(token)
|
994
|
+
save token
|
995
|
+
token = get :pattern
|
996
|
+
unless token[:value] == ?(
|
997
|
+
token = warn "Expected `(`.", token
|
998
|
+
end
|
999
|
+
save token
|
1000
|
+
token = get :pattern
|
1001
|
+
unless token[:name] <= 6 || LHS_START.match(token[:value])
|
1002
|
+
token = warn "A statement header must contain at least one expression.", token
|
1003
|
+
end
|
1004
|
+
token = parse_assignment_expressions token
|
1005
|
+
unless token[:value] == ?)
|
1006
|
+
token = warn "Expected `)`.", token
|
1007
|
+
end
|
1008
|
+
save token
|
1009
|
+
token = get :pattern
|
1010
|
+
token = parse_statement token
|
1011
|
+
token = parse_else_statement(token) if token[:value] == "else"
|
1012
|
+
token
|
1013
|
+
end
|
1014
|
+
|
1015
|
+
# Internal.
|
1016
|
+
def parse_else_statement(token)
|
1017
|
+
save token
|
1018
|
+
token = get :pattern
|
1019
|
+
parse_statement token
|
1020
|
+
end
|
1021
|
+
|
1022
|
+
# Internal.
|
1023
|
+
def parse_do_statement(token)
|
1024
|
+
save token
|
1025
|
+
token = get :pattern
|
1026
|
+
token = parse_statement token
|
1027
|
+
unless token[:value] == "while"
|
1028
|
+
token = warn "The `do...while` loop requires the `while` statement after the loop body.", token
|
1029
|
+
end
|
1030
|
+
save token
|
1031
|
+
token = get :pattern
|
1032
|
+
unless token[:value] == ?(
|
1033
|
+
token = warn "Expected `(`.", token
|
1034
|
+
end
|
1035
|
+
save token
|
1036
|
+
token = get :pattern
|
1037
|
+
unless token[:name] <= 6 || LHS_START.match(token[:value])
|
1038
|
+
token = warn "A statement header must contain at least one expression.", token
|
1039
|
+
end
|
1040
|
+
token = parse_assignment_expressions token
|
1041
|
+
unless token[:value] == ?)
|
1042
|
+
token = warn "Expected `)`.", token
|
1043
|
+
end
|
1044
|
+
save token
|
1045
|
+
token = get :pattern
|
1046
|
+
# @kitcambridge: According to Peter, the trailing semicolon is not
|
1047
|
+
# optional, though implementations apply ASI nonetheless.
|
1048
|
+
parse_semicolon token
|
1049
|
+
end
|
1050
|
+
|
1051
|
+
# Internal.
|
1052
|
+
def parse_while_statement(token)
|
1053
|
+
save token
|
1054
|
+
token = get :pattern
|
1055
|
+
unless token[:value] == ?(
|
1056
|
+
token = warn "Expected `(`.", token
|
1057
|
+
end
|
1058
|
+
save token
|
1059
|
+
token = get :pattern
|
1060
|
+
# The `while` loop header must contain a valid left-hand side start
|
1061
|
+
# value.
|
1062
|
+
unless token[:name] <= 6 || LHS_START.match(token[:value])
|
1063
|
+
token = warn "A statement header must contain at least one expression.", token
|
1064
|
+
end
|
1065
|
+
token = parse_assignment_expressions token
|
1066
|
+
unless token[:value] == ?)
|
1067
|
+
token = "Expected `)`.", token
|
1068
|
+
end
|
1069
|
+
save token
|
1070
|
+
token = get :pattern
|
1071
|
+
parse_statement(token)
|
1072
|
+
end
|
1073
|
+
|
1074
|
+
# Public: Parses a `for` or `for...in` loop.
|
1075
|
+
def parse_for_statement(token)
|
1076
|
+
save token
|
1077
|
+
token = get :pattern
|
1078
|
+
unless token[:value] == ?(
|
1079
|
+
token = warn "Expected `(`.", token
|
1080
|
+
end
|
1081
|
+
save token
|
1082
|
+
token = get :pattern
|
1083
|
+
# Parse either the initialization section of a `for` loop or the left-hand
|
1084
|
+
# side `in` operand of a `for...in` loop. The parser subsequently
|
1085
|
+
# determines which loop type is used.
|
1086
|
+
if token[:value] == "var"
|
1087
|
+
token = parse_variable_declarations(token, :header)
|
1088
|
+
elsif token[:value] != ?;
|
1089
|
+
unless token[:name] <= 6 || LHS_START.match(token[:value])
|
1090
|
+
fail "A `for` statement header must contain at least one expression.", token
|
1091
|
+
end
|
1092
|
+
# Multiple expressions are permitted, but the `in` operator is not.
|
1093
|
+
token = parse_assignment_expressions token, :header => true
|
1094
|
+
end
|
1095
|
+
if token[:value] == "in"
|
1096
|
+
# @kitcambridge: Violet does not verify if the `for...in` loop header
|
1097
|
+
# introduces only one variable. This requires the full AST.
|
1098
|
+
save token
|
1099
|
+
token = get :pattern
|
1100
|
+
token = parse_assignment_expressions token
|
1101
|
+
else
|
1102
|
+
unless token[:value] == ?;
|
1103
|
+
token = warn "A `for` loop header must contain either the `in` operator or two consecutive semicolons (`;;`).", token
|
1104
|
+
end
|
1105
|
+
# Parse two optional expressions separated by a semicolon. The `in`
|
1106
|
+
# operator is permitted.
|
1107
|
+
save token
|
1108
|
+
token = get :pattern
|
1109
|
+
token = parse_assignment_expressions(token) if token[:name] <= 6 || LHS_START.match(token[:value])
|
1110
|
+
unless token[:value] == ?;
|
1111
|
+
token = warn "Expected `;`.", token
|
1112
|
+
end
|
1113
|
+
save token
|
1114
|
+
token = get :pattern
|
1115
|
+
token = parse_assignment_expressions(token) if token[:name] <= 6 || LHS_START.match(token[:value])
|
1116
|
+
end
|
1117
|
+
unless token[:value] == ?)
|
1118
|
+
token = warn "Expected `)`.", token
|
1119
|
+
end
|
1120
|
+
save token
|
1121
|
+
token = get :pattern
|
1122
|
+
parse_statement(token)
|
1123
|
+
end
|
1124
|
+
|
1125
|
+
# Internal.
|
1126
|
+
def parse_continue_statement(token)
|
1127
|
+
save token
|
1128
|
+
token = get :pattern
|
1129
|
+
unless token[:asi] || token[:value] == ?; || token[:name] == 12 || token[:value] == ?}
|
1130
|
+
# Only identifiers may follow a `continue` statement.
|
1131
|
+
token = parse_assignment_expressions token, :single => true, :identifier => true
|
1132
|
+
unless token[:asi] || token[:value] == ?; || token[:name] == 12 || token[:value] == ?}
|
1133
|
+
token = warn "The argument to a `continue` statement must be an identifier that references an existing label.", token
|
1134
|
+
end
|
1135
|
+
end
|
1136
|
+
parse_semicolon token
|
1137
|
+
end
|
1138
|
+
|
1139
|
+
# Internal.
|
1140
|
+
def parse_break_statement(token)
|
1141
|
+
save token
|
1142
|
+
token = get :pattern
|
1143
|
+
unless token[:asi] || token[:value] == ?; || token[:name] == 12 || token[:value] == ?}
|
1144
|
+
token = parse_assignment_expressions token, :single => true, :identifier => true
|
1145
|
+
unless token[:asi] || token[:value] == ?; || token[:name] == 12 || token[:value] == ?}
|
1146
|
+
token = warn "The argument to a `break` statement must be an identifier that references an existing label.", token
|
1147
|
+
end
|
1148
|
+
end
|
1149
|
+
parse_semicolon token
|
1150
|
+
end
|
1151
|
+
|
1152
|
+
# Internal.
|
1153
|
+
def parse_return_statement(token)
|
1154
|
+
save token
|
1155
|
+
token = get :pattern
|
1156
|
+
unless token[:asi] || token[:value] == ?; || token[:name] == 12 || token[:value] == ?}
|
1157
|
+
token = parse_assignment_expressions token
|
1158
|
+
end
|
1159
|
+
parse_semicolon token
|
1160
|
+
end
|
1161
|
+
|
1162
|
+
# Internal.
|
1163
|
+
def parse_throw_statement(token)
|
1164
|
+
save token
|
1165
|
+
token = get :pattern
|
1166
|
+
# `throw` may not precede a line terminator, as it is a restricted
|
1167
|
+
# production for automatic semicolon insertion.
|
1168
|
+
if token[:asi]
|
1169
|
+
token = warn "A line terminator may not occur between a `throw` statement and its argument.", token
|
1170
|
+
end
|
1171
|
+
if token[:value] == ?;
|
1172
|
+
token = warn "The argument to `throw` is not optional.", token
|
1173
|
+
end
|
1174
|
+
token = parse_assignment_expressions token
|
1175
|
+
parse_semicolon token
|
1176
|
+
end
|
1177
|
+
|
1178
|
+
# Internal.
|
1179
|
+
def parse_switch_statement(token)
|
1180
|
+
save token
|
1181
|
+
token = get :pattern
|
1182
|
+
unless token[:value] == ?(
|
1183
|
+
token = warn "Expected `(`.", token
|
1184
|
+
end
|
1185
|
+
save token
|
1186
|
+
token = get :pattern
|
1187
|
+
unless token[:name] <= 6 || LHS_START.match(token[:value])
|
1188
|
+
fail "A `switch` statement header must contain at least one expression.", token
|
1189
|
+
end
|
1190
|
+
token = parse_assignment_expressions token
|
1191
|
+
unless token[:value] == ?)
|
1192
|
+
token = warn "Expected `)`.", token
|
1193
|
+
end
|
1194
|
+
save token
|
1195
|
+
token = get :pattern
|
1196
|
+
unless token[:value] == ?{
|
1197
|
+
token = warn "A `switch` block must begin with `{`.", token
|
1198
|
+
end
|
1199
|
+
save token
|
1200
|
+
token = get :pattern
|
1201
|
+
# A `default` case may occur only once in a `switch` block, but may
|
1202
|
+
# occur anywhere.
|
1203
|
+
has_cases, default = false, false
|
1204
|
+
while token[:value] == "case" || !default && (default = token[:value] == "default")
|
1205
|
+
has_cases = true
|
1206
|
+
token = parse_switch_clause token
|
1207
|
+
end
|
1208
|
+
unless has_cases || token[:value] == ?}
|
1209
|
+
token = warn "A `switch` block must begin with a `case` or `default` clause.", token
|
1210
|
+
end
|
1211
|
+
if default && token[:value] == "default"
|
1212
|
+
fail "`switch` blocks may not contain multiple `default` clauses.", token
|
1213
|
+
end
|
1214
|
+
unless token[:value] == ?} || token[:name] == 14
|
1215
|
+
token = warn "A `switch` block must end with `}`.", token
|
1216
|
+
end
|
1217
|
+
save token
|
1218
|
+
get :pattern
|
1219
|
+
end
|
1220
|
+
|
1221
|
+
# Internal.
|
1222
|
+
def parse_switch_clause(token)
|
1223
|
+
token = parse_switch_header token
|
1224
|
+
parse_switch_block token
|
1225
|
+
end
|
1226
|
+
|
1227
|
+
# Internal.
|
1228
|
+
def parse_switch_header(token)
|
1229
|
+
if token[:value] == "case"
|
1230
|
+
token = parse_switch_case token
|
1231
|
+
else
|
1232
|
+
token = parse_switch_default token
|
1233
|
+
end
|
1234
|
+
unless token[:value] == ?:
|
1235
|
+
token = warn "`switch` clauses must be followed by a colon.", token
|
1236
|
+
end
|
1237
|
+
save token
|
1238
|
+
get :pattern
|
1239
|
+
end
|
1240
|
+
|
1241
|
+
# Internal.
|
1242
|
+
def parse_switch_block(token)
|
1243
|
+
previous_token = nil
|
1244
|
+
# `switch` clauses may be empty.
|
1245
|
+
until %w( } case default).include?(token[:value]) || [14, 12].include?(token[:name]) || previous_token == token
|
1246
|
+
previous_token = token
|
1247
|
+
token = parse_statement token, :optional
|
1248
|
+
end
|
1249
|
+
if previous_token == token
|
1250
|
+
warn "Unexpected token in `switch` block.", token
|
1251
|
+
end
|
1252
|
+
token
|
1253
|
+
end
|
1254
|
+
|
1255
|
+
# Internal.
|
1256
|
+
def parse_switch_case(token)
|
1257
|
+
save token
|
1258
|
+
token = get :pattern
|
1259
|
+
if token[:value] == ?:
|
1260
|
+
fail "A `case` clause expects an expression as its argument.", token
|
1261
|
+
else
|
1262
|
+
token = parse_assignment_expressions token
|
1263
|
+
end
|
1264
|
+
token
|
1265
|
+
end
|
1266
|
+
|
1267
|
+
# Internal.
|
1268
|
+
def parse_switch_default(token)
|
1269
|
+
save token
|
1270
|
+
get :pattern
|
1271
|
+
end
|
1272
|
+
|
1273
|
+
# Public: Parses a `try...catch...finally` statement.
|
1274
|
+
# Returns the token immediately following the statement.
|
1275
|
+
#
|
1276
|
+
# token - The current token.
|
1277
|
+
def parse_try_statement(token)
|
1278
|
+
token = parse_try_block token
|
1279
|
+
token = parse_catch_block(token) if token[:value] == "catch"
|
1280
|
+
token = parse_finally_block(token) if token[:value] == "finally"
|
1281
|
+
unless @try
|
1282
|
+
fail "A `try` statement must be followed by either a `catch` block, a `finally` block, or both.", token
|
1283
|
+
end
|
1284
|
+
token
|
1285
|
+
ensure
|
1286
|
+
@try = false
|
1287
|
+
end
|
1288
|
+
|
1289
|
+
# Internal.
|
1290
|
+
def parse_try_block(token)
|
1291
|
+
save token
|
1292
|
+
token = get :pattern
|
1293
|
+
unless token[:value] == ?{
|
1294
|
+
token = warn "A `try` block must begin with `{`."
|
1295
|
+
end
|
1296
|
+
save token
|
1297
|
+
token = get :pattern
|
1298
|
+
token = parse_statements(token) unless token[:value] == ?}
|
1299
|
+
unless token[:value] == ?}
|
1300
|
+
token = warn "A `try` block must end with `}`.", token
|
1301
|
+
end
|
1302
|
+
save token
|
1303
|
+
get :pattern
|
1304
|
+
end
|
1305
|
+
|
1306
|
+
# Internal.
|
1307
|
+
def parse_catch_block(token)
|
1308
|
+
@try = true
|
1309
|
+
save token
|
1310
|
+
token = get :pattern
|
1311
|
+
unless token[:value] == ?(
|
1312
|
+
token = warn "The `catch` block header must accept a single argument.", token
|
1313
|
+
end
|
1314
|
+
save token
|
1315
|
+
token = get :pattern
|
1316
|
+
if token[:name] != 2
|
1317
|
+
token = warn "The `catch` block argument must be an identifier.", token
|
1318
|
+
elsif KEYWORD_OR_RESERVED.include?(token[:value])
|
1319
|
+
fail "The `catch` block argument may not be a keyword or future reserved word.", token
|
1320
|
+
end
|
1321
|
+
save token
|
1322
|
+
token = get :pattern
|
1323
|
+
unless token[:value] == ?)
|
1324
|
+
token = warn "Expected `)`.", token
|
1325
|
+
end
|
1326
|
+
save token
|
1327
|
+
token = get :pattern
|
1328
|
+
unless token[:value] == ?{
|
1329
|
+
token = warn "A `catch` block must begin with `{`.", token
|
1330
|
+
end
|
1331
|
+
save token
|
1332
|
+
token = get :pattern
|
1333
|
+
# Statements inside the `catch` block are optional.
|
1334
|
+
token = parse_statements(token) unless token[:value] == ?}
|
1335
|
+
unless token[:value] == ?}
|
1336
|
+
token = warn "A `catch` block must end with `}`.", token
|
1337
|
+
end
|
1338
|
+
save token
|
1339
|
+
get :pattern
|
1340
|
+
end
|
1341
|
+
|
1342
|
+
# Internal: Parses a `finally` clause of a `try` block.
|
1343
|
+
#
|
1344
|
+
# token - The current token.
|
1345
|
+
#
|
1346
|
+
# Returns the token immediately following the block.
|
1347
|
+
def parse_finally_block(token)
|
1348
|
+
@try = true
|
1349
|
+
save token
|
1350
|
+
token = get :pattern
|
1351
|
+
unless token[:value] == ?{
|
1352
|
+
token = warn "A `finally` block must begin with `{`.", token
|
1353
|
+
end
|
1354
|
+
save token
|
1355
|
+
token = get :pattern
|
1356
|
+
# A `finally` block can be empty.
|
1357
|
+
token = parse_statements(token) unless token[:value] == ?}
|
1358
|
+
unless token[:value] == ?}
|
1359
|
+
token = warn "A `finally` block must end with `}`.", token
|
1360
|
+
end
|
1361
|
+
save token
|
1362
|
+
get :pattern
|
1363
|
+
end
|
1364
|
+
|
1365
|
+
# Internal: Parses a `debugger` statement.
|
1366
|
+
#
|
1367
|
+
# token - The current token.
|
1368
|
+
#
|
1369
|
+
# Returns the token immediately following the statement.
|
1370
|
+
def parse_debugger_statement(token)
|
1371
|
+
save token
|
1372
|
+
token = get :pattern
|
1373
|
+
parse_semicolon token
|
1374
|
+
end
|
1375
|
+
|
1376
|
+
# Internal: Parses a `with` statement.
|
1377
|
+
#
|
1378
|
+
# token - The current token.
|
1379
|
+
#
|
1380
|
+
# Returns the token immediately following the statement.
|
1381
|
+
def parse_with_statement(token)
|
1382
|
+
save token
|
1383
|
+
token = get :pattern
|
1384
|
+
unless token[:value] == ?(
|
1385
|
+
token = warn "The `with` statement must begin with `{`.", token
|
1386
|
+
end
|
1387
|
+
save token
|
1388
|
+
token = get :pattern
|
1389
|
+
# The statement header must contain a left-hand side start value.
|
1390
|
+
unless token[:name] <= 6 || LHS_START.match(token[:value])
|
1391
|
+
token = warn "The `with` statement header cannot be empty.", token
|
1392
|
+
end
|
1393
|
+
token = parse_assignment_expressions token
|
1394
|
+
unless token[:value] == ?)
|
1395
|
+
token = warn "The `with` statement must end with `}`.", token
|
1396
|
+
end
|
1397
|
+
save token
|
1398
|
+
token = get :pattern
|
1399
|
+
parse_statement token
|
1400
|
+
end
|
1401
|
+
|
1402
|
+
# Internal: Emits a warning.
|
1403
|
+
#
|
1404
|
+
# message - The warning message.
|
1405
|
+
# token - The malformed token.
|
1406
|
+
#
|
1407
|
+
# Returns the warning `Token`.
|
1408
|
+
def warn(message, token)
|
1409
|
+
message = ParserError.new(message, token)
|
1410
|
+
# If a malformed token was previously encountered, insert it into the
|
1411
|
+
# token stream at the current position.
|
1412
|
+
save(@exception) if @exception
|
1413
|
+
# Store the current malformed token.
|
1414
|
+
@exception = token
|
1415
|
+
# Produce an error token at the current position and add it to the
|
1416
|
+
# lexer's token stream.
|
1417
|
+
error = Token.new(lexer, :error, token[:start]...token[:start])
|
1418
|
+
error[:error] = message
|
1419
|
+
lexer.insert_before(error, token)
|
1420
|
+
error
|
1421
|
+
end
|
1422
|
+
|
1423
|
+
# Internal: Emits an error.
|
1424
|
+
#
|
1425
|
+
# message - The warning message.
|
1426
|
+
# token - The malformed token.
|
1427
|
+
#
|
1428
|
+
# Returns nothing.
|
1429
|
+
def fail(message, token)
|
1430
|
+
message = ParserError.new(message, token)
|
1431
|
+
# Ignore the parse error. This will affect how the remainder of the
|
1432
|
+
# source is parsed.
|
1433
|
+
error = Token.new(lexer, :error, token[:start]...token[:start])
|
1434
|
+
error[:error] = message
|
1435
|
+
save error
|
1436
|
+
lexer.insert_before(error, token)
|
1437
|
+
nil
|
1438
|
+
end
|
1439
|
+
end
|
1440
|
+
end
|