OLD | NEW |
(Empty) | |
| 1 <script> |
| 2 |
| 3 /* |
| 4 Copyright (C) 2013 Ariya Hidayat <ariya.hidayat@gmail.com> |
| 5 Copyright (C) 2013 Thaddee Tyl <thaddee.tyl@gmail.com> |
| 6 Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com> |
| 7 Copyright (C) 2012 Mathias Bynens <mathias@qiwi.be> |
| 8 Copyright (C) 2012 Joost-Wim Boekesteijn <joost-wim@boekesteijn.nl> |
| 9 Copyright (C) 2012 Kris Kowal <kris.kowal@cixar.com> |
| 10 Copyright (C) 2012 Yusuke Suzuki <utatane.tea@gmail.com> |
| 11 Copyright (C) 2012 Arpad Borsos <arpad.borsos@googlemail.com> |
| 12 Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com> |
| 13 |
| 14 Redistribution and use in source and binary forms, with or without |
| 15 modification, are permitted provided that the following conditions are met: |
| 16 |
| 17 * Redistributions of source code must retain the above copyright |
| 18 notice, this list of conditions and the following disclaimer. |
| 19 * Redistributions in binary form must reproduce the above copyright |
| 20 notice, this list of conditions and the following disclaimer in the |
| 21 documentation and/or other materials provided with the distribution. |
| 22 |
| 23 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| 24 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 25 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| 26 ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY |
| 27 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| 28 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| 29 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| 30 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 31 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| 32 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 33 */ |
| 34 |
| 35 var Token, |
| 36 TokenName, |
| 37 Syntax, |
| 38 Messages, |
| 39 source, |
| 40 index, |
| 41 length, |
| 42 delegate, |
| 43 lookahead, |
| 44 state; |
| 45 |
| 46 Token = { |
| 47 BooleanLiteral: 1, |
| 48 EOF: 2, |
| 49 Identifier: 3, |
| 50 Keyword: 4, |
| 51 NullLiteral: 5, |
| 52 NumericLiteral: 6, |
| 53 Punctuator: 7, |
| 54 StringLiteral: 8 |
| 55 }; |
| 56 |
| 57 TokenName = {}; |
| 58 TokenName[Token.BooleanLiteral] = 'Boolean'; |
| 59 TokenName[Token.EOF] = '<end>'; |
| 60 TokenName[Token.Identifier] = 'Identifier'; |
| 61 TokenName[Token.Keyword] = 'Keyword'; |
| 62 TokenName[Token.NullLiteral] = 'Null'; |
| 63 TokenName[Token.NumericLiteral] = 'Numeric'; |
| 64 TokenName[Token.Punctuator] = 'Punctuator'; |
| 65 TokenName[Token.StringLiteral] = 'String'; |
| 66 |
| 67 Syntax = { |
| 68 ArrayExpression: 'ArrayExpression', |
| 69 BinaryExpression: 'BinaryExpression', |
| 70 CallExpression: 'CallExpression', |
| 71 ConditionalExpression: 'ConditionalExpression', |
| 72 EmptyStatement: 'EmptyStatement', |
| 73 ExpressionStatement: 'ExpressionStatement', |
| 74 Identifier: 'Identifier', |
| 75 Literal: 'Literal', |
| 76 LabeledStatement: 'LabeledStatement', |
| 77 LogicalExpression: 'LogicalExpression', |
| 78 MemberExpression: 'MemberExpression', |
| 79 ObjectExpression: 'ObjectExpression', |
| 80 Program: 'Program', |
| 81 Property: 'Property', |
| 82 ThisExpression: 'ThisExpression', |
| 83 UnaryExpression: 'UnaryExpression' |
| 84 }; |
| 85 |
| 86 // Error messages should be identical to V8. |
| 87 Messages = { |
| 88 UnexpectedToken: 'Unexpected token %0', |
| 89 UnknownLabel: 'Undefined label \'%0\'', |
| 90 Redeclaration: '%0 \'%1\' has already been declared' |
| 91 }; |
| 92 |
| 93 // Ensure the condition is true, otherwise throw an error. |
| 94 // This is only to have a better contract semantic, i.e. another safety net |
| 95 // to catch a logic error. The condition shall be fulfilled in normal case. |
| 96 // Do NOT use this to enforce a certain condition on any user input. |
| 97 |
| 98 function assert(condition, message) { |
| 99 if (!condition) { |
| 100 throw new Error('ASSERT: ' + message); |
| 101 } |
| 102 } |
| 103 |
| 104 function isDecimalDigit(ch) { |
| 105 return (ch >= 48 && ch <= 57); // 0..9 |
| 106 } |
| 107 |
| 108 |
| 109 // 7.2 White Space |
| 110 |
| 111 function isWhiteSpace(ch) { |
| 112 return (ch === 32) || // space |
| 113 (ch === 9) || // tab |
| 114 (ch === 0xB) || |
| 115 (ch === 0xC) || |
| 116 (ch === 0xA0) || |
| 117 (ch >= 0x1680 && '\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006
\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF'.indexOf(String.fromCharCode(ch
)) > 0); |
| 118 } |
| 119 |
| 120 // 7.3 Line Terminators |
| 121 |
| 122 function isLineTerminator(ch) { |
| 123 return (ch === 10) || (ch === 13) || (ch === 0x2028) || (ch === 0x2029); |
| 124 } |
| 125 |
| 126 // 7.6 Identifier Names and Identifiers |
| 127 |
| 128 function isIdentifierStart(ch) { |
| 129 return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) |
| 130 (ch >= 65 && ch <= 90) || // A..Z |
| 131 (ch >= 97 && ch <= 122); // a..z |
| 132 } |
| 133 |
| 134 function isIdentifierPart(ch) { |
| 135 return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore) |
| 136 (ch >= 65 && ch <= 90) || // A..Z |
| 137 (ch >= 97 && ch <= 122) || // a..z |
| 138 (ch >= 48 && ch <= 57); // 0..9 |
| 139 } |
| 140 |
| 141 // 7.6.1.1 Keywords |
| 142 |
| 143 function isKeyword(id) { |
| 144 return (id === 'this') |
| 145 } |
| 146 |
| 147 // 7.4 Comments |
| 148 |
| 149 function skipWhitespace() { |
| 150 while (index < length && isWhiteSpace(source.charCodeAt(index))) { |
| 151 ++index; |
| 152 } |
| 153 } |
| 154 |
| 155 function getIdentifier() { |
| 156 var start, ch; |
| 157 |
| 158 start = index++; |
| 159 while (index < length) { |
| 160 ch = source.charCodeAt(index); |
| 161 if (isIdentifierPart(ch)) { |
| 162 ++index; |
| 163 } else { |
| 164 break; |
| 165 } |
| 166 } |
| 167 |
| 168 return source.slice(start, index); |
| 169 } |
| 170 |
| 171 function scanIdentifier() { |
| 172 var start, id, type; |
| 173 |
| 174 start = index; |
| 175 |
| 176 id = getIdentifier(); |
| 177 |
| 178 // There is no keyword or literal with only one character. |
| 179 // Thus, it must be an identifier. |
| 180 if (id.length === 1) { |
| 181 type = Token.Identifier; |
| 182 } else if (isKeyword(id)) { |
| 183 type = Token.Keyword; |
| 184 } else if (id === 'null') { |
| 185 type = Token.NullLiteral; |
| 186 } else if (id === 'true' || id === 'false') { |
| 187 type = Token.BooleanLiteral; |
| 188 } else { |
| 189 type = Token.Identifier; |
| 190 } |
| 191 |
| 192 return { |
| 193 type: type, |
| 194 value: id, |
| 195 range: [start, index] |
| 196 }; |
| 197 } |
| 198 |
| 199 |
| 200 // 7.7 Punctuators |
| 201 |
| 202 function scanPunctuator() { |
| 203 var start = index, |
| 204 code = source.charCodeAt(index), |
| 205 code2, |
| 206 ch1 = source[index], |
| 207 ch2; |
| 208 |
| 209 switch (code) { |
| 210 |
| 211 // Check for most common single-character punctuators. |
| 212 case 46: // . dot |
| 213 case 40: // ( open bracket |
| 214 case 41: // ) close bracket |
| 215 case 59: // ; semicolon |
| 216 case 44: // , comma |
| 217 case 123: // { open curly brace |
| 218 case 125: // } close curly brace |
| 219 case 91: // [ |
| 220 case 93: // ] |
| 221 case 58: // : |
| 222 case 63: // ? |
| 223 ++index; |
| 224 return { |
| 225 type: Token.Punctuator, |
| 226 value: String.fromCharCode(code), |
| 227 range: [start, index] |
| 228 }; |
| 229 |
| 230 default: |
| 231 code2 = source.charCodeAt(index + 1); |
| 232 |
| 233 // '=' (char #61) marks an assignment or comparison operator. |
| 234 if (code2 === 61) { |
| 235 switch (code) { |
| 236 case 37: // % |
| 237 case 38: // & |
| 238 case 42: // *: |
| 239 case 43: // + |
| 240 case 45: // - |
| 241 case 47: // / |
| 242 case 60: // < |
| 243 case 62: // > |
| 244 case 124: // | |
| 245 index += 2; |
| 246 return { |
| 247 type: Token.Punctuator, |
| 248 value: String.fromCharCode(code) + String.fromCharCode(code2
), |
| 249 range: [start, index] |
| 250 }; |
| 251 |
| 252 case 33: // ! |
| 253 case 61: // = |
| 254 index += 2; |
| 255 |
| 256 // !== and === |
| 257 if (source.charCodeAt(index) === 61) { |
| 258 ++index; |
| 259 } |
| 260 return { |
| 261 type: Token.Punctuator, |
| 262 value: source.slice(start, index), |
| 263 range: [start, index] |
| 264 }; |
| 265 default: |
| 266 break; |
| 267 } |
| 268 } |
| 269 break; |
| 270 } |
| 271 |
| 272 // Peek more characters. |
| 273 |
| 274 ch2 = source[index + 1]; |
| 275 |
| 276 // Other 2-character punctuators: && || |
| 277 |
| 278 if (ch1 === ch2 && ('&|'.indexOf(ch1) >= 0)) { |
| 279 index += 2; |
| 280 return { |
| 281 type: Token.Punctuator, |
| 282 value: ch1 + ch2, |
| 283 range: [start, index] |
| 284 }; |
| 285 } |
| 286 |
| 287 if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) { |
| 288 ++index; |
| 289 return { |
| 290 type: Token.Punctuator, |
| 291 value: ch1, |
| 292 range: [start, index] |
| 293 }; |
| 294 } |
| 295 |
| 296 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); |
| 297 } |
| 298 |
| 299 // 7.8.3 Numeric Literals |
| 300 function scanNumericLiteral() { |
| 301 var number, start, ch; |
| 302 |
| 303 ch = source[index]; |
| 304 assert(isDecimalDigit(ch.charCodeAt(0)) || (ch === '.'), |
| 305 'Numeric literal must start with a decimal digit or a decimal point'); |
| 306 |
| 307 start = index; |
| 308 number = ''; |
| 309 if (ch !== '.') { |
| 310 number = source[index++]; |
| 311 ch = source[index]; |
| 312 |
| 313 // Hex number starts with '0x'. |
| 314 // Octal number starts with '0'. |
| 315 if (number === '0') { |
| 316 // decimal number starts with '0' such as '09' is illegal. |
| 317 if (ch && isDecimalDigit(ch.charCodeAt(0))) { |
| 318 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); |
| 319 } |
| 320 } |
| 321 |
| 322 while (isDecimalDigit(source.charCodeAt(index))) { |
| 323 number += source[index++]; |
| 324 } |
| 325 ch = source[index]; |
| 326 } |
| 327 |
| 328 if (ch === '.') { |
| 329 number += source[index++]; |
| 330 while (isDecimalDigit(source.charCodeAt(index))) { |
| 331 number += source[index++]; |
| 332 } |
| 333 ch = source[index]; |
| 334 } |
| 335 |
| 336 if (ch === 'e' || ch === 'E') { |
| 337 number += source[index++]; |
| 338 |
| 339 ch = source[index]; |
| 340 if (ch === '+' || ch === '-') { |
| 341 number += source[index++]; |
| 342 } |
| 343 if (isDecimalDigit(source.charCodeAt(index))) { |
| 344 while (isDecimalDigit(source.charCodeAt(index))) { |
| 345 number += source[index++]; |
| 346 } |
| 347 } else { |
| 348 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); |
| 349 } |
| 350 } |
| 351 |
| 352 if (isIdentifierStart(source.charCodeAt(index))) { |
| 353 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); |
| 354 } |
| 355 |
| 356 return { |
| 357 type: Token.NumericLiteral, |
| 358 value: parseFloat(number), |
| 359 range: [start, index] |
| 360 }; |
| 361 } |
| 362 |
| 363 // 7.8.4 String Literals |
| 364 |
| 365 function scanStringLiteral() { |
| 366 var str = '', quote, start, ch, octal = false; |
| 367 |
| 368 quote = source[index]; |
| 369 assert((quote === '\'' || quote === '"'), |
| 370 'String literal must starts with a quote'); |
| 371 |
| 372 start = index; |
| 373 ++index; |
| 374 |
| 375 while (index < length) { |
| 376 ch = source[index++]; |
| 377 |
| 378 if (ch === quote) { |
| 379 quote = ''; |
| 380 break; |
| 381 } else if (ch === '\\') { |
| 382 ch = source[index++]; |
| 383 if (!ch || !isLineTerminator(ch.charCodeAt(0))) { |
| 384 switch (ch) { |
| 385 case 'n': |
| 386 str += '\n'; |
| 387 break; |
| 388 case 'r': |
| 389 str += '\r'; |
| 390 break; |
| 391 case 't': |
| 392 str += '\t'; |
| 393 break; |
| 394 case 'b': |
| 395 str += '\b'; |
| 396 break; |
| 397 case 'f': |
| 398 str += '\f'; |
| 399 break; |
| 400 case 'v': |
| 401 str += '\x0B'; |
| 402 break; |
| 403 |
| 404 default: |
| 405 str += ch; |
| 406 break; |
| 407 } |
| 408 } else { |
| 409 if (ch === '\r' && source[index] === '\n') { |
| 410 ++index; |
| 411 } |
| 412 } |
| 413 } else if (isLineTerminator(ch.charCodeAt(0))) { |
| 414 break; |
| 415 } else { |
| 416 str += ch; |
| 417 } |
| 418 } |
| 419 |
| 420 if (quote !== '') { |
| 421 throwError({}, Messages.UnexpectedToken, 'ILLEGAL'); |
| 422 } |
| 423 |
| 424 return { |
| 425 type: Token.StringLiteral, |
| 426 value: str, |
| 427 octal: octal, |
| 428 range: [start, index] |
| 429 }; |
| 430 } |
| 431 |
| 432 function isIdentifierName(token) { |
| 433 return token.type === Token.Identifier || |
| 434 token.type === Token.Keyword || |
| 435 token.type === Token.BooleanLiteral || |
| 436 token.type === Token.NullLiteral; |
| 437 } |
| 438 |
| 439 function advance() { |
| 440 var ch; |
| 441 |
| 442 skipWhitespace(); |
| 443 |
| 444 if (index >= length) { |
| 445 return { |
| 446 type: Token.EOF, |
| 447 range: [index, index] |
| 448 }; |
| 449 } |
| 450 |
| 451 ch = source.charCodeAt(index); |
| 452 |
| 453 // Very common: ( and ) and ; |
| 454 if (ch === 40 || ch === 41 || ch === 58) { |
| 455 return scanPunctuator(); |
| 456 } |
| 457 |
| 458 // String literal starts with single quote (#39) or double quote (#34). |
| 459 if (ch === 39 || ch === 34) { |
| 460 return scanStringLiteral(); |
| 461 } |
| 462 |
| 463 if (isIdentifierStart(ch)) { |
| 464 return scanIdentifier(); |
| 465 } |
| 466 |
| 467 // Dot (.) char #46 can also start a floating-point number, hence the need |
| 468 // to check the next character. |
| 469 if (ch === 46) { |
| 470 if (isDecimalDigit(source.charCodeAt(index + 1))) { |
| 471 return scanNumericLiteral(); |
| 472 } |
| 473 return scanPunctuator(); |
| 474 } |
| 475 |
| 476 if (isDecimalDigit(ch)) { |
| 477 return scanNumericLiteral(); |
| 478 } |
| 479 |
| 480 return scanPunctuator(); |
| 481 } |
| 482 |
| 483 function lex() { |
| 484 var token; |
| 485 |
| 486 token = lookahead; |
| 487 index = token.range[1]; |
| 488 |
| 489 lookahead = advance(); |
| 490 |
| 491 index = token.range[1]; |
| 492 |
| 493 return token; |
| 494 } |
| 495 |
| 496 function peek() { |
| 497 var pos; |
| 498 |
| 499 pos = index; |
| 500 lookahead = advance(); |
| 501 index = pos; |
| 502 } |
| 503 |
| 504 // Throw an exception |
| 505 |
| 506 function throwError(token, messageFormat) { |
| 507 var error, |
| 508 args = Array.prototype.slice.call(arguments, 2), |
| 509 msg = messageFormat.replace( |
| 510 /%(\d)/g, |
| 511 function (whole, index) { |
| 512 assert(index < args.length, 'Message reference must be in range'
); |
| 513 return args[index]; |
| 514 } |
| 515 ); |
| 516 |
| 517 error = new Error(msg); |
| 518 error.index = index; |
| 519 error.description = msg; |
| 520 throw error; |
| 521 } |
| 522 |
| 523 // Throw an exception because of the token. |
| 524 |
| 525 function throwUnexpected(token) { |
| 526 throwError(token, Messages.UnexpectedToken, token.value); |
| 527 } |
| 528 |
| 529 // Expect the next token to match the specified punctuator. |
| 530 // If not, an exception will be thrown. |
| 531 |
| 532 function expect(value) { |
| 533 var token = lex(); |
| 534 if (token.type !== Token.Punctuator || token.value !== value) { |
| 535 throwUnexpected(token); |
| 536 } |
| 537 } |
| 538 |
| 539 // Return true if the next token matches the specified punctuator. |
| 540 |
| 541 function match(value) { |
| 542 return lookahead.type === Token.Punctuator && lookahead.value === value; |
| 543 } |
| 544 |
| 545 // Return true if the next token matches the specified keyword |
| 546 |
| 547 function matchKeyword(keyword) { |
| 548 return lookahead.type === Token.Keyword && lookahead.value === keyword; |
| 549 } |
| 550 |
| 551 function consumeSemicolon() { |
| 552 // Catch the very common case first: immediately a semicolon (char #59). |
| 553 if (source.charCodeAt(index) === 59) { |
| 554 lex(); |
| 555 return; |
| 556 } |
| 557 |
| 558 skipWhitespace(); |
| 559 |
| 560 if (match(';')) { |
| 561 lex(); |
| 562 return; |
| 563 } |
| 564 |
| 565 if (lookahead.type !== Token.EOF && !match('}')) { |
| 566 throwUnexpected(lookahead); |
| 567 } |
| 568 } |
| 569 |
| 570 // 11.1.4 Array Initialiser |
| 571 |
| 572 function parseArrayInitialiser() { |
| 573 var elements = []; |
| 574 |
| 575 expect('['); |
| 576 |
| 577 while (!match(']')) { |
| 578 if (match(',')) { |
| 579 lex(); |
| 580 elements.push(null); |
| 581 } else { |
| 582 elements.push(parseExpression()); |
| 583 |
| 584 if (!match(']')) { |
| 585 expect(','); |
| 586 } |
| 587 } |
| 588 } |
| 589 |
| 590 expect(']'); |
| 591 |
| 592 return delegate.createArrayExpression(elements); |
| 593 } |
| 594 |
| 595 // 11.1.5 Object Initialiser |
| 596 |
| 597 function parseObjectPropertyKey() { |
| 598 var token; |
| 599 |
| 600 skipWhitespace(); |
| 601 token = lex(); |
| 602 |
| 603 // Note: This function is called only from parseObjectProperty(), where |
| 604 // EOF and Punctuator tokens are already filtered out. |
| 605 if (token.type === Token.StringLiteral || token.type === Token.NumericLitera
l) { |
| 606 return delegate.createLiteral(token); |
| 607 } |
| 608 |
| 609 return delegate.createIdentifier(token.value); |
| 610 } |
| 611 |
| 612 function parseObjectProperty() { |
| 613 var token, key; |
| 614 |
| 615 token = lookahead; |
| 616 skipWhitespace(); |
| 617 |
| 618 if (token.type === Token.EOF || token.type === Token.Punctuator) { |
| 619 throwUnexpected(token); |
| 620 } |
| 621 |
| 622 key = parseObjectPropertyKey(); |
| 623 expect(':'); |
| 624 return delegate.createProperty('init', key, parseExpression()); |
| 625 } |
| 626 |
| 627 function parseObjectInitialiser() { |
| 628 var properties = []; |
| 629 |
| 630 expect('{'); |
| 631 |
| 632 while (!match('}')) { |
| 633 properties.push(parseObjectProperty()); |
| 634 |
| 635 if (!match('}')) { |
| 636 expect(','); |
| 637 } |
| 638 } |
| 639 |
| 640 expect('}'); |
| 641 |
| 642 return delegate.createObjectExpression(properties); |
| 643 } |
| 644 |
| 645 // 11.1.6 The Grouping Operator |
| 646 |
| 647 function parseGroupExpression() { |
| 648 var expr; |
| 649 |
| 650 expect('('); |
| 651 |
| 652 expr = parseExpression(); |
| 653 |
| 654 expect(')'); |
| 655 |
| 656 return expr; |
| 657 } |
| 658 |
| 659 |
| 660 // 11.1 Primary Expressions |
| 661 |
| 662 function parsePrimaryExpression() { |
| 663 var type, token, expr; |
| 664 |
| 665 if (match('(')) { |
| 666 return parseGroupExpression(); |
| 667 } |
| 668 |
| 669 type = lookahead.type; |
| 670 |
| 671 if (type === Token.Identifier) { |
| 672 expr = delegate.createIdentifier(lex().value); |
| 673 } else if (type === Token.StringLiteral || type === Token.NumericLiteral) { |
| 674 expr = delegate.createLiteral(lex()); |
| 675 } else if (type === Token.Keyword) { |
| 676 if (matchKeyword('this')) { |
| 677 lex(); |
| 678 expr = delegate.createThisExpression(); |
| 679 } |
| 680 } else if (type === Token.BooleanLiteral) { |
| 681 token = lex(); |
| 682 token.value = (token.value === 'true'); |
| 683 expr = delegate.createLiteral(token); |
| 684 } else if (type === Token.NullLiteral) { |
| 685 token = lex(); |
| 686 token.value = null; |
| 687 expr = delegate.createLiteral(token); |
| 688 } else if (match('[')) { |
| 689 expr = parseArrayInitialiser(); |
| 690 } else if (match('{')) { |
| 691 expr = parseObjectInitialiser(); |
| 692 } |
| 693 |
| 694 if (expr) { |
| 695 return expr; |
| 696 } |
| 697 |
| 698 throwUnexpected(lex()); |
| 699 } |
| 700 |
| 701 // 11.2 Left-Hand-Side Expressions |
| 702 |
| 703 function parseArguments() { |
| 704 var args = []; |
| 705 |
| 706 expect('('); |
| 707 |
| 708 if (!match(')')) { |
| 709 while (index < length) { |
| 710 args.push(parseExpression()); |
| 711 if (match(')')) { |
| 712 break; |
| 713 } |
| 714 expect(','); |
| 715 } |
| 716 } |
| 717 |
| 718 expect(')'); |
| 719 |
| 720 return args; |
| 721 } |
| 722 |
| 723 function parseNonComputedProperty() { |
| 724 var token; |
| 725 |
| 726 token = lex(); |
| 727 |
| 728 if (!isIdentifierName(token)) { |
| 729 throwUnexpected(token); |
| 730 } |
| 731 |
| 732 return delegate.createIdentifier(token.value); |
| 733 } |
| 734 |
| 735 function parseNonComputedMember() { |
| 736 expect('.'); |
| 737 |
| 738 return parseNonComputedProperty(); |
| 739 } |
| 740 |
| 741 function parseComputedMember() { |
| 742 var expr; |
| 743 |
| 744 expect('['); |
| 745 |
| 746 expr = parseExpression(); |
| 747 |
| 748 expect(']'); |
| 749 |
| 750 return expr; |
| 751 } |
| 752 |
| 753 function parseLeftHandSideExpression() { |
| 754 var expr, args, property; |
| 755 |
| 756 expr = parsePrimaryExpression(); |
| 757 |
| 758 while (true) { |
| 759 if (match('[')) { |
| 760 property = parseComputedMember(); |
| 761 expr = delegate.createMemberExpression('[', expr, property); |
| 762 } else if (match('.')) { |
| 763 property = parseNonComputedMember(); |
| 764 expr = delegate.createMemberExpression('.', expr, property); |
| 765 } else if (match('(')) { |
| 766 args = parseArguments(); |
| 767 expr = delegate.createCallExpression(expr, args); |
| 768 } else { |
| 769 break; |
| 770 } |
| 771 } |
| 772 |
| 773 return expr; |
| 774 } |
| 775 |
| 776 // 11.3 Postfix Expressions |
| 777 |
| 778 var parsePostfixExpression = parseLeftHandSideExpression; |
| 779 |
| 780 // 11.4 Unary Operators |
| 781 |
| 782 function parseUnaryExpression() { |
| 783 var token, expr; |
| 784 |
| 785 if (lookahead.type !== Token.Punctuator && lookahead.type !== Token.Keyword)
{ |
| 786 expr = parsePostfixExpression(); |
| 787 } else if (match('+') || match('-') || match('!')) { |
| 788 token = lex(); |
| 789 expr = parseUnaryExpression(); |
| 790 expr = delegate.createUnaryExpression(token.value, expr); |
| 791 } else if (matchKeyword('delete') || matchKeyword('void') || matchKeyword('t
ypeof')) { |
| 792 throwError({}, Messages.UnexpectedToken); |
| 793 } else { |
| 794 expr = parsePostfixExpression(); |
| 795 } |
| 796 |
| 797 return expr; |
| 798 } |
| 799 |
| 800 function binaryPrecedence(token) { |
| 801 var prec = 0; |
| 802 |
| 803 if (token.type !== Token.Punctuator && token.type !== Token.Keyword) { |
| 804 return 0; |
| 805 } |
| 806 |
| 807 switch (token.value) { |
| 808 case '||': |
| 809 prec = 1; |
| 810 break; |
| 811 |
| 812 case '&&': |
| 813 prec = 2; |
| 814 break; |
| 815 |
| 816 case '==': |
| 817 case '!=': |
| 818 case '===': |
| 819 case '!==': |
| 820 prec = 6; |
| 821 break; |
| 822 |
| 823 case '<': |
| 824 case '>': |
| 825 case '<=': |
| 826 case '>=': |
| 827 case 'instanceof': |
| 828 prec = 7; |
| 829 break; |
| 830 |
| 831 case 'in': |
| 832 prec = 7; |
| 833 break; |
| 834 |
| 835 case '+': |
| 836 case '-': |
| 837 prec = 9; |
| 838 break; |
| 839 |
| 840 case '*': |
| 841 case '/': |
| 842 case '%': |
| 843 prec = 11; |
| 844 break; |
| 845 |
| 846 default: |
| 847 break; |
| 848 } |
| 849 |
| 850 return prec; |
| 851 } |
| 852 |
| 853 // 11.5 Multiplicative Operators |
| 854 // 11.6 Additive Operators |
| 855 // 11.7 Bitwise Shift Operators |
| 856 // 11.8 Relational Operators |
| 857 // 11.9 Equality Operators |
| 858 // 11.10 Binary Bitwise Operators |
| 859 // 11.11 Binary Logical Operators |
| 860 |
| 861 function parseBinaryExpression() { |
| 862 var expr, token, prec, stack, right, operator, left, i; |
| 863 |
| 864 left = parseUnaryExpression(); |
| 865 |
| 866 token = lookahead; |
| 867 prec = binaryPrecedence(token); |
| 868 if (prec === 0) { |
| 869 return left; |
| 870 } |
| 871 token.prec = prec; |
| 872 lex(); |
| 873 |
| 874 right = parseUnaryExpression(); |
| 875 |
| 876 stack = [left, token, right]; |
| 877 |
| 878 while ((prec = binaryPrecedence(lookahead)) > 0) { |
| 879 |
| 880 // Reduce: make a binary expression from the three topmost entries. |
| 881 while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec)) { |
| 882 right = stack.pop(); |
| 883 operator = stack.pop().value; |
| 884 left = stack.pop(); |
| 885 expr = delegate.createBinaryExpression(operator, left, right); |
| 886 stack.push(expr); |
| 887 } |
| 888 |
| 889 // Shift. |
| 890 token = lex(); |
| 891 token.prec = prec; |
| 892 stack.push(token); |
| 893 expr = parseUnaryExpression(); |
| 894 stack.push(expr); |
| 895 } |
| 896 |
| 897 // Final reduce to clean-up the stack. |
| 898 i = stack.length - 1; |
| 899 expr = stack[i]; |
| 900 while (i > 1) { |
| 901 expr = delegate.createBinaryExpression(stack[i - 1].value, stack[i - 2],
expr); |
| 902 i -= 2; |
| 903 } |
| 904 |
| 905 return expr; |
| 906 } |
| 907 |
| 908 |
| 909 // 11.12 Conditional Operator |
| 910 |
| 911 function parseConditionalExpression() { |
| 912 var expr, consequent, alternate; |
| 913 |
| 914 expr = parseBinaryExpression(); |
| 915 |
| 916 if (match('?')) { |
| 917 lex(); |
| 918 consequent = parseConditionalExpression(); |
| 919 expect(':'); |
| 920 alternate = parseConditionalExpression(); |
| 921 |
| 922 expr = delegate.createConditionalExpression(expr, consequent, alternate)
; |
| 923 } |
| 924 |
| 925 return expr; |
| 926 } |
| 927 |
| 928 // Simplification since we do not support AssignmentExpression. |
| 929 var parseExpression = parseConditionalExpression; |
| 930 |
| 931 // Polymer Syntax extensions |
| 932 |
| 933 // Filter :: |
| 934 // Identifier |
| 935 // Identifier "(" ")" |
| 936 // Identifier "(" FilterArguments ")" |
| 937 |
| 938 function parseFilter() { |
| 939 var identifier, args; |
| 940 |
| 941 identifier = lex(); |
| 942 |
| 943 if (identifier.type !== Token.Identifier) { |
| 944 throwUnexpected(identifier); |
| 945 } |
| 946 |
| 947 args = match('(') ? parseArguments() : []; |
| 948 |
| 949 return delegate.createFilter(identifier.value, args); |
| 950 } |
| 951 |
| 952 // Filters :: |
| 953 // "|" Filter |
| 954 // Filters "|" Filter |
| 955 |
| 956 function parseFilters() { |
| 957 while (match('|')) { |
| 958 lex(); |
| 959 parseFilter(); |
| 960 } |
| 961 } |
| 962 |
| 963 // TopLevel :: |
| 964 // LabelledExpressions |
| 965 // AsExpression |
| 966 // InExpression |
| 967 // FilterExpression |
| 968 |
| 969 // AsExpression :: |
| 970 // FilterExpression as Identifier |
| 971 |
| 972 // InExpression :: |
| 973 // Identifier, Identifier in FilterExpression |
| 974 // Identifier in FilterExpression |
| 975 |
| 976 // FilterExpression :: |
| 977 // Expression |
| 978 // Expression Filters |
| 979 |
| 980 function parseTopLevel() { |
| 981 skipWhitespace(); |
| 982 peek(); |
| 983 |
| 984 var expr = parseExpression(); |
| 985 if (expr) { |
| 986 if (lookahead.value === ',' || lookahead.value == 'in' && |
| 987 expr.type === Syntax.Identifier) { |
| 988 parseInExpression(expr); |
| 989 } else { |
| 990 parseFilters(); |
| 991 if (lookahead.value === 'as') { |
| 992 parseAsExpression(expr); |
| 993 } else { |
| 994 delegate.createTopLevel(expr); |
| 995 } |
| 996 } |
| 997 } |
| 998 |
| 999 if (lookahead.type !== Token.EOF) { |
| 1000 throwUnexpected(lookahead); |
| 1001 } |
| 1002 } |
| 1003 |
| 1004 function parseAsExpression(expr) { |
| 1005 lex(); // as |
| 1006 var identifier = lex().value; |
| 1007 delegate.createAsExpression(expr, identifier); |
| 1008 } |
| 1009 |
| 1010 function parseInExpression(identifier) { |
| 1011 var indexName; |
| 1012 if (lookahead.value === ',') { |
| 1013 lex(); |
| 1014 if (lookahead.type !== Token.Identifier) |
| 1015 throwUnexpected(lookahead); |
| 1016 indexName = lex().value; |
| 1017 } |
| 1018 |
| 1019 lex(); // in |
| 1020 var expr = parseExpression(); |
| 1021 parseFilters(); |
| 1022 delegate.createInExpression(identifier.name, indexName, expr); |
| 1023 } |
| 1024 |
| 1025 function parse(code, inDelegate) { |
| 1026 delegate = inDelegate; |
| 1027 source = code; |
| 1028 index = 0; |
| 1029 length = source.length; |
| 1030 lookahead = null; |
| 1031 state = { |
| 1032 labelSet: {} |
| 1033 }; |
| 1034 |
| 1035 return parseTopLevel(); |
| 1036 } |
| 1037 |
| 1038 module.exports = { |
| 1039 parse: parse |
| 1040 }; |
| 1041 |
| 1042 </script> |
OLD | NEW |