Bug:Fix file validation issue
[vnfsdk/refrepo.git] / vnfmarket / src / main / webapp / vnfmarket / node_modules / zeparser / ZeParser.js
1 if (typeof exports !== 'undefined') {\r
2         var Tokenizer = require('./Tokenizer').Tokenizer;\r
3         exports.ZeParser = ZeParser;\r
4 }\r
5 \r
6 /**\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
13  */\r
14 function ZeParser(inp, tok, stack, simple){\r
15         this.input = inp;\r
16         this.tokenizer = tok;\r
17         this.stack = stack;\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
22 \r
23         this.errorStack = [];\r
24 \r
25         stack.scope = this.scope; // hook root\r
26         stack.labels = this.statementLabels;\r
27 \r
28         this.regexLhsStart = ZeParser.regexLhsStart;\r
29 /*\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
34 */\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
45 \r
46         this.ast = !simple; ///#define FULL_AST\r
47 };\r
48 /**\r
49  * Returns just a stacked parse tree (regular array)\r
50  * @param {string} input\r
51  * @param {boolean} simple=false\r
52  * @return {Array}\r
53  */\r
54 ZeParser.parse = function(input, simple){\r
55         var tok = new Tokenizer(input);\r
56         var stack = [];\r
57         try {\r
58                 var parser = new ZeParser(input, tok, stack);\r
59                 if (simple) parser.ast = false;\r
60                 parser.parse();\r
61                 return stack;\r
62         } catch (e) {\r
63                 console.log("Parser has a bug for this input, please report it :)", e);\r
64                 return null;\r
65         }\r
66 };\r
67 /**\r
68  * Returns a new parser instance with parse details for input\r
69  * @param {string} input\r
70  * @returns {ZeParser}\r
71  */\r
72 ZeParser.createParser = function(input){\r
73         var tok = new Tokenizer(input);\r
74         var stack = [];\r
75         try {\r
76                 var parser = new ZeParser(input, tok, stack);\r
77                 parser.parse();\r
78                 return parser;\r
79         } catch (e) {\r
80                 console.log("Parser has a bug for this input, please report it :)", e);\r
81                 return null;\r
82         }\r
83 };\r
84 ZeParser.prototype = {\r
85         input: null,\r
86         tokenizer: null,\r
87         stack: null,\r
88         scope: null,\r
89         statementLabels: null,\r
90         errorStack: null,\r
91 \r
92         ast: null,\r
93 \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
97 \r
98                 match = this.eatSourceElements(match, this.stack);\r
99 \r
100                 var cycled = false;\r
101                 do {\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
107                                 cycled = true;\r
108                         }\r
109 \r
110                 // keep gobbling any errors...\r
111                 } while (match && match.name == 14/*error*/);\r
112 \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
115 \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
120                 }\r
121 \r
122                 return match;\r
123         },\r
124 \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
128                 else {\r
129                         // try asi\r
130                         // only if:\r
131                         // - this token was preceeded by at least one newline (match.newline) or next token is }\r
132                         // - this is EOF\r
133                         // - prev token was one of return,continue,break,throw (restricted production), not checked here.\r
134 \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
139 \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
143                         } else {\r
144                                 // ASI\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
147                                 stack.push(asi);\r
148                                 \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
152                         }\r
153                 }\r
154                 match.semi = true;\r
155                 return match;\r
156         },\r
157         /**\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
167          * @return {Object}\r
168          */\r
169         eatExpressions: function(mayParseLabeledStatementInstead, match, stack, onlyOne, forHeader, isBreakOrContinueArg){\r
170                 if (this.ast) { //#ifdef FULL_AST\r
171                         var pstack = stack;\r
172                         stack = [];\r
173                         stack.desc = 'expressions';\r
174                         stack.nextBlack = match.tokposb;\r
175                         pstack.push(stack);\r
176 \r
177                         var parsedExpressions = 0;\r
178                 } //#endif\r
179 \r
180                 var first = true;\r
181                 do {\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
184                         if (!first) {\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
187                         }\r
188 \r
189                         if (this.ast) { //#ifdef FULL_AST\r
190                                 ++parsedExpressions;\r
191 \r
192                                 var astack = stack;\r
193                                 stack = [];\r
194                                 stack.desc = 'expression';\r
195                                 stack.nextBlack = match.tokposb;\r
196                                 astack.push(stack);\r
197                         } //#endif\r
198 \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
203 \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
208                                         stack = [];\r
209                                         stack.desc = 'sub-expression';\r
210                                         stack.nextBlack = match.tokposb;\r
211                                         estack.push(stack);\r
212 \r
213                                         var news = 0; // encountered new operators waiting for parenthesis\r
214                                 } //#endif\r
215 \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
223                                 while (\r
224                                         !isBreakOrContinueArg && // no unary for break/continue\r
225                                         (isUnary =\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
228                                         )\r
229                                 ) {\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
234                                         } //#endif\r
235 \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
241                                 };\r
242 \r
243                                 // if we parsed any kind of unary operator, we cannot be parsing a labeled statement\r
244                                 if (parsedUnaryOperator) mayParseLabeledStatementInstead = false;\r
245 \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
249 \r
250                                 var acceptAssignment = false;\r
251 \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
256                                                 stack = [];\r
257                                                 stack.desc = 'grouped';\r
258                                                 stack.nextBlack = match.tokposb;\r
259                                                 groupStack.push(stack);\r
260 \r
261                                                 var lhp = match;\r
262 \r
263                                                 match.isGroupStart = true;\r
264                                         } //#endif\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
269 \r
270                                         if (match.value != ')') match = this.failsafe('UnclosedGroupingOperator', match);\r
271                                         if (this.ast) { //#ifdef FULL_AST\r
272                                                 match.twin = lhp;\r
273                                                 lhp.twin = match;\r
274 \r
275                                                 match.isGroupStop = true;\r
276 \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
280                                                 }\r
281                                         } //#endif\r
282                                         match = this.tokenizer.storeCurrentAndFetchNextToken(true, match, stack); // might be div\r
283 \r
284                                         if (this.ast) { //#ifdef FULL_AST\r
285                                                 stack = groupStack;\r
286                                         } //#endif\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
294                                                 var lhsb = match;\r
295 \r
296                                                 match.isArrayLiteralStart = true;\r
297 \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
301 \r
302                                                 match.targetScope = this.scope;\r
303                                         } //#endif\r
304                                         // keep parsing expressions as long as they are followed by a comma\r
305                                         match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
306 \r
307                                         // arrays may start with "elided" commas\r
308                                         while (match.value == ',') match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
309 \r
310                                         var foundAtLeastOneComma = true; // for entry in while\r
311                                         while (foundAtLeastOneComma && match.value != ']') {\r
312                                                 foundAtLeastOneComma = false;\r
313 \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
316 \r
317                                                 while (match.value == ',') {\r
318                                                         foundAtLeastOneComma = true;\r
319                                                         match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
320                                                 }\r
321                                         }\r
322                                         if (match.value != ']') {\r
323                                                 match = this.failsafe('UnclosedPropertyBracket', match);\r
324                                         }\r
325                                         if (this.ast) { //#ifdef FULL_AST\r
326                                                 match.twin = lhsb;\r
327                                                 lhsb.twin = match;\r
328 \r
329                                                 match.isArrayLiteralStop = true;\r
330                                         } //#endif\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
336                                         }\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
342 \r
343                                                 match.isObjectLiteralStart = true;\r
344 \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
348 \r
349                                                 var targetObject = match;\r
350                                                 match.targetScope = this.scope;\r
351         \r
352                                                 var lhc = match;\r
353                                         } //#endif\r
354 \r
355                                         match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
356                                         if (match.name == 12/*eof*/) {\r
357                                                 match = this.failsafe('ObjectLiteralExpectsColonAfterName', match);\r
358                                         }\r
359                                         // ObjectLiteral\r
360                                         // PropertyNameAndValueList\r
361 \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
368                                                 }\r
369 \r
370                                                 if (this.ast) { //#ifdef FULL_AST\r
371                                                         var objLitStack = stack;\r
372                                                         stack = [];\r
373                                                         stack.desc = 'objlit pair';\r
374                                                         stack.isObjectLiteralPair = true;\r
375                                                         stack.nextBlack = match.tokposb;\r
376                                                         objLitStack.push(stack);\r
377 \r
378                                                         var propNameStack = stack;\r
379                                                         stack = [];\r
380                                                         stack.desc = 'objlit pair name';\r
381                                                         stack.nextBlack = match.tokposb;\r
382                                                         propNameStack.push(stack);\r
383 \r
384                                                         propNameStack.sub = 'data';\r
385 \r
386                                                         var propName = match;\r
387                                                         propName.isPropertyName = true;\r
388                                                 } //#endif\r
389 \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
394                                                 } //#endif\r
395                                                 \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
402                                                                 } //#endif\r
403                                                                 match = this.eatObjectLiteralColonAndBody(match, stack);\r
404                                                         } else {\r
405                                                                 if (this.ast) { //#ifdef FULL_AST\r
406                                                                         match.isPropertyOf = targetObject;\r
407                                                                         propNameStack.sub = 'getter';\r
408                                                                         propNameStack.isAccessor = true;\r
409                                                                 } //#endif\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
415                                                                         var lhp = match;\r
416                                                                 } //#endif\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
420                                                                         match.twin = lhp;\r
421                                                                         lhp.twin = match;\r
422                                                                 } //#endif\r
423                                                                 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
424                                                                 match = this.eatFunctionBody(match, stack);\r
425                                                         }\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
431                                                                 } //#endif\r
432                                                                 match = this.eatObjectLiteralColonAndBody(match, stack);\r
433                                                         } else {\r
434                                                                 if (this.ast) { //#ifdef FULL_AST\r
435                                                                         match.isPropertyOf = targetObject;\r
436                                                                         propNameStack.sub = 'setter';\r
437                                                                         propNameStack.isAccessor = true;\r
438                                                                 } //#endif\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
443                                                                         var lhp = match;\r
444                                                                 } //#endif\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
449                                                                 }\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
454                                                                 }\r
455                                                                 if (this.ast) { //#ifdef FULL_AST\r
456                                                                         match.twin = lhp;\r
457                                                                         lhp.twin = match;\r
458                                                                 } //#endif\r
459                                                                 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
460                                                                 match = this.eatFunctionBody(match, stack);\r
461                                                         }\r
462                                                 } else {\r
463                                                         // PropertyName ":" AssignmentExpression\r
464                                                         if (this.ast) { //#ifdef FULL_AST\r
465                                                                 propName.isPropertyOf = targetObject;\r
466                                                         } //#endif\r
467                                                         match = this.eatObjectLiteralColonAndBody(match, stack);\r
468                                                 }\r
469 \r
470                                                 if (this.ast) { //#ifdef FULL_AST\r
471                                                         stack = objLitStack;\r
472                                                 } //#endif\r
473 \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
479 \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
482                                         }\r
483                                         // closing curly\r
484                                         if (this.ast) { //#ifdef FULL_AST\r
485                                                 match.twin = lhc;\r
486                                                 lhc.twin = match;\r
487 \r
488                                                 match.isObjectLiteralStop = true;\r
489                                         } //#endif\r
490 \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
495                                         }\r
496                                 } else if (match.value == 'function') { // function expression\r
497                                         if (this.ast) { //#ifdef FULL_AST\r
498                                                 var oldstack = stack;\r
499                                                 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
508                                                 // add new scope\r
509                                                 match.scope = stack.scope = this.scope = [\r
510                                                         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
520                                         } //#endif\r
521                                         var funcExprToken = match;\r
522 \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
533                                                 } //#endif\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
536                                         }\r
537                                         match = this.eatFunctionParametersAndBody(match, stack, true, funcExprToken); // first token after func-expr is div\r
538 \r
539                                         while (match.value == '++' || match.value == '--') {\r
540                                                 this.failignore('InvalidPostfixOperandFunction', match, stack);\r
541                                                 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
542                                         }\r
543 \r
544                                         if (this.ast) { //#ifdef FULL_AST\r
545                                                 // restore stack and scope\r
546                                                 stack = oldstack;\r
547                                                 this.scope = oldscope;\r
548                                         } //#endif\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
552 \r
553                                         // validate the identifier, if any\r
554                                         if (match.name == 2/*IDENTIFIER*/) {\r
555                                                 if (\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
560                                                 ) {\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
566                                                         } else {\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
573                                                         }\r
574                                                 }\r
575 \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
579 \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
586                                                 }\r
587                                         } //#endif\r
588 \r
589 \r
590                                         // ok. gobble it.\r
591                                         match = this.tokenizer.storeCurrentAndFetchNextToken(true, match, stack); // division allowed\r
592 \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
599                                                 }\r
600 \r
601                                                 mayParseLabeledStatementInstead = true; // mark label parsed (TOFIX:speed?)\r
602                                                 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
603 \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
607 \r
608                                                         possibleLabel.isLabelDeclaration = true;\r
609                                                         this.statementLabels.push(possibleLabel.value);\r
610 \r
611                                                         stack.desc = 'labeled statement';\r
612                                                 } //#endif\r
613 \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
617 \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
624                                                 }\r
625 \r
626                                                 match.wasLabel = true;\r
627 \r
628                                                 return match;\r
629                                         }\r
630 \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
637                                         do {\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
642                                                         \r
643                                                         this.failSpecial({start:match.start,stop:match.start,name:14/*error*/,error:pe}, match, stack)\r
644                                                 }\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
649                                         return match;\r
650                                 } else {\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
655                                 }\r
656 \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
660 \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
667                                                 } //#endif\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
672                                                         var lhsb = match;\r
673                                                         match.propertyAccessStart = true;\r
674                                                 } //#endif\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
680                                                 }\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
684                                                         match.twin = lhsb;\r
685                                                         match.propertyAccessStop = true;\r
686                                                         lhsb.twin = match;\r
687 \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
691                                                         }\r
692                                                 } //#endif\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
697                                                         var lhp = match;\r
698                                                         match.isCallExpressionStart = true;\r
699                                                         if (news) {\r
700                                                                 match.parensBelongToNew = true;\r
701                                                                 --news;\r
702                                                         }\r
703                                                 } //#endif\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
709                                                         match.twin = lhp;\r
710                                                         lhp.twin = match;\r
711                                                         match.isCallExpressionStop = true;\r
712 \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
716                                                         }\r
717                                                 } //#endif\r
718                                                 match = this.tokenizer.storeCurrentAndFetchNextToken(true, match, stack); // might be div\r
719                                                 acceptAssignment = false;\r
720                                         }\r
721                                 }\r
722 \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
730                                 }\r
731 \r
732                                 if (this.ast) { //#ifdef FULL_AST\r
733                                         // restore "expression" stack\r
734                                         stack = estack;\r
735                                 } //#endif\r
736                                 // now see if there is an operator following...\r
737 \r
738                                 do { // this do allows us to parse multiple ternary expressions in succession without screwing up.\r
739                                         var ternary = false;\r
740                                         if (\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
746                                                 )\r
747                                         ) {\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
751                                                 }\r
752                                                 if (isBreakOrContinueArg) match = this.failsafe('BreakOrContinueArgMustBeJustIdentifier', match);\r
753 \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
759                                                         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
767                                                 } //#endif\r
768                                                 ternary = match.value == '?';\r
769                                                 // math, logic, assignment or in or instanceof\r
770                                                 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
771 \r
772                                                 if (this.ast) { //#ifdef FULL_AST\r
773                                                         // restore "expression" stack\r
774                                                         stack = ostack;\r
775                                                 } //#endif\r
776 \r
777                                                 // minor exception to ternary operator, we need to parse two expressions nao. leave the trailing expression to the loop.\r
778                                                 if (ternary) {\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
783 \r
784                                                         if (match.value != ':') {\r
785                                                                 if (match.value == ',') match = this.failsafe('TernarySecondExpressionCanNotContainComma', match);\r
786                                                                 else match = this.failsafe('UnfinishedTernaryOperator', match);\r
787                                                         }\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
791                                                                 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
798                                                         } //#endif\r
799                                                         match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
800                                                         if (this.ast) { //#ifdef FULL_AST\r
801                                                                 stack = ostack;\r
802                                                         } //#endif\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
805                                                 }\r
806                                         } else {\r
807                                                 parseAnotherExpression = false;\r
808                                         }\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
810 \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
815                                 }\r
816                         }\r
817 \r
818                         if (this.ast) { //#ifdef FULL_AST\r
819                                 // restore "expressions" stack\r
820                                 stack = astack;\r
821                         } //#endif\r
822 \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
827 \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
834                 } //#endif\r
835                 return match;\r
836         },\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
847                         // add new scope\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
854                         ];\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
858                         \r
859                         match.functionStack = stack;\r
860 \r
861                         match.isFuncDeclKeyword = true;\r
862                 } //#endif\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
874                 } //#endif\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
880                 } //#endif\r
881                 return match;\r
882         },\r
883         eatObjectLiteralColonAndBody: function(match, stack){\r
884                 if (this.ast) { //#ifdef FULL_AST\r
885                         var propValueStack = stack;\r
886                         stack = [];\r
887                         stack.desc = 'objlit pair colon';\r
888                         stack.nextBlack = match.tokposb;\r
889                         propValueStack.push(stack);\r
890                 } //#endif\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
895                 } //#endif\r
896 \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
906 \r
907                 return match;\r
908         },\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
913                         var lhp = match;\r
914                         funcToken.lhp = match;\r
915                 } //#endif\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
925                         } //#endif\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
934                                 }\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
940                                 } //#endif\r
941                                 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
942                         }\r
943                 }\r
944                 if (this.ast) { //#ifdef FULL_AST\r
945                         if (lhp) {\r
946                                 match.twin = lhp;\r
947                                 lhp.twin = match;\r
948                                 funcToken.rhp = match;\r
949                         }\r
950                 } //#endif\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
954                 return match;\r
955         },\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
961 \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
966                 } //#endif\r
967 \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
972                         var lhc = match;\r
973                         if (funcToken) funcToken.lhc = lhc;\r
974                 } //#endif\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
979                         match.twin = lhc;\r
980                         lhc.twin = match;\r
981                         if (funcToken) funcToken.rhc = match;\r
982                 } //#endif\r
983                 match = this.tokenizer.storeCurrentAndFetchNextToken(div, match, stack);\r
984 \r
985                 if (this.ast) { //#ifdef FULL_AST\r
986                         // restore label set\r
987                         this.statementLabels = labelBackup;\r
988                 } //#endif\r
989 \r
990                 return match;\r
991         },\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
996                         stack.sub = 'var';\r
997                         stack.nextBlack = match.tokposb;\r
998                         match.stack = stack;\r
999                         match.isVarKeyword = true;\r
1000                 } //#endif\r
1001                 match = this.eatVarDecl(match, stack);\r
1002                 match = this.eatSemiColon(match, stack);\r
1003 \r
1004                 return match;\r
1005         },\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
1012 \r
1013                         var targetScope = this.scope;\r
1014                         while (targetScope.catchScope) targetScope = targetScope[0];\r
1015                 } //#endif\r
1016                 var first = true;\r
1017                 var varsDeclared = 0;\r
1018                 do {\r
1019                         ++varsDeclared;\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
1023                                 var 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
1028 \r
1029                                 var singleDecStack = stack;\r
1030                                 stack = [];\r
1031                                 stack.desc = 'sub-expression';\r
1032                                 stack.nextBlack = match.tokposb;\r
1033                                 singleDecStack.push(stack);\r
1034                         } //#endif\r
1035 \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
1045                         }\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
1050                         } //#endif\r
1051                         match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
1052 \r
1053                         if (this.ast) { //#ifdef FULL_AST\r
1054                                 stack = singleDecStack;\r
1055                         } //#endif\r
1056 \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
1062                                         stack = [];\r
1063                                         stack.desc = 'operator-expression';\r
1064                                         stack.sub = '=';\r
1065                                         stack.nextBlack = match.tokposb;\r
1066                                         singleDecStack.push(stack);\r
1067 \r
1068                                         stack.isAssignment = true;\r
1069                                 } //#endif\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
1074                                 } //#endif\r
1075 \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
1080                         }\r
1081                         if (this.ast) { //#ifdef FULL_AST\r
1082                                 stack = declStack;\r
1083                         } //#endif\r
1084 \r
1085                         // determines proper error message in one case\r
1086                         first = false;\r
1087                 // keep parsing name(=expression) sequences as long as you see a comma here\r
1088                 } while (match.value == ',');\r
1089 \r
1090                 if (this.ast) { //#ifdef FULL_AST\r
1091                         stack.varsDeclared = varsDeclared;\r
1092                 } //#endif\r
1093 \r
1094                 return match;\r
1095         },\r
1096 \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
1101                         stack.sub = 'if';\r
1102                         stack.hasElse = false;\r
1103                         stack.nextBlack = match.tokposb;\r
1104                 } //#endif\r
1105                 // (\r
1106                 // expression\r
1107                 // )\r
1108                 // statement\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
1114                         var lhp = match;\r
1115                         match.statementHeaderStart = true;\r
1116                 } //#endif\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
1122                         match.twin = lhp;\r
1123                         match.statementHeaderStop = true;\r
1124                         lhp.twin = match;\r
1125 \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
1129                         }\r
1130                 } //#endif\r
1131                 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
1132                 match = this.eatStatement(false, match, stack);\r
1133 \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
1138                         } //#endif\r
1139                         match = this.eatElse(match, stack);\r
1140                 }\r
1141 \r
1142                 return match;\r
1143         },\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
1151                 } //#endif\r
1152                 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
1153                 match = this.eatStatement(false, match, stack);\r
1154 \r
1155                 return match;\r
1156         },\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
1161                         stack.sub = 'do';\r
1162                         stack.isIteration = true;\r
1163                         stack.nextBlack = match.tokposb;\r
1164                         this.statementLabels.push(''); // add "empty"\r
1165                         var doToken = match;\r
1166                 } //#endif\r
1167                 // statement\r
1168                 // while\r
1169                 // (\r
1170                 // expression\r
1171                 // )\r
1172                 // semi-colon\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
1178                 } //#endif\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
1182                         var lhp = match;\r
1183                         match.statementHeaderStart = true;\r
1184                 } //#endif\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
1190                         match.twin = lhp;\r
1191                         match.statementHeaderStop = true;\r
1192                         match.isForDoWhile = true; // prevents missing block warnings\r
1193                         lhp.twin = match;\r
1194 \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
1198                         }\r
1199                 } //#endif\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
1202 \r
1203                 return match;\r
1204         },\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
1213                 } //#endif\r
1214 \r
1215                 // (\r
1216                 // expression\r
1217                 // )\r
1218                 // statement\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
1222                         var lhp = match;\r
1223                         match.statementHeaderStart = true;\r
1224                 } //#endif\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
1230                         match.twin = lhp;\r
1231                         match.statementHeaderStop = true;\r
1232                         lhp.twin = match;\r
1233 \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
1237                         }\r
1238                 } //#endif\r
1239                 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
1240                 match = this.eatStatement(false, match, stack);\r
1241 \r
1242                 return match;\r
1243         },\r
1244 \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
1253                 } //#endif\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
1263                         var lhp = match;\r
1264                         match.statementHeaderStart = true;\r
1265                         match.forHeaderStart = true;\r
1266                 } //#endif\r
1267                 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
1268 \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
1275                         }\r
1276                         match = this.eatExpressions(false, match, stack, false, true); // can parse multiple expressions, in is not ok here\r
1277                 }\r
1278 \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
1285                         }\r
1286                         \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
1290                         } //#endif\r
1291 \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
1295                 } else {\r
1296                         if (match.value != ';') match = this.failsafe('ForHeaderShouldHaveSemisOrIn', match);\r
1297 \r
1298                         if (this.ast) { //#ifdef FULL_AST\r
1299                                 stack.forType = 'each';\r
1300                                 match.forEachHeaderStart = true;\r
1301                         } //#endif\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
1308                         } //#endif\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
1311                 }\r
1312 \r
1313                 if (match.value != ')') match = this.failsafe('ExpectedStatementHeaderClose', match);\r
1314                 if (this.ast) { //#ifdef FULL_AST\r
1315                         match.twin = lhp;\r
1316                         match.statementHeaderStop = true;\r
1317                         match.forHeaderStop = true;\r
1318                         lhp.twin = match;\r
1319 \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
1323                         }\r
1324                 } //#endif\r
1325                 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
1326 \r
1327                 match = this.eatStatement(false, match, stack);\r
1328 \r
1329                 return match;\r
1330         },\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
1337 \r
1338                         match.restricted = true;\r
1339                 } //#endif\r
1340                 // (no-line-break identifier)\r
1341                 // ;\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
1347 \r
1348                                 var continueArg = match; // remember to see if this continue parsed a label\r
1349                         } //#endif\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
1354                         } //#endif\r
1355                         if (match.value != ';' && !match.newline && match.name != 12/*eof*/ && match.value != '}') match = this.failsafe('BreakOrContinueArgMustBeJustIdentifier', match);\r
1356                 }\r
1357                 match = this.eatSemiColon(match, stack);\r
1358 \r
1359                 return match;\r
1360         },\r
1361         eatBreak: function(match, stack){\r
1362                 if (this.ast) { //#ifdef FULL_AST\r
1363                         var parentstack = stack\r
1364                         stack = [];\r
1365                         stack.desc = 'statement';\r
1366                         stack.sub = 'break';\r
1367                         stack.nextBlack = match.tokposb;\r
1368                         \r
1369                         parentstack.push(stack);\r
1370 \r
1371                         match.restricted = true;\r
1372                 } //#endif\r
1373                 // (no-line-break identifier)\r
1374                 // ;\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
1381                         } //#endif\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
1386                         } //#endif\r
1387 \r
1388                         if (match.value != ';' && !match.newline && match.name != 12/*eof*/ && match.value != '}') match = this.failsafe('BreakOrContinueArgMustBeJustIdentifier', match);\r
1389                 }\r
1390                 match = this.eatSemiColon(match, stack);\r
1391 \r
1392                 return match;\r
1393         },\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
1401 \r
1402                         match.restricted = true;\r
1403                 } //#endif\r
1404                 // (no-line-break expression)\r
1405                 // ;\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
1409                 }\r
1410                 match = this.eatSemiColon(match, stack);\r
1411 \r
1412                 return match;\r
1413         },\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
1420 \r
1421                         match.restricted = true;\r
1422                 } //#endif\r
1423                 // (no-line-break expression)\r
1424                 // ;\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
1430 \r
1431                 return match;\r
1432         },\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
1439 \r
1440                         this.statementLabels.push(''); // add "empty"\r
1441                 } //#endif\r
1442                 // meh.\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
1446                         var lhp = match;\r
1447                         match.statementHeaderStart = true;\r
1448                 } //#endif\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
1452                 }\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
1456                         match.twin = lhp;\r
1457                         match.statementHeaderStop = true;\r
1458                         lhp.twin = match;\r
1459 \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
1463                         }\r
1464                 } //#endif\r
1465                 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
1466                 if (match.value != '{') match = this.failsafe('SwitchBodyStartsWithCurly', match);\r
1467 \r
1468                 if (this.ast) { //#ifdef FULL_AST\r
1469                         var lhc = match;\r
1470                 } //#endif\r
1471                 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
1472 \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
1475 \r
1476                 while (match.value == 'case' || (!stack.parsedSwitchDefault && match.value == 'default')) {\r
1477                         parsedAnything = true;\r
1478 \r
1479                         match = this.eatSwitchClause(match, stack);\r
1480                 }\r
1481 \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
1485                 }\r
1486 \r
1487                 if (stack.parsedSwitchDefault && match.value == 'default') {\r
1488                         this.failignore('SwitchCannotHaveDoubleDefault', match, stack);\r
1489                 }\r
1490 \r
1491                 if (match.value != '}' && match.name != 14/*error*/) match = this.failsafe('SwitchBodyEndsWithCurly', match);\r
1492 \r
1493                 if (this.ast) { //#ifdef FULL_AST\r
1494                         match.twin = lhc;\r
1495                         lhc.twin = match;\r
1496                 } //#endif\r
1497                 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
1498 \r
1499                 return match;\r
1500         },\r
1501         eatSwitchClause: function(match, stack){\r
1502                 match = this.eatSwitchHeader(match, stack);\r
1503                 match = this.eatSwitchBody(match, stack);\r
1504 \r
1505                 return match;\r
1506         },\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
1514                 } //#endif\r
1515 \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
1521                         } //#endif\r
1522                         match = this.eatSwitchDefaultHead(match, stack);\r
1523                 }\r
1524 \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
1530                 } //#endif\r
1531 \r
1532                 if (match.value != ':') {\r
1533                         match = this.failsafe('SwitchClausesEndWithColon', match);\r
1534                 }\r
1535                 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
1536 \r
1537                 return match;\r
1538         },\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
1544                 } //#endif\r
1545 \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
1552                 }\r
1553                 if (lastMatch == match) this.failsafe('UnexpectedInputSwitch', match);\r
1554 \r
1555                 return match;\r
1556         },\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
1561 \r
1562                         stack.push(stack = []);\r
1563                         stack.desc = 'case';\r
1564                         stack.nextBlack = match.tokposb;\r
1565 \r
1566                         match.isCase = true;\r
1567                 } //#endif\r
1568                 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
1569 \r
1570                 if (match.value == ':') {\r
1571                         this.failignore('CaseMissingExpression', match, stack);\r
1572                 } else {\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
1577                         } //#endif\r
1578                         match = this.eatExpressions(false, match, stack);\r
1579                 }\r
1580 \r
1581                 return match;\r
1582         },\r
1583         eatSwitchDefaultHead: function(match, stack){\r
1584                 if (this.ast) { //#ifdef FULL_AST\r
1585                         stack.sub = 'default';\r
1586 \r
1587                         stack.push(stack = []);\r
1588                         stack.desc = 'case';\r
1589                         stack.nextBlack = match.tokposb;\r
1590 \r
1591                         match.isDefault = true;\r
1592                 } //#endif\r
1593                 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
1594 \r
1595                 return match;\r
1596         },\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
1603                 } //#endif\r
1604 \r
1605                 match = this.eatTry(match, stack);\r
1606 \r
1607                 if (match.value == 'catch') {\r
1608                         if (this.ast) { //#ifdef FULL_AST\r
1609                                 stack.hasCatch = true;\r
1610                         } //#endif\r
1611                         match = this.eatCatch(match, stack);\r
1612                 }\r
1613                 if (match.value == 'finally') {\r
1614                         if (this.ast) { //#ifdef FULL_AST\r
1615                                 stack.hasFinally = true;\r
1616                         } //#endif\r
1617                         match = this.eatFinally(match, stack);\r
1618                 }\r
1619 \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
1623                 }\r
1624 \r
1625                 return match;\r
1626         },\r
1627         eatTry: function(match, stack){\r
1628                 // block\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
1633 \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
1639                         var lhc = match;\r
1640                 } //#endif\r
1641 \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
1645 \r
1646                 if (this.ast) { //#ifdef FULL_AST\r
1647                         match.twin = lhc;\r
1648                         lhc.twin = match;\r
1649                 } //#endif\r
1650                 \r
1651                 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
1652 \r
1653                 return match;\r
1654         },\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
1662 \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
1669 \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
1673 \r
1674                         // find first function scope or global scope object...\r
1675                         var nonCatchScope = catchScopeBackup;\r
1676                         while (nonCatchScope.catchScope) nonCatchScope = nonCatchScope[0];\r
1677 \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
1684 \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
1695                 } //#endif\r
1696 \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
1700                         var lhp = match;\r
1701                 } //#endif\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
1706                 }\r
1707 \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
1714                 } //#endif\r
1715 \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
1720 \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
1724                         match.twin = lhp;\r
1725                         lhp.twin = match;\r
1726                 } //#endif\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
1730                         var lhc = match;\r
1731                 } //#endif\r
1732                 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
1733 \r
1734                 // catch body. statements are optional. \r
1735                 if (match.value != '}') match = this.eatStatements(match, stack);\r
1736 \r
1737                 if (match.value != '}') match = this.failsafe('MissingCatchBlockCurlyClose', match);\r
1738                 if (this.ast) { //#ifdef FULL_AST\r
1739                         match.twin = lhc;\r
1740                         lhc.twin = match;\r
1741                 } //#endif\r
1742                 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
1743 \r
1744                 if (this.ast) { //#ifdef FULL_AST\r
1745                         this.scope = catchScopeBackup;\r
1746                 } //#endif\r
1747 \r
1748                 return match;\r
1749         },\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
1757                 } //#endif\r
1758 \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
1762                         var lhc = match;\r
1763                 } //#endif\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
1768                         match.twin = lhc;\r
1769                         lhc.twin = match;\r
1770                 } //#endif\r
1771                 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
1772 \r
1773                 return match;\r
1774         },\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
1781                 } //#endif\r
1782                 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
1783                 match = this.eatSemiColon(match, stack);\r
1784 \r
1785                 return match;\r
1786         },\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
1793                 } //#endif\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
1797                         var lhp = match;\r
1798                         match.statementHeaderStart = true;\r
1799                 } //#endif\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
1805                         match.twin = lhp;\r
1806                         match.statementHeaderStop = true;\r
1807                         lhp.twin = match;\r
1808 \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
1812                         }\r
1813                 } //#endif\r
1814                 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
1815                 match = this.eatStatement(false, match, stack);\r
1816 \r
1817                 return match;\r
1818         },\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
1827 \r
1828                 return match;\r
1829         },\r
1830         eatLabelOrExpression: function(match, stack){\r
1831                 if (this.ast) { //#ifdef FULL_AST\r
1832                         var parentstack = stack;\r
1833 \r
1834                         stack = [];\r
1835                         stack.desc = 'statement';\r
1836                         stack.sub = 'expression';\r
1837                         stack.nextBlack = match.tokposb;\r
1838                         parentstack.push(stack);\r
1839                 } //#endif\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
1845 \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
1851                         } //#endif\r
1852                         // it will have already eaten another statement for the label\r
1853                 } else {\r
1854                         if (this.ast) { //#ifdef FULL_AST\r
1855                                 stack.sub = 'expression';\r
1856                         } //#endif\r
1857                         // only parse semi if we didnt parse a label just now...\r
1858                         match = this.eatSemiColon(match, stack);\r
1859                 }\r
1860 \r
1861                 return match;\r
1862         },\r
1863         eatBlock: function(match, stack){\r
1864                 if (this.ast) { //#ifdef FULL_AST\r
1865                         stack.sub = 'block';\r
1866                         var lhc = match;\r
1867                 } //#endif\r
1868 \r
1869                 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
1870 \r
1871                 if (match.value == '}') {\r
1872                         if (this.ast) { //#ifdef FULL_AST\r
1873                                 stack.isEmptyBlock = true;\r
1874                         } //#endif\r
1875                 } else {\r
1876                         match = this.eatStatements(match, stack);\r
1877                 }\r
1878                 if (match.value != '}') match = this.failsafe('BlockCurlyClose', match);\r
1879                 if (this.ast) { //#ifdef FULL_AST\r
1880                         match.twin = lhc;\r
1881                         lhc.twin = match;\r
1882                 } //#endif\r
1883                 match = this.tokenizer.storeCurrentAndFetchNextToken(false, match, stack);\r
1884 \r
1885                 return match;\r
1886         },\r
1887 \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
1894 \r
1895                 while (match != (match = this.eatStatement(true, match, stack)));\r
1896                 return match;\r
1897         },\r
1898         eatStatement: function(isOptional, match, stack){\r
1899                 if (!match && isOptional) return match; // eof\r
1900 \r
1901                 if (this.ast) { //#ifdef FULL_AST\r
1902                         match.statementStart = true;\r
1903                         var pstack = stack;\r
1904                         stack = [];\r
1905                         stack.desc = 'statement-parent';\r
1906                         stack.nextBlack = match.tokposb;\r
1907                         pstack.push(stack);\r
1908 \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
1913                 } //#endif\r
1914 \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
1919                                 case 'var':\r
1920                                         match = this.eatVar(match, stack);\r
1921                                         break;\r
1922                                 case 'if':\r
1923                                         match = this.eatIf(match, stack);\r
1924                                         break;\r
1925                                 case 'do':\r
1926                                         match = this.eatDo(match, stack);\r
1927                                         break;\r
1928                                 case 'while':\r
1929                                         match = this.eatWhile(match, stack);\r
1930                                         break;\r
1931                                 case 'for':\r
1932                                         match = this.eatFor(match, stack);\r
1933                                         break;\r
1934                                 case 'continue':\r
1935                                         match = this.eatContinue(match, stack);\r
1936                                         break;\r
1937                                 case 'break':\r
1938                                         match = this.eatBreak(match, stack);\r
1939                                         break;\r
1940                                 case 'return':\r
1941                                         match = this.eatReturn(match, stack);\r
1942                                         break;\r
1943                                 case 'throw':\r
1944                                         match = this.eatThrow(match, stack);\r
1945                                         break;\r
1946                                 case 'switch':\r
1947                                         match = this.eatSwitch(match, stack);\r
1948                                         break;\r
1949                                 case 'try':\r
1950                                         match = this.eatTryCatchFinally(match, stack);\r
1951                                         break;\r
1952                                 case 'debugger':\r
1953                                         match = this.eatDebugger(match, stack);\r
1954                                         break;\r
1955                                 case 'with':\r
1956                                         match = this.eatWith(match, stack);\r
1957                                         break;\r
1958                                 case 'function':\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
1962 \r
1963                                         // now parse as declaration... (most likely?)\r
1964                                         match = this.eatFunctionDeclaration(match, stack);\r
1965 \r
1966                                         break;\r
1967                                 default: // either a label or an expression-statement\r
1968                                         match = this.eatLabelOrExpression(match, stack);\r
1969                         }\r
1970                 } else if (match.value == '{') { // Block (make sure you do this before checking for expression...)\r
1971                         match = this.eatBlock(match, stack);\r
1972                 } else if (\r
1973                         // expression statements:\r
1974                         match.isString ||\r
1975                         match.isNumber ||\r
1976                         match.name == 1/*REG_EX*/ ||\r
1977                         this.regexLhsStart.test(match.value)\r
1978                 ) {\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
1988                         } //#endif\r
1989                         match = this.failsafe('UnableToParseStatement', match);\r
1990                 } else {\r
1991                         // unmark token as being start of a statement, since it's obviously not\r
1992                         if (this.ast) match.statementStart = true;\r
1993                 }\r
1994 \r
1995                 if (this.ast) { //#ifdef FULL_AST\r
1996                         if (!stack.length) pstack.length = pstack.length-1;\r
1997 \r
1998                         // restore label set\r
1999                         this.statementLabels = labelBackup;\r
2000                 } //#endif\r
2001 \r
2002                 return match;\r
2003         },\r
2004 \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
2016                 }\r
2017                 return match;\r
2018         },\r
2019 \r
2020         failsafe: function(name, match, doNotAddMatch){\r
2021                 var pe = new ZeParser.Error(name, match);\r
2022                 this.errorStack.push(pe);\r
2023 \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
2029                 }\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
2032                 return error;\r
2033         },\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
2041         },\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
2046         },\r
2047 \r
2048 0:0};\r
2049 \r
2050 //#ifdef TEST_SUITE\r
2051 ZeParser.testSuite = function(tests){\r
2052         var ok = 0;\r
2053         var fail = 0;\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
2057                 try {\r
2058                         new ZeParser(input, new Tokenizer(input), stack).parse();\r
2059                         ++ok;\r
2060                 } catch (e) {\r
2061                         ++fail;\r
2062                 }\r
2063                 document.getElementsByTagName('div')[0].innerHTML = ('Ze parser test suite finished ('+(+new Date - start)+' ms). ok:'+ok+', fail:'+fail);\r
2064         };\r
2065 };\r
2066 //#endif\r
2067 \r
2068 ZeParser.regexLhsStart = /[\+\-\~\!\(\{\[]/;\r
2069 /*\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
2074 */\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
2084 \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
2090 };\r
2091 \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
2180 };\r