1 if (typeof exports !== 'undefined') {
\r
2 var Tokenizer = require('./Tokenizer').Tokenizer;
\r
3 exports.ZeParser = ZeParser;
\r
7 * This is my js Parser: Ze. It's actually the post-dev pre-cleanup version. Clearly.
\r
8 * Some optimizations have been applied :)
\r
9 * (c) Peter van der Zee, qfox.nl
\r
10 * @param {String} inp Input
\r
11 * @param {Tokenizer} tok
\r
12 * @param {Array} stack The tokens will be put in this array. If you're looking for the AST, this would be it :)
\r
14 function ZeParser(inp, tok, stack, simple){
\r
16 this.tokenizer = tok;
\r
18 this.stack.root = true;
\r
19 this.scope = stack.scope = [{value:'this', isDeclared:true, isEcma:true, thisIsGlobal:true}]; // names of variables
\r
20 this.scope.global = true;
\r
21 this.statementLabels = [];
\r
23 this.errorStack = [];
\r
25 stack.scope = this.scope; // hook root
\r
26 stack.labels = this.statementLabels;
\r
28 this.regexLhsStart = ZeParser.regexLhsStart;
\r
30 this.regexStartKeyword = ZeParser.regexStartKeyword;
\r
31 this.regexKeyword = ZeParser.regexKeyword;
\r
32 this.regexStartReserved = ZeParser.regexStartReserved;
\r
33 this.regexReserved = ZeParser.regexReserved;
\r
35 this.regexStartKeyOrReserved = ZeParser.regexStartKeyOrReserved;
\r
36 this.hashStartKeyOrReserved = ZeParser.hashStartKeyOrReserved;
\r
37 this.regexIsKeywordOrReserved = ZeParser.regexIsKeywordOrReserved;
\r
38 this.regexAssignments = ZeParser.regexAssignments;
\r
39 this.regexNonAssignmentBinaryExpressionOperators = ZeParser.regexNonAssignmentBinaryExpressionOperators;
\r
40 this.regexUnaryKeywords = ZeParser.regexUnaryKeywords;
\r
41 this.hashUnaryKeywordStart = ZeParser.hashUnaryKeywordStart;
\r
42 this.regexUnaryOperators = ZeParser.regexUnaryOperators;
\r
43 this.regexLiteralKeywords = ZeParser.regexLiteralKeywords;
\r
44 this.testing = {'this':1,'null':1,'true':1,'false':1};
\r
46 this.ast = !simple; ///#define FULL_AST
\r
49 * Returns just a stacked parse tree (regular array)
\r
50 * @param {string} input
\r
51 * @param {boolean} simple=false
\r
54 ZeParser.parse = function(input, simple){
\r
55 var tok = new Tokenizer(input);
\r
58 var parser = new ZeParser(input, tok, stack);
\r
59 if (simple) parser.ast = false;
\r
63 console.log("Parser has a bug for this input, please report it :)", e);
\r
68 * Returns a new parser instance with parse details for input
\r
69 * @param {string} input
\r
70 * @returns {ZeParser}
\r
72 ZeParser.createParser = function(input){
\r
73 var tok = new Tokenizer(input);
\r
76 var parser = new ZeParser(input, tok, stack);
\r
80 console.log("Parser has a bug for this input, please report it :)", e);
\r
84 ZeParser.prototype = {
\r
89 statementLabels: null,
\r
94 parse: function(match){
\r
95 if (match) match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, this.stack); // meh
\r
96 else match = this.tokenizer.storeCurrentAndFetchNextToken(false, null, this.stack, true); // initialization step, dont store the match (there isnt any!)
\r
98 match = this.eatSourceElements(match, this.stack);
\r
100 var cycled = false;
\r
102 if (match && match.name != 12/*eof*/) {
\r
103 // if not already an error, insert an error before it
\r
104 if (match.name != 14/*error*/) this.failignore('UnexpectedToken', match, this.stack);
\r
105 // just parse the token as is and continue.
\r
106 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, this.stack);
\r
110 // keep gobbling any errors...
\r
111 } while (match && match.name == 14/*error*/);
\r
113 // now try again (but only if we gobbled at least one token)...
\r
114 if (cycled && match && match.name != 12/*eof*/) match = this.parse(match);
\r
116 // pop the last token off the stack if it caused an error at eof
\r
117 if (this.tokenizer.errorEscape) {
\r
118 this.stack.push(this.tokenizer.errorEscape);
\r
119 this.tokenizer.errorEscape = null;
\r
125 eatSemiColon: function(match, stack){
\r
126 //this.stats.eatSemiColon = (+//this.stats.eatSemiColon||0)+1;
\r
127 if (match.value == ';') match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
131 // - this token was preceeded by at least one newline (match.newline) or next token is }
\r
133 // - prev token was one of return,continue,break,throw (restricted production), not checked here.
\r
135 // the exceptions to this rule are
\r
136 // - if the next line is a regex
\r
137 // - the semi is part of the for-header.
\r
138 // these exceptions are automatically caught by the way the parser is built
\r
140 // not eof and just parsed semi or no newline preceeding and next isnt }
\r
141 if (match.name != 12/*EOF*/ && (match.semi || (!match.newline && match.value != '}')) && !(match.newline && (match.value == '++' || match.value == '--'))) {
\r
142 this.failignore('NoASI', match, stack);
\r
145 // (match is actually the match _after_ this asi, so the position of asi is match.start, not stop (!)
\r
146 var asi = {start:match.start,stop:match.start,name:13/*ASI*/};
\r
149 // slip it in the stream, before the current match.
\r
150 // for the other tokens see the tokenizer near the end of the main parsing function
\r
151 this.tokenizer.addTokenToStreamBefore(asi, match);
\r
158 * Eat one or more "AssignmentExpression"s. May also eat a labeled statement if
\r
159 * the parameters are set that way. This is the only way to linearly distinct between
\r
160 * an expression-statement and a labeled-statement without double lookahead. (ok, maybe not "only")
\r
161 * @param {boolean} mayParseLabeledStatementInstead=false If the first token is an identifier and the second a colon, accept this match as a labeled statement instead... Only true if the match in the parameter is an (unreserved) identifier (so no need to validate that further)
\r
162 * @param {Object} match
\r
163 * @param {Array} stack
\r
164 * @param {boolean} onlyOne=false Only parse a AssignmentExpression
\r
165 * @param {boolean} forHeader=false Do not allow the `in` operator
\r
166 * @param {boolean} isBreakOrContinueArg=false The argument for break or continue is always a single identifier
\r
169 eatExpressions: function(mayParseLabeledStatementInstead, match, stack, onlyOne, forHeader, isBreakOrContinueArg){
\r
170 if (this.ast) { //#ifdef FULL_AST
\r
171 var pstack = stack;
\r
173 stack.desc = 'expressions';
\r
174 stack.nextBlack = match.tokposb;
\r
175 pstack.push(stack);
\r
177 var parsedExpressions = 0;
\r
182 var parsedNonAssignmentOperator = false; // once we parse a non-assignment, this expression can no longer parse an assignment
\r
183 // TOFIX: can probably get the regex out somehow...
\r
185 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
186 if (!(/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value))) match = this.failsafe('ExpectedAnotherExpressionComma', match);
\r
189 if (this.ast) { //#ifdef FULL_AST
\r
190 ++parsedExpressions;
\r
192 var astack = stack;
\r
194 stack.desc = 'expression';
\r
195 stack.nextBlack = match.tokposb;
\r
196 astack.push(stack);
\r
199 // start of expression is given: match
\r
200 // it should indeed be a properly allowed lhs
\r
201 // first eat all unary operators
\r
202 // they can be added to the stack, but we need to ensure they have indeed a valid operator
\r
204 var parseAnotherExpression = true;
\r
205 while (parseAnotherExpression) { // keep parsing lhs+operator as long as there is an operator after the lhs.
\r
206 if (this.ast) { //#ifdef FULL_AST
\r
207 var estack = stack;
\r
209 stack.desc = 'sub-expression';
\r
210 stack.nextBlack = match.tokposb;
\r
211 estack.push(stack);
\r
213 var news = 0; // encountered new operators waiting for parenthesis
\r
216 // start checking lhs
\r
217 // if lhs is identifier (new/call expression), allow to parse an assignment operator next
\r
218 // otherwise keep eating unary expressions and then any "value"
\r
219 // after that search for a binary operator. if we only ate a new/call expression then
\r
220 // also allow to eat assignments. repeat for the rhs.
\r
221 var parsedUnaryOperator = false;
\r
222 var isUnary = null;
\r
224 !isBreakOrContinueArg && // no unary for break/continue
\r
226 (match.value && this.hashUnaryKeywordStart[match.value[0]] && this.regexUnaryKeywords.test(match.value)) || // (match.value == 'delete' || match.value == 'void' || match.value == 'typeof' || match.value == 'new') ||
\r
227 (match.name == 11/*PUNCTUATOR*/ && this.regexUnaryOperators.test(match.value))
\r
230 if (isUnary) match.isUnaryOp = true;
\r
231 if (this.ast) { //#ifdef FULL_AST
\r
232 // find parenthesis
\r
233 if (match.value == 'new') ++news;
\r
236 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
237 // ensure that it is in fact a valid lhs-start
\r
238 if (!(/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value))) match = this.failsafe('ExpectedAnotherExpressionRhs', match);
\r
239 // not allowed to parse assignment
\r
240 parsedUnaryOperator = true;
\r
243 // if we parsed any kind of unary operator, we cannot be parsing a labeled statement
\r
244 if (parsedUnaryOperator) mayParseLabeledStatementInstead = false;
\r
246 // so now we know match is a valid lhs-start and not a unary operator
\r
247 // it must be a string, number, regex, identifier
\r
248 // or the start of an object literal ({), array literal ([) or group operator (().
\r
250 var acceptAssignment = false;
\r
252 // take care of the "open" cases first (group, array, object)
\r
253 if (match.value == '(') {
\r
254 if (this.ast) { //#ifdef FULL_AST
\r
255 var groupStack = stack;
\r
257 stack.desc = 'grouped';
\r
258 stack.nextBlack = match.tokposb;
\r
259 groupStack.push(stack);
\r
263 match.isGroupStart = true;
\r
265 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
266 if (!(/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value))) match = this.failsafe('GroupingShouldStartWithExpression', match);
\r
267 // keep parsing expressions as long as they are followed by a comma
\r
268 match = this.eatExpressions(false, match, stack);
\r
270 if (match.value != ')') match = this.failsafe('UnclosedGroupingOperator', match);
\r
271 if (this.ast) { //#ifdef FULL_AST
\r
275 match.isGroupStop = true;
\r
277 if (stack[stack.length-1].desc == 'expressions') {
\r
278 // create ref to this expression group to the opening paren
\r
279 lhp.expressionArg = stack[stack.length-1];
\r
282 match = this.tokenizer.storeCurrentAndFetchNextToken(true, match, stack); // might be div
\r
284 if (this.ast) { //#ifdef FULL_AST
\r
285 stack = groupStack;
\r
287 // you can assign to group results. and as long as the group does not contain a comma (and valid ref), it will work too :)
\r
288 acceptAssignment = true;
\r
289 // there's an extra rule for [ namely that, it must start with an expression but after that, expressions are optional
\r
290 } else if (match.value == '[') {
\r
291 if (this.ast) { //#ifdef FULL_AST
\r
292 stack.sub = 'array literal';
\r
293 stack.hasArrayLiteral = true;
\r
296 match.isArrayLiteralStart = true;
\r
298 if (!this.scope.arrays) this.scope.arrays = [];
\r
299 match.arrayId = this.scope.arrays.length;
\r
300 this.scope.arrays.push(match);
\r
302 match.targetScope = this.scope;
\r
304 // keep parsing expressions as long as they are followed by a comma
\r
305 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
307 // arrays may start with "elided" commas
\r
308 while (match.value == ',') match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
310 var foundAtLeastOneComma = true; // for entry in while
\r
311 while (foundAtLeastOneComma && match.value != ']') {
\r
312 foundAtLeastOneComma = false;
\r
314 if (!(/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value)) && match.name != 14/*error*/) match = this.failsafe('ArrayShouldStartWithExpression', match);
\r
315 match = this.eatExpressions(false, match, stack, true);
\r
317 while (match.value == ',') {
\r
318 foundAtLeastOneComma = true;
\r
319 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
322 if (match.value != ']') {
\r
323 match = this.failsafe('UnclosedPropertyBracket', match);
\r
325 if (this.ast) { //#ifdef FULL_AST
\r
329 match.isArrayLiteralStop = true;
\r
331 match = this.tokenizer.storeCurrentAndFetchNextToken(true, match, stack); // might be div
\r
332 while (match.value == '++' || match.value == '--') {
\r
333 // gobble and ignore?
\r
334 this.failignore('InvalidPostfixOperandArray', match, stack);
\r
335 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
337 // object literals need seperate handling...
\r
338 } else if (match.value == '{') {
\r
339 if (this.ast) { //#ifdef FULL_AST
\r
340 stack.sub = 'object literal';
\r
341 stack.hasObjectLiteral = true;
\r
343 match.isObjectLiteralStart = true;
\r
345 if (!this.scope.objects) this.scope.objects = [];
\r
346 match.objectId = this.scope.objects.length;
\r
347 this.scope.objects.push(match);
\r
349 var targetObject = match;
\r
350 match.targetScope = this.scope;
\r
355 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
356 if (match.name == 12/*eof*/) {
\r
357 match = this.failsafe('ObjectLiteralExpectsColonAfterName', match);
\r
360 // PropertyNameAndValueList
\r
362 while (match.value != '}' && match.name != 14/*error*/) { // will stop if next token is } or throw if not and no comma is found
\r
363 // expecting a string, number, or identifier
\r
364 //if (match.name != 5/*STRING_SINGLE*/ && match.name != 6/*STRING_DOUBLE*/ && match.name != 3/*NUMERIC_HEX*/ && match.name != 4/*NUMERIC_DEC*/ && match.name != 2/*IDENTIFIER*/) {
\r
365 // TOFIX: more specific errors depending on type...
\r
366 if (!match.isNumber && !match.isString && match.name != 2/*IDENTIFIER*/) {
\r
367 match = this.failsafe('IllegalPropertyNameToken', match);
\r
370 if (this.ast) { //#ifdef FULL_AST
\r
371 var objLitStack = stack;
\r
373 stack.desc = 'objlit pair';
\r
374 stack.isObjectLiteralPair = true;
\r
375 stack.nextBlack = match.tokposb;
\r
376 objLitStack.push(stack);
\r
378 var propNameStack = stack;
\r
380 stack.desc = 'objlit pair name';
\r
381 stack.nextBlack = match.tokposb;
\r
382 propNameStack.push(stack);
\r
384 propNameStack.sub = 'data';
\r
386 var propName = match;
\r
387 propName.isPropertyName = true;
\r
390 var getset = match.value;
\r
391 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
392 if (this.ast) { //#ifdef FULL_AST
\r
393 stack = propNameStack;
\r
396 // for get/set we parse a function-like definition. but only if it's immediately followed by an identifier (otherwise it'll just be the property 'get' or 'set')
\r
397 if (getset == 'get') {
\r
398 // "get" PropertyName "(" ")" "{" FunctionBody "}"
\r
399 if (match.value == ':') {
\r
400 if (this.ast) { //#ifdef FULL_AST
\r
401 propName.isPropertyOf = targetObject;
\r
403 match = this.eatObjectLiteralColonAndBody(match, stack);
\r
405 if (this.ast) { //#ifdef FULL_AST
\r
406 match.isPropertyOf = targetObject;
\r
407 propNameStack.sub = 'getter';
\r
408 propNameStack.isAccessor = true;
\r
410 // if (match.name != 2/*IDENTIFIER*/ && match.name != 5/*STRING_SINGLE*/ && match.name != 6/*STRING_DOUBLE*/ && match.name != 3/*NUMERIC_HEX*/ && match.name != 4/*NUMERIC_DEC*/) {
\r
411 if (!match.isNumber && !match.isString && match.name != 2/*IDENTIFIER*/) match = this.failsafe('IllegalGetterSetterNameToken', match, true);
\r
412 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
413 if (match.value != '(') match = this.failsafe('GetterSetterNameFollowedByOpenParen', match);
\r
414 if (this.ast) { //#ifdef FULL_AST
\r
417 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
418 if (match.value != ')') match = this.failsafe('GetterHasNoArguments', match);
\r
419 if (this.ast) { //#ifdef FULL_AST
\r
423 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
424 match = this.eatFunctionBody(match, stack);
\r
426 } else if (getset == 'set') {
\r
427 // "set" PropertyName "(" PropertySetParameterList ")" "{" FunctionBody "}"
\r
428 if (match.value == ':') {
\r
429 if (this.ast) { //#ifdef FULL_AST
\r
430 propName.isPropertyOf = targetObject;
\r
432 match = this.eatObjectLiteralColonAndBody(match, stack);
\r
434 if (this.ast) { //#ifdef FULL_AST
\r
435 match.isPropertyOf = targetObject;
\r
436 propNameStack.sub = 'setter';
\r
437 propNameStack.isAccessor = true;
\r
439 if (!match.isNumber && !match.isString && match.name != 2/*IDENTIFIER*/) match = this.failsafe('IllegalGetterSetterNameToken', match);
\r
440 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
441 if (match.value != '(') match = this.failsafe('GetterSetterNameFollowedByOpenParen', match);
\r
442 if (this.ast) { //#ifdef FULL_AST
\r
445 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
446 if (match.name != 2/*IDENTIFIER*/) {
\r
447 if (match.value == ')') match = this.failsafe('SettersMustHaveArgument', match);
\r
448 else match = this.failsafe('IllegalSetterArgumentNameToken', match);
\r
450 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
451 if (match.value != ')') {
\r
452 if (match.value == ',') match = this.failsafe('SettersOnlyGetOneArgument', match);
\r
453 else match = this.failsafe('SetterHeaderShouldHaveClosingParen', match);
\r
455 if (this.ast) { //#ifdef FULL_AST
\r
459 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
460 match = this.eatFunctionBody(match, stack);
\r
463 // PropertyName ":" AssignmentExpression
\r
464 if (this.ast) { //#ifdef FULL_AST
\r
465 propName.isPropertyOf = targetObject;
\r
467 match = this.eatObjectLiteralColonAndBody(match, stack);
\r
470 if (this.ast) { //#ifdef FULL_AST
\r
471 stack = objLitStack;
\r
474 // one trailing comma allowed
\r
475 if (match.value == ',') {
\r
476 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
477 if (match.value == ',') match = this.failsafe('IllegalDoubleCommaInObjectLiteral', match);
\r
478 } else if (match.value != '}') match = this.failsafe('UnclosedObjectLiteral', match);
\r
480 // either the next token is } and the loop breaks or
\r
481 // the next token is the start of the next PropertyAssignment...
\r
484 if (this.ast) { //#ifdef FULL_AST
\r
488 match.isObjectLiteralStop = true;
\r
491 match = this.tokenizer.storeCurrentAndFetchNextToken(true, match, stack); // next may be div
\r
492 while (match.value == '++' || match.value == '--') {
\r
493 this.failignore('InvalidPostfixOperandObject', match, stack);
\r
494 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
496 } else if (match.value == 'function') { // function expression
\r
497 if (this.ast) { //#ifdef FULL_AST
\r
498 var oldstack = stack;
\r
500 stack.desc = 'func expr';
\r
501 stack.isFunction = true;
\r
502 stack.nextBlack = match.tokposb;
\r
503 if (!this.scope.functions) this.scope.functions = [];
\r
504 match.functionId = this.scope.functions.length;
\r
505 this.scope.functions.push(match);
\r
506 oldstack.push(stack);
\r
507 var oldscope = this.scope;
\r
509 match.scope = stack.scope = this.scope = [
\r
511 {value:'this', isDeclared:true, isEcma:true, functionStack: stack},
\r
512 {value:'arguments', isDeclared:true, isEcma:true, varType:['Object']}
\r
513 ]; // add the current scope (to build chain up-down)
\r
514 this.scope.upper = oldscope;
\r
515 // ref to back to function that's the cause for this scope
\r
516 this.scope.scopeFor = match;
\r
517 match.targetScope = oldscope; // consistency
\r
518 match.isFuncExprKeyword = true;
\r
519 match.functionStack = stack;
\r
521 var funcExprToken = match;
\r
523 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
524 if (mayParseLabeledStatementInstead && match.value == ':') match = this.failsafe('LabelsMayNotBeReserved', match);
\r
525 if (match.name == 2/*IDENTIFIER*/) {
\r
526 funcExprToken.funcName = match;
\r
527 match.meta = "func expr name";
\r
528 match.varType = ['Function'];
\r
529 match.functionStack = stack; // ref to the stack, in case we detect the var being a constructor
\r
530 if (this.ast) { //#ifdef FULL_AST
\r
531 // name is only available to inner scope
\r
532 this.scope.push({value:match.value});
\r
534 if (this.hashStartKeyOrReserved[match.value[0]] /*this.regexStartKeyOrReserved.test(match.value[0])*/ && this.regexIsKeywordOrReserved.test(match.value)) match = this.failsafe('FunctionNameMustNotBeReserved', match);
\r
535 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
537 match = this.eatFunctionParametersAndBody(match, stack, true, funcExprToken); // first token after func-expr is div
\r
539 while (match.value == '++' || match.value == '--') {
\r
540 this.failignore('InvalidPostfixOperandFunction', match, stack);
\r
541 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
544 if (this.ast) { //#ifdef FULL_AST
\r
545 // restore stack and scope
\r
547 this.scope = oldscope;
\r
549 } else if (match.name <= 6) { // IDENTIFIER STRING_SINGLE STRING_DOUBLE NUMERIC_HEX NUMERIC_DEC REG_EX
\r
550 // save it in case it turns out to be a label.
\r
551 var possibleLabel = match;
\r
553 // validate the identifier, if any
\r
554 if (match.name == 2/*IDENTIFIER*/) {
\r
556 // this, null, true, false are actually allowed here
\r
557 !this.regexLiteralKeywords.test(match.value) &&
\r
558 // other reserved words are not
\r
559 this.hashStartKeyOrReserved[match.value[0]] /*this.regexStartKeyOrReserved.test(match.value[0])*/ && this.regexIsKeywordOrReserved.test(match.value)
\r
561 // if break/continue, we skipped the unary operator check so throw the proper error here
\r
562 if (isBreakOrContinueArg) {
\r
563 this.failignore('BreakOrContinueArgMustBeJustIdentifier', match, stack);
\r
564 } else if (match.value == 'else') {
\r
565 this.failignore('DidNotExpectElseHere', match, stack);
\r
567 //if (mayParseLabeledStatementInstead) {new ZeParser.Error('LabelsMayNotBeReserved', match);
\r
568 // TOFIX: lookahead to see if colon is following. throw label error instead if that's the case
\r
569 // any forbidden keyword at this point is likely to be a statement start.
\r
570 // its likely that the parser will take a while to recover from this point...
\r
571 this.failignore('UnexpectedToken', match, stack);
\r
572 // TOFIX: maybe i should just return at this point. cut my losses and hope for the best.
\r
576 // only accept assignments after a member expression (identifier or ending with a [] suffix)
\r
577 acceptAssignment = true;
\r
578 } else if (isBreakOrContinueArg) match = this.failsafe('BreakOrContinueArgMustBeJustIdentifier', match);
\r
580 // the current match is the lead value being queried. tag it that way
\r
581 if (this.ast) { //#ifdef FULL_AST
\r
582 // dont mark labels
\r
583 if (!isBreakOrContinueArg) {
\r
584 match.meta = 'lead value';
\r
585 match.leadValue = true;
\r
591 match = this.tokenizer.storeCurrentAndFetchNextToken(true, match, stack); // division allowed
\r
593 // now check for labeled statement (if mayParseLabeledStatementInstead then the first token for this expression must be an (unreserved) identifier)
\r
594 if (mayParseLabeledStatementInstead && match.value == ':') {
\r
595 if (possibleLabel.name != 2/*IDENTIFIER*/) {
\r
596 // label was not an identifier
\r
597 // TOFIX: this colon might be a different type of error... more analysis required
\r
598 this.failignore('LabelsMayOnlyBeIdentifiers', match, stack);
\r
601 mayParseLabeledStatementInstead = true; // mark label parsed (TOFIX:speed?)
\r
602 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
604 possibleLabel.isLabel = true;
\r
605 if (this.ast) { //#ifdef FULL_AST
\r
606 delete possibleLabel.meta; // oh oops, it's not a lead value.
\r
608 possibleLabel.isLabelDeclaration = true;
\r
609 this.statementLabels.push(possibleLabel.value);
\r
611 stack.desc = 'labeled statement';
\r
614 var errorIdToReplace = this.errorStack.length;
\r
615 // eat another statement now, its the body of the labeled statement (like if and while)
\r
616 match = this.eatStatement(false, match, stack);
\r
618 // if no statement was found, check here now and correct error
\r
619 if (match.error && match.error.msg == ZeParser.Errors.UnableToParseStatement.msg) {
\r
620 // replace with better error...
\r
621 match.error = new ZeParser.Error('LabelRequiresStatement');
\r
622 // also replace on stack
\r
623 this.errorStack[errorIdToReplace] = match.error;
\r
626 match.wasLabel = true;
\r
631 mayParseLabeledStatementInstead = false;
\r
632 } else if (match.value == '}') {
\r
633 // ignore... its certainly the end of this expression, but maybe asi can be applied...
\r
634 // it might also be an object literal expecting more, but that case has been covered else where.
\r
635 // if it turns out the } is bad after all, .parse() will try to recover
\r
636 } else if (match.name == 14/*error*/) {
\r
638 if (match.tokenError) {
\r
639 var pe = new ZeParser.Error('TokenizerError', match);
\r
640 pe.msg += ': '+match.error.msg;
\r
641 this.errorStack.push(pe);
\r
643 this.failSpecial({start:match.start,stop:match.start,name:14/*error*/,error:pe}, match, stack)
\r
645 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
646 } while (match.name == 14/*error*/);
\r
647 } else if (match.name == 12/*eof*/) {
\r
648 // cant parse any further. you're probably just typing...
\r
651 //if (!this.errorStack.length && match.name != 12/*eof*/) console.log(["unknown token", match, stack, Gui.escape(this.input)]);
\r
652 this.failignore('UnknownToken', match, stack);
\r
653 // we cant really ignore this. eat the token and try again. possibly you're just typing?
\r
654 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
657 // search for "value" suffix. property access and call parens.
\r
658 while (match.value == '.' || match.value == '[' || match.value == '(') {
\r
659 if (isBreakOrContinueArg) match = this.failsafe('BreakOrContinueArgMustBeJustIdentifier', match);
\r
661 if (match.value == '.') {
\r
662 // property access. read in an IdentifierName (no keyword checks). allow assignments
\r
663 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
664 if (match.name != 2/*IDENTIFIER*/) this.failignore('PropertyNamesMayOnlyBeIdentifiers', match, stack);
\r
665 if (this.ast) { //#ifdef FULL_AST
\r
666 match.isPropertyName = true;
\r
668 match = this.tokenizer.storeCurrentAndFetchNextToken(true, match, stack); // may parse div
\r
669 acceptAssignment = true;
\r
670 } else if (match.value == '[') {
\r
671 if (this.ast) { //#ifdef FULL_AST
\r
673 match.propertyAccessStart = true;
\r
675 // property access, read expression list. allow assignments
\r
676 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
677 if (!(/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value))) {
\r
678 if (match.value == ']') match = this.failsafe('SquareBracketsMayNotBeEmpty', match);
\r
679 else match = this.failsafe('SquareBracketExpectsExpression', match);
\r
681 match = this.eatExpressions(false, match, stack);
\r
682 if (match.value != ']') match = this.failsafe('UnclosedSquareBrackets', match);
\r
683 if (this.ast) { //#ifdef FULL_AST
\r
685 match.propertyAccessStop = true;
\r
688 if (stack[stack.length-1].desc == 'expressions') {
\r
689 // create ref to this expression group to the opening bracket
\r
690 lhsb.expressionArg = stack[stack.length-1];
\r
693 match = this.tokenizer.storeCurrentAndFetchNextToken(true, match, stack); // might be div
\r
694 acceptAssignment = true;
\r
695 } else if (match.value == '(') {
\r
696 if (this.ast) { //#ifdef FULL_AST
\r
698 match.isCallExpressionStart = true;
\r
700 match.parensBelongToNew = true;
\r
704 // call expression, eat optional expression list, disallow assignments
\r
705 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
706 if (/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value)) match = this.eatExpressions(false, match, stack); // arguments are optional
\r
707 if (match.value != ')') match = this.failsafe('UnclosedCallParens', match);
\r
708 if (this.ast) { //#ifdef FULL_AST
\r
711 match.isCallExpressionStop = true;
\r
713 if (stack[stack.length-1].desc == 'expressions') {
\r
714 // create ref to this expression group to the opening bracket
\r
715 lhp.expressionArg = stack[stack.length-1];
\r
718 match = this.tokenizer.storeCurrentAndFetchNextToken(true, match, stack); // might be div
\r
719 acceptAssignment = false;
\r
723 // check for postfix operators ++ and --
\r
724 // they are stronger than the + or - binary operators
\r
725 // they can be applied to any lhs (even when it wouldnt make sense)
\r
726 // if there was a newline, it should get an ASI
\r
727 if ((match.value == '++' || match.value == '--') && !match.newline) {
\r
728 if (isBreakOrContinueArg) match = this.failsafe('BreakOrContinueArgMustBeJustIdentifier', match);
\r
729 match = this.tokenizer.storeCurrentAndFetchNextToken(true, match, stack); // may parse div
\r
732 if (this.ast) { //#ifdef FULL_AST
\r
733 // restore "expression" stack
\r
736 // now see if there is an operator following...
\r
738 do { // this do allows us to parse multiple ternary expressions in succession without screwing up.
\r
739 var ternary = false;
\r
741 (!forHeader && match.value == 'in') || // one of two named binary operators, may not be first expression in for-header (when semi's occur in the for-header)
\r
742 (match.value == 'instanceof') || // only other named binary operator
\r
743 ((match.name == 11/*PUNCTUATOR*/) && // we can only expect a punctuator now
\r
744 (match.isAssignment = this.regexAssignments.test(match.value)) || // assignments are only okay with proper lhs
\r
745 this.regexNonAssignmentBinaryExpressionOperators.test(match.value) // test all other binary operators
\r
748 if (match.isAssignment) {
\r
749 if (!acceptAssignment) this.failignore('IllegalLhsForAssignment', match, stack);
\r
750 else if (parsedNonAssignmentOperator) this.failignore('AssignmentNotAllowedAfterNonAssignmentInExpression', match, stack);
\r
752 if (isBreakOrContinueArg) match = this.failsafe('BreakOrContinueArgMustBeJustIdentifier', match);
\r
754 if (!match.isAssignment) parsedNonAssignmentOperator = true; // last allowed assignment
\r
755 if (this.ast) { //#ifdef FULL_AST
\r
756 match.isBinaryOperator = true;
\r
757 // we build a stack to ensure any whitespace doesnt break the 1+(n*2) children rule for expressions
\r
758 var ostack = stack;
\r
760 stack.desc = 'operator-expression';
\r
761 stack.isBinaryOperator = true;
\r
762 stack.sub = match.value;
\r
763 stack.nextBlack = match.tokposb;
\r
764 ostack.sub = match.value;
\r
765 stack.isAssignment = match.isAssignment;
\r
766 ostack.push(stack);
\r
768 ternary = match.value == '?';
\r
769 // math, logic, assignment or in or instanceof
\r
770 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
772 if (this.ast) { //#ifdef FULL_AST
\r
773 // restore "expression" stack
\r
777 // minor exception to ternary operator, we need to parse two expressions nao. leave the trailing expression to the loop.
\r
779 // LogicalORExpression "?" AssignmentExpression ":" AssignmentExpression
\r
780 // so that means just one expression center and right.
\r
781 if (!(/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value))) this.failignore('InvalidCenterTernaryExpression', match, stack);
\r
782 match = this.eatExpressions(false, match, stack, true, forHeader); // only one expression allowed inside ternary center/right
\r
784 if (match.value != ':') {
\r
785 if (match.value == ',') match = this.failsafe('TernarySecondExpressionCanNotContainComma', match);
\r
786 else match = this.failsafe('UnfinishedTernaryOperator', match);
\r
788 if (this.ast) { //#ifdef FULL_AST
\r
789 // we build a stack to ensure any whitespace doesnt break the 1+(n*2) children rule for expressions
\r
790 var ostack = stack;
\r
792 stack.desc = 'operator-expression';
\r
793 stack.sub = match.value;
\r
794 stack.nextBlack = match.tokposb;
\r
795 ostack.sub = match.value;
\r
796 stack.isAssignment = match.isAssignment;
\r
797 ostack.push(stack);
\r
799 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
800 if (this.ast) { //#ifdef FULL_AST
\r
803 // rhs of the ternary can not contain a comma either
\r
804 match = this.eatExpressions(false, match, stack, true, forHeader); // only one expression allowed inside ternary center/right
\r
807 parseAnotherExpression = false;
\r
809 } while (ternary); // if we just parsed a ternary expression, we need to check _again_ whether the next token is a binary operator.
\r
811 // start over. match is the rhs for the lhs we just parsed, but lhs for the next expression
\r
812 if (parseAnotherExpression && !(/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value))) {
\r
813 // no idea what to do now. lets just ignore and see where it ends. TOFIX: maybe just break the loop or return?
\r
814 this.failignore('InvalidRhsExpression', match, stack);
\r
818 if (this.ast) { //#ifdef FULL_AST
\r
819 // restore "expressions" stack
\r
823 // at this point we should have parsed one AssignmentExpression
\r
824 // lets see if we can parse another one...
\r
825 mayParseLabeledStatementInstead = first = false;
\r
826 } while (!onlyOne && match.value == ',');
\r
828 if (this.ast) { //#ifdef FULL_AST
\r
829 // remove empty array
\r
830 if (!stack.length) pstack.length = pstack.length-1;
\r
831 pstack.numberOfExpressions = parsedExpressions;
\r
832 if (pstack[0]) pstack[0].numberOfExpressions = parsedExpressions;
\r
833 stack.expressionCount = parsedExpressions;
\r
837 eatFunctionDeclaration: function(match, stack){
\r
838 if (this.ast) { //#ifdef FULL_AST
\r
839 stack.push(stack = []);
\r
840 var prevscope = this.scope;
\r
841 stack.desc = 'func decl';
\r
842 stack.isFunction = true;
\r
843 stack.nextBlack = match.tokposb;
\r
844 if (!this.scope.functions) this.scope.functions = [];
\r
845 match.functionId = this.scope.functions.length;
\r
846 this.scope.functions.push(match);
\r
848 match.scope = stack.scope = this.scope = [
\r
849 this.scope, // add current scope (build scope chain up-down)
\r
850 // Object.create(null,
\r
851 {value:'this', isDeclared:true, isEcma:true, functionStack:stack},
\r
852 // Object.create(null,
\r
853 {value:'arguments', isDeclared:true, isEcma:true, varType:['Object']}
\r
855 // ref to back to function that's the cause for this scope
\r
856 this.scope.scopeFor = match;
\r
857 match.targetScope = prevscope; // consistency
\r
859 match.functionStack = stack;
\r
861 match.isFuncDeclKeyword = true;
\r
863 // only place that this function is used already checks whether next token is function
\r
864 var functionKeyword = match;
\r
865 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
866 if (match.name != 2/*IDENTIFIER*/) match = this.failsafe('FunctionDeclarationsMustHaveName', match);
\r
867 if (this.hashStartKeyOrReserved[match.value[0]] /*this.regexStartKeyOrReserved.test(match.value[0])*/ && this.regexIsKeywordOrReserved.test(match.value)) this.failignore('FunctionNameMayNotBeReserved', match, stack);
\r
868 if (this.ast) { //#ifdef FULL_AST
\r
869 functionKeyword.funcName = match;
\r
870 prevscope.push({value:match.value});
\r
871 match.meta = 'func decl name'; // that's what it is, really
\r
872 match.varType = ['Function'];
\r
873 match.functionStack = stack;
\r
875 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
876 match = this.eatFunctionParametersAndBody(match, stack, false, functionKeyword); // first token after func-decl is regex
\r
877 if (this.ast) { //#ifdef FULL_AST
\r
878 // restore previous scope
\r
879 this.scope = prevscope;
\r
883 eatObjectLiteralColonAndBody: function(match, stack){
\r
884 if (this.ast) { //#ifdef FULL_AST
\r
885 var propValueStack = stack;
\r
887 stack.desc = 'objlit pair colon';
\r
888 stack.nextBlack = match.tokposb;
\r
889 propValueStack.push(stack);
\r
891 if (match.value != ':') match = this.failsafe('ObjectLiteralExpectsColonAfterName', match);
\r
892 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
893 if (this.ast) { //#ifdef FULL_AST
\r
894 stack = propValueStack;
\r
897 // this might actually fail due to ASI optimization.
\r
898 // if the property name does not exist and it is the last item
\r
899 // of the objlit, the expression parser will see an unexpected
\r
900 // } and ignore it, giving some leeway to apply ASI. of course,
\r
901 // that doesnt work for objlits. but we dont want to break the
\r
902 // existing mechanisms. so we check this differently... :)
\r
903 var prevMatch = match;
\r
904 match = this.eatExpressions(false, match, stack, true); // only one expression
\r
905 if (match == prevMatch) match = this.failsafe('ObjectLiteralMissingPropertyValue', match);
\r
909 eatFunctionParametersAndBody: function(match, stack, div, funcToken){
\r
910 // div: the first token _after_ a function expression may be a division...
\r
911 if (match.value != '(') match = this.failsafe('ExpectingFunctionHeaderStart', match);
\r
912 else if (this.ast) { //#ifdef FULL_AST
\r
914 funcToken.lhp = match;
\r
916 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
917 if (match.name == 2/*IDENTIFIER*/) { // params
\r
918 if (this.hashStartKeyOrReserved[match.value[0]] /*this.regexStartKeyOrReserved.test(match.value[0])*/ && this.regexIsKeywordOrReserved.test(match.value)) this.failignore('FunctionArgumentsCanNotBeReserved', match, stack);
\r
919 if (this.ast) { //#ifdef FULL_AST
\r
920 if (!funcToken.paramNames) funcToken.paramNames = [];
\r
921 stack.paramNames = funcToken.paramNames;
\r
922 funcToken.paramNames.push(match);
\r
923 this.scope.push({value:match.value}); // add param name to scope
\r
924 match.meta = 'parameter';
\r
926 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
927 while (match.value == ',') {
\r
928 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
929 if (match.name != 2/*IDENTIFIER*/) {
\r
930 // example: if name is 12, the source is incomplete...
\r
931 this.failignore('FunctionParametersMustBeIdentifiers', match, stack);
\r
932 } else if (this.hashStartKeyOrReserved[match.value[0]] /*this.regexStartKeyOrReserved.test(match.value[0])*/ && this.regexIsKeywordOrReserved.test(match.value)) {
\r
933 this.failignore('FunctionArgumentsCanNotBeReserved', match, stack);
\r
935 if (this.ast) { //#ifdef FULL_AST
\r
936 // Object.create(null,
\r
937 this.scope.push({value:match.value}); // add param name to scope
\r
938 match.meta = 'parameter';
\r
939 if (match.name == 2/*IDENTIFIER*/) funcToken.paramNames.push(match);
\r
941 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
944 if (this.ast) { //#ifdef FULL_AST
\r
948 funcToken.rhp = match;
\r
951 if (match.value != ')') match = this.failsafe('ExpectedFunctionHeaderClose', match); // TOFIX: can be various things here...
\r
952 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
953 match = this.eatFunctionBody(match, stack, div, funcToken);
\r
956 eatFunctionBody: function(match, stack, div, funcToken){
\r
957 if (this.ast) { //#ifdef FULL_AST
\r
958 stack.push(stack = []);
\r
959 stack.desc = 'func body';
\r
960 stack.nextBlack = match.tokposb;
\r
962 // create EMPTY list of functions. labels cannot cross function boundaries
\r
963 var labelBackup = this.statementLabels;
\r
964 this.statementLabels = [];
\r
965 stack.labels = this.statementLabels;
\r
968 // if div, a division can occur _after_ this function expression
\r
969 //this.stats.eatFunctionBody = (+//this.stats.eatFunctionBody||0)+1;
\r
970 if (match.value != '{') match = this.failsafe('ExpectedFunctionBodyCurlyOpen', match);
\r
971 if (this.ast) { //#ifdef FULL_AST
\r
973 if (funcToken) funcToken.lhc = lhc;
\r
975 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
976 match = this.eatSourceElements(match, stack);
\r
977 if (match.value != '}') match = this.failsafe('ExpectedFunctionBodyCurlyClose', match);
\r
978 if (this.ast) { //#ifdef FULL_AST
\r
981 if (funcToken) funcToken.rhc = match;
\r
983 match = this.tokenizer.storeCurrentAndFetchNextToken(div, match, stack);
\r
985 if (this.ast) { //#ifdef FULL_AST
\r
986 // restore label set
\r
987 this.statementLabels = labelBackup;
\r
992 eatVar: function(match, stack){
\r
993 if (this.ast) { //#ifdef FULL_AST
\r
994 stack.push(stack = []);
\r
995 stack.desc = 'statement';
\r
997 stack.nextBlack = match.tokposb;
\r
998 match.stack = stack;
\r
999 match.isVarKeyword = true;
\r
1001 match = this.eatVarDecl(match, stack);
\r
1002 match = this.eatSemiColon(match, stack);
\r
1006 eatVarDecl: function(match, stack, forHeader){
\r
1007 // assumes match is indeed the identifier 'var'
\r
1008 if (this.ast) { //#ifdef FULL_AST
\r
1009 stack.push(stack = []);
\r
1010 stack.desc = 'var decl';
\r
1011 stack.nextBlack = match.tokposb;
\r
1013 var targetScope = this.scope;
\r
1014 while (targetScope.catchScope) targetScope = targetScope[0];
\r
1017 var varsDeclared = 0;
\r
1020 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); // start: var, iteration: comma
\r
1021 if (this.ast) { //#ifdef FULL_AST
\r
1022 var declStack = stack;
\r
1024 stack.desc = 'single var decl';
\r
1025 stack.varStack = declStack; // reference to the var statement stack, it might hook to jsdoc needed for these vars
\r
1026 stack.nextBlack = match.tokposb;
\r
1027 declStack.push(stack);
\r
1029 var singleDecStack = stack;
\r
1031 stack.desc = 'sub-expression';
\r
1032 stack.nextBlack = match.tokposb;
\r
1033 singleDecStack.push(stack);
\r
1036 // next token should be a valid identifier
\r
1037 if (match.name == 12/*eof*/) {
\r
1038 if (first) match = this.failsafe('VarKeywordMissingName', match);
\r
1039 // else, ignore. TOFIX: return?
\r
1040 else match = this.failsafe('IllegalTrailingComma', match);
\r
1041 } else if (match.name != 2/*IDENTIFIER*/) {
\r
1042 match = this.failsafe('VarNamesMayOnlyBeIdentifiers', match);
\r
1043 } else if (this.hashStartKeyOrReserved[match.value[0]] /*this.regexStartKeyOrReserved.test(match.value[0])*/ && this.regexIsKeywordOrReserved.test(match.value)) {
\r
1044 match = this.failsafe('VarNamesCanNotBeReserved', match);
\r
1046 // mark the match as being a variable name. we need it for lookup later :)
\r
1047 if (this.ast) { //#ifdef FULL_AST
\r
1048 match.meta = 'var name';
\r
1049 targetScope.push({value:match.value});
\r
1051 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1053 if (this.ast) { //#ifdef FULL_AST
\r
1054 stack = singleDecStack;
\r
1057 // next token should either be a = , or ;
\r
1058 // if = parse an expression and optionally a comma
\r
1059 if (match.value == '=') {
\r
1060 if (this.ast) { //#ifdef FULL_AST
\r
1061 singleDecStack = stack;
\r
1063 stack.desc = 'operator-expression';
\r
1065 stack.nextBlack = match.tokposb;
\r
1066 singleDecStack.push(stack);
\r
1068 stack.isAssignment = true;
\r
1070 match.isInitialiser = true;
\r
1071 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1072 if (this.ast) { //#ifdef FULL_AST
\r
1073 stack = singleDecStack;
\r
1076 if (!(/*is left hand side start?*/ match.name <= 6 || match.name == 14/*error*/ || this.regexLhsStart.test(match.value))) match = this.failsafe('VarInitialiserExpressionExpected', match);
\r
1077 match = this.eatExpressions(false, match, stack, true, forHeader); // only one expression
\r
1078 // var statement: comma or semi now
\r
1079 // for statement: semi, comma or 'in'
\r
1081 if (this.ast) { //#ifdef FULL_AST
\r
1082 stack = declStack;
\r
1085 // determines proper error message in one case
\r
1087 // keep parsing name(=expression) sequences as long as you see a comma here
\r
1088 } while (match.value == ',');
\r
1090 if (this.ast) { //#ifdef FULL_AST
\r
1091 stack.varsDeclared = varsDeclared;
\r
1097 eatIf: function(match, stack){
\r
1098 if (this.ast) { //#ifdef FULL_AST
\r
1099 stack.push(stack = []);
\r
1100 stack.desc = 'statement';
\r
1102 stack.hasElse = false;
\r
1103 stack.nextBlack = match.tokposb;
\r
1109 // [else statement]
\r
1110 var ifKeyword = match;
\r
1111 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1112 if (match.value != '(') match = this.failsafe('ExpectedStatementHeaderOpen', match);
\r
1113 if (this.ast) { //#ifdef FULL_AST
\r
1115 match.statementHeaderStart = true;
\r
1117 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1118 if (!(/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value))) match = this.failsafe('StatementHeaderIsNotOptional', match);
\r
1119 match = this.eatExpressions(false, match, stack);
\r
1120 if (match.value != ')') match = this.failsafe('ExpectedStatementHeaderClose', match);
\r
1121 if (this.ast) { //#ifdef FULL_AST
\r
1123 match.statementHeaderStop = true;
\r
1126 if (stack[stack.length-1].desc == 'expressions') {
\r
1127 // create ref to this expression group to the opening bracket
\r
1128 lhp.expressionArg = stack[stack.length-1];
\r
1131 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1132 match = this.eatStatement(false, match, stack);
\r
1134 // match might be null here... (if the if-statement was end part of the source)
\r
1135 if (match && match.value == 'else') {
\r
1136 if (this.ast) { //#ifdef FULL_AST
\r
1137 ifKeyword.hasElse = match;
\r
1139 match = this.eatElse(match, stack);
\r
1144 eatElse: function(match, stack){
\r
1145 if (this.ast) { //#ifdef FULL_AST
\r
1146 stack.hasElse = true;
\r
1147 stack.push(stack = []);
\r
1148 stack.desc = 'statement';
\r
1149 stack.sub = 'else';
\r
1150 stack.nextBlack = match.tokposb;
\r
1152 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1153 match = this.eatStatement(false, match, stack);
\r
1157 eatDo: function(match, stack){
\r
1158 if (this.ast) { //#ifdef FULL_AST
\r
1159 stack.push(stack = []);
\r
1160 stack.desc = 'statement';
\r
1162 stack.isIteration = true;
\r
1163 stack.nextBlack = match.tokposb;
\r
1164 this.statementLabels.push(''); // add "empty"
\r
1165 var doToken = match;
\r
1173 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1174 match = this.eatStatement(false, match, stack);
\r
1175 if (match.value != 'while') match = this.failsafe('DoShouldBeFollowedByWhile', match);
\r
1176 if (this.ast) { //#ifdef FULL_AST
\r
1177 match.hasDo = doToken;
\r
1179 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1180 if (match.value != '(') match = this.failsafe('ExpectedStatementHeaderOpen', match);
\r
1181 if (this.ast) { //#ifdef FULL_AST
\r
1183 match.statementHeaderStart = true;
\r
1185 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1186 if (!(/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value))) match = this.failsafe('StatementHeaderIsNotOptional', match);
\r
1187 match = this.eatExpressions(false, match, stack);
\r
1188 if (match.value != ')') match = this.failsafe('ExpectedStatementHeaderClose', match);
\r
1189 if (this.ast) { //#ifdef FULL_AST
\r
1191 match.statementHeaderStop = true;
\r
1192 match.isForDoWhile = true; // prevents missing block warnings
\r
1195 if (stack[stack.length-1].desc == 'expressions') {
\r
1196 // create ref to this expression group to the opening bracket
\r
1197 lhp.expressionArg = stack[stack.length-1];
\r
1200 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1201 match = this.eatSemiColon(match, stack); // TOFIX: this is not optional according to the spec, but browsers apply ASI anyways
\r
1205 eatWhile: function(match, stack){
\r
1206 if (this.ast) { //#ifdef FULL_AST
\r
1207 stack.push(stack = []);
\r
1208 stack.desc = 'statement';
\r
1209 stack.sub = 'while';
\r
1210 stack.isIteration = true;
\r
1211 stack.nextBlack = match.tokposb;
\r
1212 this.statementLabels.push(''); // add "empty"
\r
1219 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1220 if (match.value != '(') match = this.failsafe('ExpectedStatementHeaderOpen', match);
\r
1221 if (this.ast) { //#ifdef FULL_AST
\r
1223 match.statementHeaderStart = true;
\r
1225 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1226 if (!(/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value))) match = this.failsafe('StatementHeaderIsNotOptional', match);
\r
1227 match = this.eatExpressions(false, match, stack);
\r
1228 if (match.value != ')') match = this.failsafe('ExpectedStatementHeaderClose', match);
\r
1229 if (this.ast) { //#ifdef FULL_AST
\r
1231 match.statementHeaderStop = true;
\r
1234 if (stack[stack.length-1].desc == 'expressions') {
\r
1235 // create ref to this expression group to the opening bracket
\r
1236 lhp.expressionArg = stack[stack.length-1];
\r
1239 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1240 match = this.eatStatement(false, match, stack);
\r
1245 eatFor: function(match, stack){
\r
1246 if (this.ast) { //#ifdef FULL_AST
\r
1247 stack.push(stack = []);
\r
1248 stack.desc = 'statement';
\r
1249 stack.sub = 'for';
\r
1250 stack.isIteration = true;
\r
1251 stack.nextBlack = match.tokposb;
\r
1252 this.statementLabels.push(''); // add "empty"
\r
1254 // either a for(..in..) or for(..;..;..)
\r
1255 // start eating an expression but refuse to parse
\r
1256 // 'in' on the top-level of that expression. they are fine
\r
1257 // in sub-levels (group, array, etc). Now the expression
\r
1258 // must be followed by either ';' or 'in'. Else throw.
\r
1259 // Branch on that case, ; requires two.
\r
1260 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1261 if (match.value != '(') match = this.failsafe('ExpectedStatementHeaderOpen', match);
\r
1262 if (this.ast) { //#ifdef FULL_AST
\r
1264 match.statementHeaderStart = true;
\r
1265 match.forHeaderStart = true;
\r
1267 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1269 // for (either case) may start with var, in which case you'll parse a var declaration before encountering the 'in' or first semi.
\r
1270 if (match.value == 'var') {
\r
1271 match = this.eatVarDecl(match, stack, true);
\r
1272 } else if (match.value != ';') { // expressions are optional in for-each
\r
1273 if (!(/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value))) {
\r
1274 this.failignore('StatementHeaderIsNotOptional', match, stack);
\r
1276 match = this.eatExpressions(false, match, stack, false, true); // can parse multiple expressions, in is not ok here
\r
1279 // now we parsed an expression if it existed. the next token should be either ';' or 'in'. branch accordingly
\r
1280 if (match.value == 'in') {
\r
1281 var declStack = stack[stack.length-1];
\r
1282 if (declStack.varsDeclared > 1) {
\r
1283 // disallowed. for-in var decls can only have one var name declared
\r
1284 this.failignore('ForInCanOnlyDeclareOnVar', match, stack);
\r
1287 if (this.ast) { //#ifdef FULL_AST
\r
1288 stack.forType = 'in';
\r
1289 match.forFor = true; // make easy distinction between conditional and iterational operator
\r
1292 // just parse another expression, where 'in' is allowed.
\r
1293 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1294 match = this.eatExpressions(false, match, stack);
\r
1296 if (match.value != ';') match = this.failsafe('ForHeaderShouldHaveSemisOrIn', match);
\r
1298 if (this.ast) { //#ifdef FULL_AST
\r
1299 stack.forType = 'each';
\r
1300 match.forEachHeaderStart = true;
\r
1302 // parse another optional no-in expression, another semi and then one more optional no-in expression
\r
1303 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1304 if (/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value)) match = this.eatExpressions(false, match, stack); // in is ok here
\r
1305 if (match.value != ';') match = this.failsafe('ExpectedSecondSemiOfForHeader', match);
\r
1306 if (this.ast) { //#ifdef FULL_AST
\r
1307 match.forEachHeaderStop = true;
\r
1309 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1310 if (/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value)) match = this.eatExpressions(false, match, stack); // in is ok here
\r
1313 if (match.value != ')') match = this.failsafe('ExpectedStatementHeaderClose', match);
\r
1314 if (this.ast) { //#ifdef FULL_AST
\r
1316 match.statementHeaderStop = true;
\r
1317 match.forHeaderStop = true;
\r
1320 if (match.forType == 'in' && stack[stack.length-1].desc == 'expressions') {
\r
1321 // create ref to this expression group to the opening bracket
\r
1322 lhp.expressionArg = stack[stack.length-1];
\r
1325 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1327 match = this.eatStatement(false, match, stack);
\r
1331 eatContinue: function(match, stack){
\r
1332 if (this.ast) { //#ifdef FULL_AST
\r
1333 stack.push(stack = []);
\r
1334 stack.desc = 'statement';
\r
1335 stack.sub = 'continue';
\r
1336 stack.nextBlack = match.tokposb;
\r
1338 match.restricted = true;
\r
1340 // (no-line-break identifier)
\r
1342 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); // may not have line terminator...
\r
1343 if (!match.newline && match.value != ';' && match.name != 12/*EOF*/ && match.value != '}') {
\r
1344 if (this.ast) { //#ifdef FULL_AST
\r
1345 match.isLabel = true;
\r
1346 match.isLabelTarget = true;
\r
1348 var continueArg = match; // remember to see if this continue parsed a label
\r
1350 // may only parse exactly an identifier at this point
\r
1351 match = this.eatExpressions(false, match, stack, true, false, true); // first true=onlyOne, second: continue/break arg
\r
1352 if (this.ast) { //#ifdef FULL_AST
\r
1353 stack.hasLabel = continueArg != match;
\r
1355 if (match.value != ';' && !match.newline && match.name != 12/*eof*/ && match.value != '}') match = this.failsafe('BreakOrContinueArgMustBeJustIdentifier', match);
\r
1357 match = this.eatSemiColon(match, stack);
\r
1361 eatBreak: function(match, stack){
\r
1362 if (this.ast) { //#ifdef FULL_AST
\r
1363 var parentstack = stack
\r
1365 stack.desc = 'statement';
\r
1366 stack.sub = 'break';
\r
1367 stack.nextBlack = match.tokposb;
\r
1369 parentstack.push(stack);
\r
1371 match.restricted = true;
\r
1373 // (no-line-break identifier)
\r
1375 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); // may not have line terminator...
\r
1376 if (!match.newline && match.value != ';' && match.name != 12/*EOF*/ && match.value != '}') {
\r
1377 if (this.ast) { //#ifdef FULL_AST
\r
1378 match.isLabel = true;
\r
1379 match.isLabelTarget = true;
\r
1380 var breakArg = match; // remember to see if this break parsed a label
\r
1382 // may only parse exactly an identifier at this point
\r
1383 match = this.eatExpressions(false, match, stack, true, false, true); // first true=onlyOne, second: continue/break arg
\r
1384 if (this.ast) { //#ifdef FULL_AST
\r
1385 stack.hasLabel = breakArg != match;
\r
1388 if (match.value != ';' && !match.newline && match.name != 12/*eof*/ && match.value != '}') match = this.failsafe('BreakOrContinueArgMustBeJustIdentifier', match);
\r
1390 match = this.eatSemiColon(match, stack);
\r
1394 eatReturn: function(match, stack){
\r
1395 if (this.ast) { //#ifdef FULL_AST
\r
1396 stack.push(stack = []);
\r
1397 stack.desc = 'statement';
\r
1398 stack.sub = 'return';
\r
1399 stack.nextBlack = match.tokposb;
\r
1400 stack.returnFor = this.scope.scopeFor;
\r
1402 match.restricted = true;
\r
1404 // (no-line-break expression)
\r
1406 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); // may not have line terminator...
\r
1407 if (!match.newline && match.value != ';' && match.name != 12/*EOF*/ && match.value != '}') {
\r
1408 match = this.eatExpressions(false, match, stack);
\r
1410 match = this.eatSemiColon(match, stack);
\r
1414 eatThrow: function(match, stack){
\r
1415 if (this.ast) { //#ifdef FULL_AST
\r
1416 stack.push(stack = []);
\r
1417 stack.desc = 'statement';
\r
1418 stack.sub = 'throw';
\r
1419 stack.nextBlack = match.tokposb;
\r
1421 match.restricted = true;
\r
1423 // (no-line-break expression)
\r
1425 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack); // may not have line terminator...
\r
1426 if (match.newline) match = this.failsafe('ThrowCannotHaveReturn', match);
\r
1427 if (match.value == ';') match = this.failsafe('ThrowMustHaveArgument', match);
\r
1428 match = this.eatExpressions(false, match, stack);
\r
1429 match = this.eatSemiColon(match, stack);
\r
1433 eatSwitch: function(match, stack){
\r
1434 if (this.ast) { //#ifdef FULL_AST
\r
1435 stack.push(stack = []);
\r
1436 stack.desc = 'statement';
\r
1437 stack.sub = 'switch';
\r
1438 stack.nextBlack = match.tokposb;
\r
1440 this.statementLabels.push(''); // add "empty"
\r
1443 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1444 if (match.value != '(') match = this.failsafe('ExpectedStatementHeaderOpen', match);
\r
1445 if (this.ast) { //#ifdef FULL_AST
\r
1447 match.statementHeaderStart = true;
\r
1449 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1450 if (!(/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value))) {
\r
1451 this.failignore('StatementHeaderIsNotOptional', match, stack);
\r
1453 match = this.eatExpressions(false, match, stack);
\r
1454 if (match.value != ')') match = this.failsafe('ExpectedStatementHeaderClose', match);
\r
1455 if (this.ast) { //#ifdef FULL_AST
\r
1457 match.statementHeaderStop = true;
\r
1460 if (stack[stack.length-1].desc == 'expressions') {
\r
1461 // create ref to this expression group to the opening bracket
\r
1462 lhp.expressionArg = stack[stack.length-1];
\r
1465 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1466 if (match.value != '{') match = this.failsafe('SwitchBodyStartsWithCurly', match);
\r
1468 if (this.ast) { //#ifdef FULL_AST
\r
1471 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1473 // you may parse a default case, and only once per switch. but you may do so anywhere.
\r
1474 var parsedAnything = false;
\r
1476 while (match.value == 'case' || (!stack.parsedSwitchDefault && match.value == 'default')) {
\r
1477 parsedAnything = true;
\r
1479 match = this.eatSwitchClause(match, stack);
\r
1482 // if you didnt parse anything but not encountering a closing curly now, you might be thinking that switches may start with silly stuff
\r
1483 if (!parsedAnything && match.value != '}') {
\r
1484 match = this.failsafe('SwitchBodyMustStartWithClause', match);
\r
1487 if (stack.parsedSwitchDefault && match.value == 'default') {
\r
1488 this.failignore('SwitchCannotHaveDoubleDefault', match, stack);
\r
1491 if (match.value != '}' && match.name != 14/*error*/) match = this.failsafe('SwitchBodyEndsWithCurly', match);
\r
1493 if (this.ast) { //#ifdef FULL_AST
\r
1497 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1501 eatSwitchClause: function(match, stack){
\r
1502 match = this.eatSwitchHeader(match, stack);
\r
1503 match = this.eatSwitchBody(match, stack);
\r
1507 eatSwitchHeader: function(match, stack){
\r
1508 if (this.ast) { //#ifdef FULL_AST
\r
1509 // collect whitespace...
\r
1510 var switchHeaderStack = stack
\r
1511 stack.push(stack = []);
\r
1512 stack.desc = 'switch clause header';
\r
1513 stack.nextBlack = match.tokposb;
\r
1516 if (match.value == 'case') {
\r
1517 match = this.eatSwitchCaseHead(match, stack);
\r
1518 } else { // default
\r
1519 if (this.ast) { //#ifdef FULL_AST
\r
1520 switchHeaderStack.hasDefaultClause = true;
\r
1522 match = this.eatSwitchDefaultHead(match, stack);
\r
1525 if (this.ast) { //#ifdef FULL_AST
\r
1526 // just to group whitespace (makes certain navigation easier..)
\r
1527 stack.push(stack = []);
\r
1528 stack.desc = 'colon';
\r
1529 stack.nextBlack = match.tokposb;
\r
1532 if (match.value != ':') {
\r
1533 match = this.failsafe('SwitchClausesEndWithColon', match);
\r
1535 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1539 eatSwitchBody: function(match, stack){
\r
1540 if (this.ast) { //#ifdef FULL_AST
\r
1541 stack.push(stack = []);
\r
1542 stack.desc = 'switch clause body';
\r
1543 stack.nextBlack = match.tokposb;
\r
1546 // parse body of case or default, just so long case and default keywords are not seen and end of switch is not reached
\r
1547 // (clause bodies may be empty, for instance to fall through)
\r
1548 var lastMatch = null;
\r
1549 while (match.value != 'default' && match.value != 'case' && match.value != '}' && match.name != 14/*error*/ && match.name != 12/*eof*/ && lastMatch != match) {
\r
1550 lastMatch = match; // prevents endless loops on error ;)
\r
1551 match = this.eatStatement(true, match, stack);
\r
1553 if (lastMatch == match) this.failsafe('UnexpectedInputSwitch', match);
\r
1557 eatSwitchCaseHead: function(match, stack){
\r
1558 if (this.ast) { //#ifdef FULL_AST
\r
1559 stack.sub = 'case';
\r
1560 var caseHeadStack = stack;
\r
1562 stack.push(stack = []);
\r
1563 stack.desc = 'case';
\r
1564 stack.nextBlack = match.tokposb;
\r
1566 match.isCase = true;
\r
1568 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1570 if (match.value == ':') {
\r
1571 this.failignore('CaseMissingExpression', match, stack);
\r
1573 if (this.ast) { //#ifdef FULL_AST
\r
1574 caseHeadStack.push(stack = []);
\r
1575 stack.desc = 'case arg';
\r
1576 stack.nextBlack = match.tokposb;
\r
1578 match = this.eatExpressions(false, match, stack);
\r
1583 eatSwitchDefaultHead: function(match, stack){
\r
1584 if (this.ast) { //#ifdef FULL_AST
\r
1585 stack.sub = 'default';
\r
1587 stack.push(stack = []);
\r
1588 stack.desc = 'case';
\r
1589 stack.nextBlack = match.tokposb;
\r
1591 match.isDefault = true;
\r
1593 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1597 eatTryCatchFinally: function(match, stack){
\r
1598 if (this.ast) { //#ifdef FULL_AST
\r
1599 stack.push(stack = []);
\r
1600 stack.desc = 'statement';
\r
1601 stack.sub = 'try';
\r
1602 stack.nextBlack = match.tokposb;
\r
1605 match = this.eatTry(match, stack);
\r
1607 if (match.value == 'catch') {
\r
1608 if (this.ast) { //#ifdef FULL_AST
\r
1609 stack.hasCatch = true;
\r
1611 match = this.eatCatch(match, stack);
\r
1613 if (match.value == 'finally') {
\r
1614 if (this.ast) { //#ifdef FULL_AST
\r
1615 stack.hasFinally = true;
\r
1617 match = this.eatFinally(match, stack);
\r
1620 // at least a catch or finally block must follow. may be both.
\r
1621 if (!stack.tryHasCatchOrFinally) {
\r
1622 this.failignore('TryMustHaveCatchOrFinally', match, stack);
\r
1627 eatTry: function(match, stack){
\r
1629 // (catch ( identifier ) block )
\r
1630 // (finally block)
\r
1631 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1632 if (match.value != '{') match = this.failsafe('MissingTryBlockCurlyOpen', match);
\r
1634 if (this.ast) { //#ifdef FULL_AST
\r
1635 stack.push(stack = []);
\r
1636 stack.desc = 'statement';
\r
1637 stack.sub = 'tryblock';
\r
1638 stack.nextBlack = match.tokposb;
\r
1642 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1643 if (match.value != '}') match = this.eatStatements(match, stack);
\r
1644 if (match.value != '}') match = this.failsafe('MissingTryBlockCurlyClose', match);
\r
1646 if (this.ast) { //#ifdef FULL_AST
\r
1651 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1655 eatCatch: function(match, stack){
\r
1656 stack.tryHasCatchOrFinally = true;
\r
1657 if (this.ast) { //#ifdef FULL_AST
\r
1658 stack.push(stack = []);
\r
1659 stack.desc = 'statement';
\r
1660 stack.sub = 'catch';
\r
1661 stack.nextBlack = match.tokposb;
\r
1663 // the catch block has a header which can contain at most one parameter
\r
1664 // this parameter is bound to a local stack. formally, if that parameter
\r
1665 // shadows another variable, changes made to the variable inside the catch
\r
1666 // should not be reflected by the variable being shadowed. however, this
\r
1667 // is not very safe to rely on so there ought to be a warning. note that
\r
1668 // only this parameter gets bound to this inner scope, other parameters.
\r
1670 var catchScopeBackup = this.scope;
\r
1671 match.scope = this.scope = stack.scope = [this.scope];
\r
1672 this.scope.catchScope = true; // mark this as being a catchScope
\r
1674 // find first function scope or global scope object...
\r
1675 var nonCatchScope = catchScopeBackup;
\r
1676 while (nonCatchScope.catchScope) nonCatchScope = nonCatchScope[0];
\r
1678 // get catch id, which is governed by the function/global scope only
\r
1679 if (!nonCatchScope.catches) nonCatchScope.catches = [];
\r
1680 match.catchId = nonCatchScope.catches.length;
\r
1681 nonCatchScope.catches.push(match);
\r
1682 match.targetScope = nonCatchScope;
\r
1683 match.catchScope = this.scope;
\r
1685 // ref to back to function that's the cause for this scope
\r
1686 this.scope.scopeFor = match;
\r
1687 // catch clauses dont have a special `this` or `arguments`, map them to their parent scope
\r
1688 if (catchScopeBackup.global) this.scope.push(catchScopeBackup[0]); // global (has no `arguments` but always a `this`)
\r
1689 else if (catchScopeBackup.catchScope) {
\r
1690 // tricky. there will at least be a this
\r
1691 this.scope.push(catchScopeBackup[1]);
\r
1692 // but there might not be an arguments
\r
1693 if (catchScopeBackup[2] && catchScopeBackup[2].value == 'arguments') this.scope.push(catchScopeBackup[2]);
\r
1694 } else this.scope.push(catchScopeBackup[1], catchScopeBackup[2]); // function scope, copy this and arguments
\r
1697 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1698 if (match.value != '(') match = this.failsafe('CatchHeaderMissingOpen', match);
\r
1699 if (this.ast) { //#ifdef FULL_AST
\r
1702 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1703 if (match.name != 2/*IDENTIFIER*/) match = this.failsafe('MissingCatchParameter', match);
\r
1704 if (this.hashStartKeyOrReserved[match.value[0]] /*this.regexStartKeyOrReserved.test(match.value[0])*/ && this.regexIsKeywordOrReserved.test(match.value)) {
\r
1705 this.failignore('CatchParameterNameMayNotBeReserved', match, stack);
\r
1708 if (this.ast) { //#ifdef FULL_AST
\r
1709 match.meta = 'var name';
\r
1710 // this is the catch variable. bind it to a scope but keep the scope as
\r
1711 // it currently is.
\r
1712 this.scope.push(match);
\r
1713 match.isCatchVar = true;
\r
1716 // now the catch body will use the outer scope to bind new variables. the problem is that
\r
1717 // inner scopes, if any, should have access to the scope variable, so their scope should
\r
1718 // be linked to the catch scope. this is a problem in the current architecture but the
\r
1719 // idea is to pass on the catchScope as the scope to the eatStatements call, etc.
\r
1721 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1722 if (match.value != ')') match = this.failsafe('CatchHeaderMissingClose', match);
\r
1723 if (this.ast) { //#ifdef FULL_AST
\r
1727 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1728 if (match.value != '{') match = this.failsafe('MissingCatchBlockCurlyOpen', match);
\r
1729 if (this.ast) { //#ifdef FULL_AST
\r
1732 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1734 // catch body. statements are optional.
\r
1735 if (match.value != '}') match = this.eatStatements(match, stack);
\r
1737 if (match.value != '}') match = this.failsafe('MissingCatchBlockCurlyClose', match);
\r
1738 if (this.ast) { //#ifdef FULL_AST
\r
1742 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1744 if (this.ast) { //#ifdef FULL_AST
\r
1745 this.scope = catchScopeBackup;
\r
1750 eatFinally: function(match, stack){
\r
1751 stack.tryHasCatchOrFinally = true;
\r
1752 if (this.ast) { //#ifdef FULL_AST
\r
1753 stack.push(stack = []);
\r
1754 stack.desc = 'statement';
\r
1755 stack.sub = 'finally';
\r
1756 stack.nextBlack = match.tokposb;
\r
1759 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1760 if (match.value != '{') match = this.failsafe('MissingFinallyBlockCurlyOpen', match);
\r
1761 if (this.ast) { //#ifdef FULL_AST
\r
1764 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1765 if (match.value != '}') match = this.eatStatements(match, stack);
\r
1766 if (match.value != '}') match = this.failsafe('MissingFinallyBlockCurlyClose', match);
\r
1767 if (this.ast) { //#ifdef FULL_AST
\r
1771 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1775 eatDebugger: function(match, stack){
\r
1776 if (this.ast) { //#ifdef FULL_AST
\r
1777 stack.push(stack = []);
\r
1778 stack.desc = 'statement';
\r
1779 stack.sub = 'debugger';
\r
1780 stack.nextBlack = match.tokposb;
\r
1782 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1783 match = this.eatSemiColon(match, stack);
\r
1787 eatWith: function(match, stack){
\r
1788 if (this.ast) { //#ifdef FULL_AST
\r
1789 stack.push(stack = []);
\r
1790 stack.desc = 'statement';
\r
1791 stack.sub = 'with';
\r
1792 stack.nextBlack = match.tokposb;
\r
1794 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1795 if (match.value != '(') match = this.failsafe('ExpectedStatementHeaderOpen', match);
\r
1796 if (this.ast) { //#ifdef FULL_AST
\r
1798 match.statementHeaderStart = true;
\r
1800 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1801 if (!(/*is left hand side start?*/ match.name <= 6 || this.regexLhsStart.test(match.value))) match = this.failsafe('StatementHeaderIsNotOptional', match);
\r
1802 match = this.eatExpressions(false, match, stack);
\r
1803 if (match.value != ')') match = this.failsafe('ExpectedStatementHeaderClose', match);
\r
1804 if (this.ast) { //#ifdef FULL_AST
\r
1806 match.statementHeaderStop = true;
\r
1809 if (stack[stack.length-1].desc == 'expressions') {
\r
1810 // create ref to this expression group to the opening bracket
\r
1811 lhp.expressionArg = stack[stack.length-1];
\r
1814 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1815 match = this.eatStatement(false, match, stack);
\r
1819 eatFunction: function(match, stack){
\r
1820 var pe = new ZeParser.Error
\r
1821 this.errorStack.push(pe);
\r
1822 // ignore. browsers will accept it anyways
\r
1823 var error = {start:match.stop,stop:match.stop,name:14/*error*/,error:pe};
\r
1824 this.specialError(error, match, stack);
\r
1825 // now try parsing a function declaration...
\r
1826 match = this.eatFunctionDeclaration(match, stack);
\r
1830 eatLabelOrExpression: function(match, stack){
\r
1831 if (this.ast) { //#ifdef FULL_AST
\r
1832 var parentstack = stack;
\r
1835 stack.desc = 'statement';
\r
1836 stack.sub = 'expression';
\r
1837 stack.nextBlack = match.tokposb;
\r
1838 parentstack.push(stack);
\r
1840 // must be an expression or a labeled statement.
\r
1841 // in order to prevent very weird return constructs, we'll first check the first match
\r
1842 // if that's an identifier, we'll gobble it here and move on to the second.
\r
1843 // if that's a colon, we'll gobble it as a labeled statement. otherwise, we'll pass on
\r
1844 // control to eatExpression, with the note that we've already gobbled a
\r
1846 match = this.eatExpressions(true, match, stack);
\r
1847 // if we parsed a label, the returned match (colon) will have this property
\r
1848 if (match.wasLabel) {
\r
1849 if (this.ast) { //#ifdef FULL_AST
\r
1850 stack.sub = 'labeled';
\r
1852 // it will have already eaten another statement for the label
\r
1854 if (this.ast) { //#ifdef FULL_AST
\r
1855 stack.sub = 'expression';
\r
1857 // only parse semi if we didnt parse a label just now...
\r
1858 match = this.eatSemiColon(match, stack);
\r
1863 eatBlock: function(match, stack){
\r
1864 if (this.ast) { //#ifdef FULL_AST
\r
1865 stack.sub = 'block';
\r
1869 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1871 if (match.value == '}') {
\r
1872 if (this.ast) { //#ifdef FULL_AST
\r
1873 stack.isEmptyBlock = true;
\r
1876 match = this.eatStatements(match, stack);
\r
1878 if (match.value != '}') match = this.failsafe('BlockCurlyClose', match);
\r
1879 if (this.ast) { //#ifdef FULL_AST
\r
1883 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);
\r
1888 eatStatements: function(match, stack){
\r
1889 //this.stats.eatStatements = (+//this.stats.eatStatements||0)+1;
\r
1890 // detecting the start of a statement "quickly" is virtually impossible.
\r
1891 // instead we keep eating statements until the match stops changing
\r
1892 // the first argument indicates that the statement is optional. if that
\r
1893 // statement was not found, the input match will also be the output.
\r
1895 while (match != (match = this.eatStatement(true, match, stack)));
\r
1898 eatStatement: function(isOptional, match, stack){
\r
1899 if (!match && isOptional) return match; // eof
\r
1901 if (this.ast) { //#ifdef FULL_AST
\r
1902 match.statementStart = true;
\r
1903 var pstack = stack;
\r
1905 stack.desc = 'statement-parent';
\r
1906 stack.nextBlack = match.tokposb;
\r
1907 pstack.push(stack);
\r
1909 // list of labels, these are bound to statements (and can access any label higher up, but not cross functions)
\r
1910 var labelBackup = this.statementLabels;
\r
1911 this.statementLabels = [labelBackup]; // make ref like tree. we need this to catch labels parsed beyond the current position (not yet known to use)
\r
1912 stack.labels = this.statementLabels;
\r
1915 if (match.name == 2/*IDENTIFIER*/) {
\r
1916 // try to determine whether it's a statement
\r
1917 // (block/empty statements come later, this branch is only for identifiers)
\r
1918 switch (match.value) {
\r
1920 match = this.eatVar(match, stack);
\r
1923 match = this.eatIf(match, stack);
\r
1926 match = this.eatDo(match, stack);
\r
1929 match = this.eatWhile(match, stack);
\r
1932 match = this.eatFor(match, stack);
\r
1935 match = this.eatContinue(match, stack);
\r
1938 match = this.eatBreak(match, stack);
\r
1941 match = this.eatReturn(match, stack);
\r
1944 match = this.eatThrow(match, stack);
\r
1947 match = this.eatSwitch(match, stack);
\r
1950 match = this.eatTryCatchFinally(match, stack);
\r
1953 match = this.eatDebugger(match, stack);
\r
1956 match = this.eatWith(match, stack);
\r
1959 // I'm not sure whether this is at all possible.... (but it's bad, either way ;)
\r
1960 // so add an error token, but parse the function as if it was a declaration.
\r
1961 this.failignore('StatementMayNotStartWithFunction', match, stack);
\r
1963 // now parse as declaration... (most likely?)
\r
1964 match = this.eatFunctionDeclaration(match, stack);
\r
1967 default: // either a label or an expression-statement
\r
1968 match = this.eatLabelOrExpression(match, stack);
\r
1970 } else if (match.value == '{') { // Block (make sure you do this before checking for expression...)
\r
1971 match = this.eatBlock(match, stack);
\r
1973 // expression statements:
\r
1976 match.name == 1/*REG_EX*/ ||
\r
1977 this.regexLhsStart.test(match.value)
\r
1979 match = this.eatExpressions(false, match,stack);
\r
1980 match = this.eatSemiColon(match, stack);
\r
1981 } else if (match.value == ';') { // empty statement
\r
1982 match.emptyStatement = true;
\r
1983 match = this.eatSemiColon(match, stack);
\r
1984 } else if (!isOptional) {
\r
1985 if (this.ast) { //#ifdef FULL_AST
\r
1986 // unmark token as being start of a statement, since it's obviously not
\r
1987 match.statementStart = false;
\r
1989 match = this.failsafe('UnableToParseStatement', match);
\r
1991 // unmark token as being start of a statement, since it's obviously not
\r
1992 if (this.ast) match.statementStart = true;
\r
1995 if (this.ast) { //#ifdef FULL_AST
\r
1996 if (!stack.length) pstack.length = pstack.length-1;
\r
1998 // restore label set
\r
1999 this.statementLabels = labelBackup;
\r
2005 eatSourceElements: function(match, stack){
\r
2006 //this.stats.eatSourceElements = (+//this.stats.eatSourceElements||0)+1;
\r
2007 // detecting the start of a statement "quickly" is virtually impossible.
\r
2008 // instead we keep eating statements until the match stops changing
\r
2009 // the first argument indicates that the statement is optional. if that
\r
2010 // statement was not found, the input match will also be the output.
\r
2011 while (match != oldMatch) { // difficult to determine whether ` && match.name != 12/*EOF*/` is actually speeding things up. it's an extra check vs one less call to eatStatement...
\r
2012 var oldMatch = match;
\r
2013 // always try to eat function declaration first. otherwise 'function' at the start might cause eatStatement to throw up
\r
2014 if (match.value == 'function') match = this.eatFunctionDeclaration(match, stack);
\r
2015 else match = this.eatStatement(true, match, stack);
\r
2020 failsafe: function(name, match, doNotAddMatch){
\r
2021 var pe = new ZeParser.Error(name, match);
\r
2022 this.errorStack.push(pe);
\r
2024 if (!doNotAddMatch) {
\r
2025 // the match was bad, but add it to the ast anyways. in most cases this is the case but in some its not.
\r
2026 // the tokenizer will pick up on the errorEscape property and add it after the match we passed on.
\r
2027 if (this.tokenizer.errorEscape) this.stack.push(this.tokenizer.errorEscape);
\r
2028 this.tokenizer.errorEscape = match;
\r
2030 var error = {start:match.start,stop:match.start,len:0, name:14/*error*/,error:pe, value:''};
\r
2031 this.tokenizer.addTokenToStreamBefore(error, match);
\r
2034 failignore: function(name, match, stack){
\r
2035 var pe = new ZeParser.Error(name, match);
\r
2036 this.errorStack.push(pe);
\r
2037 // ignore the error (this will screw up :)
\r
2038 var error = {start:match.start,stop:match.start,len:0,name:14/*error*/,error:pe, value:''};
\r
2039 stack.push(error);
\r
2040 this.tokenizer.addTokenToStreamBefore(error, match);
\r
2042 failSpecial: function(error, match, stack){
\r
2043 // we cant really ignore this. eat the token
\r
2044 stack.push(error);
\r
2045 this.tokenizer.addTokenToStreamBefore(error, match);
\r
2050 //#ifdef TEST_SUITE
\r
2051 ZeParser.testSuite = function(tests){
\r
2054 var start = +new Date;
\r
2055 for (var i = 0; i < tests.length; ++i) {
\r
2056 var test = tests[i], input = test[0], desc = test[test.length - 1], stack = [];
\r
2058 new ZeParser(input, new Tokenizer(input), stack).parse();
\r
2063 document.getElementsByTagName('div')[0].innerHTML = ('Ze parser test suite finished ('+(+new Date - start)+' ms). ok:'+ok+', fail:'+fail);
\r
2068 ZeParser.regexLhsStart = /[\+\-\~\!\(\{\[]/;
\r
2070 ZeParser.regexStartKeyword = /[bcdefinrstvw]/;
\r
2071 ZeParser.regexKeyword = /^break$|^catch$|^continue$|^debugger$|^default$|^delete$|^do$|^else$|^finally$|^for$|^function$|^if$|^in$|^instanceof$|^new$|^return$|^switch$|^this$|^throw$|^try$|^typeof$|^var$|^void$|^while$|^with$/;
\r
2072 ZeParser.regexStartReserved = /[ceis]/;
\r
2073 ZeParser.regexReserved = /^class$|^const$|^enum$|^export$|^extends$|^import$|^super$/;
\r
2075 ZeParser.regexStartKeyOrReserved = /[bcdefinrstvw]/;
\r
2076 ZeParser.hashStartKeyOrReserved = Object.create ? Object.create(null, {b:{value:1},c:{value:1},d:{value:1},e:{value:1},f:{value:1},i:{value:1},n:{value:1},r:{value:1},s:{value:1},t:{value:1},v:{value:1},w:{value:1}}) : {b:1,c:1,d:1,e:1,f:1,i:1,n:1,r:1,s:1,t:1,v:1,w:1};
\r
2077 ZeParser.regexIsKeywordOrReserved = /^break$|^catch$|^continue$|^debugger$|^default$|^delete$|^do$|^else$|^finally$|^for$|^function$|^if$|^in$|^instanceof$|^new$|^return$|^switch$|^case$|^this$|^true$|^false$|^null$|^throw$|^try$|^typeof$|^var$|^void$|^while$|^with$|^class$|^const$|^enum$|^export$|^extends$|^import$|^super$/;
\r
2078 ZeParser.regexAssignments = /^[\+\-\*\%\&\|\^\/]?=$|^\<\<\=$|^\>{2,3}\=$/;
\r
2079 ZeParser.regexNonAssignmentBinaryExpressionOperators = /^[\+\-\*\%\|\^\&\?\/]$|^[\<\>]\=?$|^[\=\!]\=\=?$|^\<\<|\>\>\>?$|^\&\&$|^\|\|$/;
\r
2080 ZeParser.regexUnaryKeywords = /^delete$|^void$|^typeof$|^new$/;
\r
2081 ZeParser.hashUnaryKeywordStart = Object.create ? Object.create(null, {d:{value:1},v:{value:1},t:{value:1},n:{value:1}}) : {d:1,v:1,t:1,n:1};
\r
2082 ZeParser.regexUnaryOperators = /[\+\-\~\!]/;
\r
2083 ZeParser.regexLiteralKeywords = /^this$|^null$|^true$|^false$/;
\r
2085 ZeParser.Error = function(type, match){
\r
2086 //if (type == 'BreakOrContinueArgMustBeJustIdentifier') throw here;
\r
2087 this.msg = ZeParser.Errors[type].msg;
\r
2088 this.before = ZeParser.Errors[type].before;
\r
2089 this.match = match;
\r
2092 ZeParser.Errors = {
\r
2093 NoASI: {msg:'Expected semi-colon, was unable to apply ASI'},
\r
2094 ExpectedAnotherExpressionComma: {msg:'expecting another (left hand sided) expression after the comma'},
\r
2095 ExpectedAnotherExpressionRhs: {msg:"expected a rhs expression"},
\r
2096 UnclosedGroupingOperator: {msg:"Unclosed grouping operator"},
\r
2097 GroupingShouldStartWithExpression: {msg:'The grouping operator (`(`) should start with a left hand sided expression'},
\r
2098 ArrayShouldStartWithExpression: {msg:'The array literal (`[`) should start with a left hand sided expression'},
\r
2099 UnclosedPropertyBracket: {msg:'Property bracket was not closed after expression (expecting `]`)'},
\r
2100 IllegalPropertyNameToken: {msg:'Object literal property names can only be assigned as strings, numbers or identifiers'},
\r
2101 IllegalGetterSetterNameToken: {msg:'Name of a getter/setter can only be assigned as strings, numbers or identifiers'},
\r
2102 GetterSetterNameFollowedByOpenParen: {msg:'The name of the getter/setter should immediately be followed by the opening parenthesis `(`'},
\r
2103 GetterHasNoArguments: {msg:'The opening parenthesis `(` of the getter should be immediately followed by the closing parenthesis `)`, the getter cannot have an argument'},
\r
2104 IllegalSetterArgumentNameToken: {msg:'Expecting the name of the argument of a setter, can only be assigned as strings, numbers or identifiers'},
\r
2105 SettersOnlyGetOneArgument: {msg:'Setters have one and only one argument, missing the closing parenthesis `)`'},
\r
2106 SetterHeaderShouldHaveClosingParen: {msg:'After the first argument of a setter should come a closing parenthesis `)`'},
\r
2107 SettersMustHaveArgument: {msg:'Setters must have exactly one argument defined'},
\r
2108 UnclosedObjectLiteral: {msg:'Expected to find a comma `,` for the next expression or a closing curly brace `}` to end the object literal'},
\r
2109 FunctionNameMustNotBeReserved: {msg:'Function name may not be a keyword or a reserved word'},
\r
2110 ExpressionMayNotStartWithKeyword: {msg:'Expressions may not start with keywords or reserved words that are not in this list: [this, null, true, false, void, typeof, delete, new]'},
\r
2111 LabelsMayOnlyBeIdentifiers: {msg:'Label names may only be defined as an identifier'},
\r
2112 LabelsMayNotBeReserved: {msg:'Labels may not be a keyword or a reserved word'},
\r
2113 UnknownToken: {msg:'Unknown token encountered, dont know how to proceed'},
\r
2114 PropertyNamesMayOnlyBeIdentifiers: {msg:'The tokens of property names accessed through the dot operator may only be identifiers'},
\r
2115 SquareBracketExpectsExpression: {msg:'The square bracket property access expects an expression'},
\r
2116 SquareBracketsMayNotBeEmpty: {msg:'Square brackets may never be empty, expecting an expression'},
\r
2117 UnclosedSquareBrackets: {msg:'Unclosed square bracket encountered, was expecting `]` after the expression'},
\r
2118 UnclosedCallParens: {msg:'Unclosed call parenthesis, expecting `)` after the optional expression'},
\r
2119 InvalidCenterTernaryExpression: {msg:'Center expression of ternary operator should be a regular expression (but may not contain the comma operator directly)'},
\r
2120 UnfinishedTernaryOperator: {msg:'Encountered a ternary operator start (`?`) but did not find the required colon (`:`) after the center expression'},
\r
2121 TernarySecondExpressionCanNotContainComma: {msg:'The second and third expressions of the ternary operator can/may not "directly" contain a comma operator'},
\r
2122 InvalidRhsExpression: {msg:'Expected a right hand side expression after the operator (which should also be a valid lhs) but did not find one'},
\r
2123 FunctionDeclarationsMustHaveName: {msg:'Function declaration must have name'},
\r
2124 FunctionNameMayNotBeReserved: {msg:'Function name may not be a keyword or reserved word'},
\r
2125 ExpectingFunctionHeaderStart: {msg:'Expected the opening parenthesis of the function header'},
\r
2126 FunctionArgumentsCanNotBeReserved: {msg:'Function arguments may not be keywords or reserved words'},
\r
2127 FunctionParametersMustBeIdentifiers: {msg:'Function arguments must be identifiers'},
\r
2128 ExpectedFunctionHeaderClose: {msg:'Expected the closing parenthesis `)` of the function header'},
\r
2129 ExpectedFunctionBodyCurlyOpen: {msg:'Expected the opening curly brace `{` for the function body'},
\r
2130 ExpectedFunctionBodyCurlyClose: {msg:'Expected the closing curly brace `}` for the function body'},
\r
2131 VarNamesMayOnlyBeIdentifiers: {msg:'Missing variable name, must be a proper identifier'},
\r
2132 VarNamesCanNotBeReserved: {msg:'Variable names may not be keywords or reserved words'},
\r
2133 VarInitialiserExpressionExpected: {msg:'The initialiser of the variable statement should be an expression without comma'},
\r
2134 ExpectedStatementHeaderOpen: {msg:'Expected opening parenthesis `(` for statement header'},
\r
2135 StatementHeaderIsNotOptional: {msg:'Statement header must not be empty'},
\r
2136 ExpectedStatementHeaderClose: {msg:'Expected closing parenthesis `)` for statement header'},
\r
2137 DoShouldBeFollowedByWhile: {msg:'The do-while statement requires the `while` keyword after the expression'},
\r
2138 ExpectedSecondSemiOfForHeader: {msg:'Expected the second semi-colon of the for-each header'},
\r
2139 ForHeaderShouldHaveSemisOrIn: {msg:'The for-header should contain at least the `in` operator or two semi-colons (`;`)'},
\r
2140 SwitchBodyStartsWithCurly: {msg:'The body of a switch statement starts with a curly brace `{`'},
\r
2141 SwitchClausesEndWithColon: {msg:'Switch clauses (`case` and `default`) end with a colon (`:`)'},
\r
2142 SwitchCannotHaveDoubleDefault: {msg:'Switches cannot have more than one `default` clause'},
\r
2143 SwitchBodyEndsWithCurly: {msg:'The body of a switch statement ends with a curly brace `}`'},
\r
2144 MissingTryBlockCurlyOpen: {msg:'Missing the opening curly brace (`{`) for the block of the try statement'},
\r
2145 MissingTryBlockCurlyClose: {msg:'Missing the closing curly brace (`}`) for the block of the try statement'},
\r
2146 CatchHeaderMissingOpen: {msg:'Missing the opening parenthesis of the catch header'},
\r
2147 MissingCatchParameter: {msg:'Catch clauses should have exactly one argument which will be bound to the error object being thrown'},
\r
2148 CatchParameterNameMayNotBeReserved: {msg:'Catch clause parameter may not be a keyword or reserved word'},
\r
2149 CatchHeaderMissingClose: {msg:'Missing the closing parenthesis of the catch header'},
\r
2150 MissingCatchBlockCurlyOpen: {msg:'Missing the opening curly brace (`{`) for the block of the catch statement'},
\r
2151 MissingCatchBlockCurlyClose: {msg:'Missing the closing curly brace (`}`) for the block of the catch statement'},
\r
2152 MissingFinallyBlockCurlyOpen: {msg:'Missing the opening curly brace (`{`) for the block of the finally statement'},
\r
2153 MissingFinallyBlockCurlyClose: {msg:'Missing the closing curly brace (`}`) for the block of the finally statement'},
\r
2154 StatementMayNotStartWithFunction: {msg:'statements may not start with function...', before:true},
\r
2155 BlockCurlyClose: {msg:'Expected the closing curly (`}`) for a block statement'},
\r
2156 BlockCurlyOpen: {msg:'Expected the closing curly (`}`) for a block statement'},
\r
2157 UnableToParseStatement: {msg:'Was unable to find a statement when it was requested'},
\r
2158 IllegalDoubleCommaInObjectLiteral: {msg:'A double comma in object literals is not allowed'},
\r
2159 ObjectLiteralExpectsColonAfterName: {msg:'After every property name (identifier, string or number) a colon (`:`) should follow'},
\r
2160 ThrowMustHaveArgument: {msg:'The expression argument for throw is not optional'},
\r
2161 ThrowCannotHaveReturn: {msg:'There may not be a return between throw and the start of its expression argument'},
\r
2162 SwitchBodyMustStartWithClause: {msg:'The body of a switch clause must start with at a case or default clause (but may be empty, which would be silly)'},
\r
2163 BreakOrContinueArgMustBeJustIdentifier: {msg:'The argument to a break or continue statement must be exactly and only an identifier (an existing label)'},
\r
2164 AssignmentNotAllowedAfterNonAssignmentInExpression: {msg:'An assignment is not allowed if it is preceeded by a non-expression operator in the same expression-level'},
\r
2165 IllegalLhsForAssignment: {msg:'Illegal left hand side for assignment (you cannot assign to things like string literals, number literals or function calls}'},
\r
2166 VarKeywordMissingName: {msg:'Var keyword should be followed by a variable name'},
\r
2167 IllegalTrailingComma: {msg:'Illegal trailing comma found'},
\r
2168 ObjectLiteralMissingPropertyValue: {msg:'Missing object literal property value'},
\r
2169 TokenizerError: {msg:'Tokenizer encountered unexpected input'},
\r
2170 LabelRequiresStatement: {msg:'Saw a label without the (required) statement following'},
\r
2171 DidNotExpectElseHere: {msg:'Did not expect an else here. To what if should it belong? Maybe you put a ; after the if-block? (if(x){};else{})'},
\r
2172 UnexpectedToken: {msg:'Found an unexpected token and have no idea why'},
\r
2173 InvalidPostfixOperandArray: {msg:'You cannot apply ++ or -- to an array'},
\r
2174 InvalidPostfixOperandObject: {msg:'You cannot apply ++ or -- to an object'},
\r
2175 InvalidPostfixOperandFunction: {msg:'You cannot apply ++ or -- to a function'},
\r
2176 CaseMissingExpression: {msg:'Case expects an expression before the colon'},
\r
2177 TryMustHaveCatchOrFinally: {msg:'The try statement must have a catch or finally block'},
\r
2178 UnexpectedInputSwitch: {msg:'Unexpected input while parsing a switch clause...'},
\r
2179 ForInCanOnlyDeclareOnVar: {msg:'For-in header can only introduce one new variable'}
\r