refactor: cleanup lexers & parsers

This commit is contained in:
Victor Berchet 2016-06-21 11:10:25 -07:00
parent f114dd300b
commit 1a212259af
6 changed files with 261 additions and 336 deletions

View File

@ -62,6 +62,20 @@ export const $PIPE = 124;
export const $TILDA = 126;
export const $AT = 64;
export const $BT = 96;
export function isWhitespace(code: number): boolean {
return (code >= $TAB && code <= $SPACE) || (code == $NBSP);
}
export function isDigit(code: number): boolean {
return $0 <= code && code <= $9;
}
export function isAsciiLetter(code: number): boolean {
return code >= $a && code <= $z || code >= $A && code <= $Z;
}
export function isAsciiHexDigit(code: number): boolean {
return code >= $a && code <= $f || code >= $A && code <= $F || isDigit(code);
}

View File

@ -1,9 +1,7 @@
import {$$, $0, $9, $A, $AMPERSAND, $AT, $BACKSLASH, $BANG, $CARET, $COLON, $COMMA, $CR, $DQ, $EOF, $EQ, $FF, $GT, $HASH, $LBRACE, $LBRACKET, $LF, $LPAREN, $MINUS, $PERCENT, $PERIOD, $PIPE, $PLUS, $QUESTION, $RBRACE, $RBRACKET, $RPAREN, $SEMICOLON, $SLASH, $SPACE, $SQ, $STAR, $TAB, $TILDA, $VTAB, $Z, $_, $a, $z, isWhitespace} from './chars';
import * as chars from './chars';
import {BaseException} from './facade/exceptions';
import {StringWrapper, isPresent, resolveEnumToken} from './facade/lang';
export {$AT, $COLON, $COMMA, $EOF, $GT, $LBRACE, $LBRACKET, $LPAREN, $PLUS, $RBRACE, $RBRACKET, $RPAREN, $SEMICOLON, $SLASH, $SPACE, $TAB, $TILDA, isWhitespace} from './chars';
export enum CssTokenType {
EOF,
String,
@ -148,25 +146,25 @@ export class CssScanner {
}
peekAt(index: number): number {
return index >= this.length ? $EOF : StringWrapper.charCodeAt(this.input, index);
return index >= this.length ? chars.$EOF : StringWrapper.charCodeAt(this.input, index);
}
consumeEmptyStatements(): void {
this.consumeWhitespace();
while (this.peek == $SEMICOLON) {
while (this.peek == chars.$SEMICOLON) {
this.advance();
this.consumeWhitespace();
}
}
consumeWhitespace(): void {
while (isWhitespace(this.peek) || isNewline(this.peek)) {
while (chars.isWhitespace(this.peek) || isNewline(this.peek)) {
this.advance();
if (!this._trackComments && isCommentStart(this.peek, this.peekPeek)) {
this.advance(); // /
this.advance(); // *
while (!isCommentEnd(this.peek, this.peekPeek)) {
if (this.peek == $EOF) {
if (this.peek == chars.$EOF) {
this.error('Unterminated comment');
}
this.advance();
@ -255,7 +253,7 @@ export class CssScanner {
_scan(): CssToken {
var peek = this.peek;
var peekPeek = this.peekPeek;
if (peek == $EOF) return null;
if (peek == chars.$EOF) return null;
if (isCommentStart(peek, peekPeek)) {
// even if comments are not tracked we still lex the
@ -266,13 +264,13 @@ export class CssScanner {
}
}
if (_trackWhitespace(this._currentMode) && (isWhitespace(peek) || isNewline(peek))) {
if (_trackWhitespace(this._currentMode) && (chars.isWhitespace(peek) || isNewline(peek))) {
return this.scanWhitespace();
}
peek = this.peek;
peekPeek = this.peekPeek;
if (peek == $EOF) return null;
if (peek == chars.$EOF) return null;
if (isStringStart(peek, peekPeek)) {
return this.scanString();
@ -283,14 +281,15 @@ export class CssScanner {
return this.scanCssValueFunction();
}
var isModifier = peek == $PLUS || peek == $MINUS;
var digitA = isModifier ? false : isDigit(peek);
var digitB = isDigit(peekPeek);
if (digitA || (isModifier && (peekPeek == $PERIOD || digitB)) || (peek == $PERIOD && digitB)) {
var isModifier = peek == chars.$PLUS || peek == chars.$MINUS;
var digitA = isModifier ? false : chars.isDigit(peek);
var digitB = chars.isDigit(peekPeek);
if (digitA || (isModifier && (peekPeek == chars.$PERIOD || digitB)) ||
(peek == chars.$PERIOD && digitB)) {
return this.scanNumber();
}
if (peek == $AT) {
if (peek == chars.$AT) {
return this.scanAtExpression();
}
@ -319,7 +318,7 @@ export class CssScanner {
this.advance(); // *
while (!isCommentEnd(this.peek, this.peekPeek)) {
if (this.peek == $EOF) {
if (this.peek == chars.$EOF) {
this.error('Unterminated comment');
}
this.advance();
@ -336,7 +335,7 @@ export class CssScanner {
var start = this.index;
var startingColumn = this.column;
var startingLine = this.line;
while (isWhitespace(this.peek) && this.peek != $EOF) {
while (chars.isWhitespace(this.peek) && this.peek != chars.$EOF) {
this.advance();
}
var str = this.input.substring(start, this.index);
@ -357,7 +356,7 @@ export class CssScanner {
this.advance();
while (!isCharMatch(target, previous, this.peek)) {
if (this.peek == $EOF || isNewline(this.peek)) {
if (this.peek == chars.$EOF || isNewline(this.peek)) {
this.error('Unterminated quote');
}
previous = this.peek;
@ -376,12 +375,12 @@ export class CssScanner {
scanNumber(): CssToken {
var start = this.index;
var startingColumn = this.column;
if (this.peek == $PLUS || this.peek == $MINUS) {
if (this.peek == chars.$PLUS || this.peek == chars.$MINUS) {
this.advance();
}
var periodUsed = false;
while (isDigit(this.peek) || this.peek == $PERIOD) {
if (this.peek == $PERIOD) {
while (chars.isDigit(this.peek) || this.peek == chars.$PERIOD) {
if (this.peek == chars.$PERIOD) {
if (periodUsed) {
this.error('Unexpected use of a second period value');
}
@ -412,11 +411,11 @@ export class CssScanner {
var start = this.index;
var startingColumn = this.column;
var parenBalance = 1;
while (this.peek != $EOF && parenBalance > 0) {
while (this.peek != chars.$EOF && parenBalance > 0) {
this.advance();
if (this.peek == $LPAREN) {
if (this.peek == chars.$LPAREN) {
parenBalance++;
} else if (this.peek == $RPAREN) {
} else if (this.peek == chars.$RPAREN) {
parenBalance--;
}
}
@ -440,7 +439,7 @@ export class CssScanner {
}
scanAtExpression(): CssToken {
if (this.assertCondition(this.peek == $AT, 'Expected @ value')) {
if (this.assertCondition(this.peek == chars.$AT, 'Expected @ value')) {
return null;
}
@ -481,53 +480,45 @@ export class CssScanner {
}
}
function isAtKeyword(current: CssToken, next: CssToken): boolean {
return current.numValue == $AT && next.type == CssTokenType.Identifier;
}
function isCharMatch(target: number, previous: number, code: number): boolean {
return code == target && previous != $BACKSLASH;
}
function isDigit(code: number): boolean {
return $0 <= code && code <= $9;
return code == target && previous != chars.$BACKSLASH;
}
function isCommentStart(code: number, next: number): boolean {
return code == $SLASH && next == $STAR;
return code == chars.$SLASH && next == chars.$STAR;
}
function isCommentEnd(code: number, next: number): boolean {
return code == $STAR && next == $SLASH;
return code == chars.$STAR && next == chars.$SLASH;
}
function isStringStart(code: number, next: number): boolean {
var target = code;
if (target == $BACKSLASH) {
if (target == chars.$BACKSLASH) {
target = next;
}
return target == $DQ || target == $SQ;
return target == chars.$DQ || target == chars.$SQ;
}
function isIdentifierStart(code: number, next: number): boolean {
var target = code;
if (target == $MINUS) {
if (target == chars.$MINUS) {
target = next;
}
return ($a <= target && target <= $z) || ($A <= target && target <= $Z) || target == $BACKSLASH ||
target == $MINUS || target == $_;
return chars.isAsciiLetter(target) || target == chars.$BACKSLASH || target == chars.$MINUS ||
target == chars.$_;
}
function isIdentifierPart(target: number): boolean {
return ($a <= target && target <= $z) || ($A <= target && target <= $Z) || target == $BACKSLASH ||
target == $MINUS || target == $_ || isDigit(target);
return chars.isAsciiLetter(target) || target == chars.$BACKSLASH || target == chars.$MINUS ||
target == chars.$_ || chars.isDigit(target);
}
function isValidPseudoSelectorCharacter(code: number): boolean {
switch (code) {
case $LPAREN:
case $RPAREN:
case chars.$LPAREN:
case chars.$RPAREN:
return true;
default:
return false;
@ -535,18 +526,18 @@ function isValidPseudoSelectorCharacter(code: number): boolean {
}
function isValidKeyframeBlockCharacter(code: number): boolean {
return code == $PERCENT;
return code == chars.$PERCENT;
}
function isValidAttributeSelectorCharacter(code: number): boolean {
// value^*|$~=something
switch (code) {
case $$:
case $PIPE:
case $CARET:
case $TILDA:
case $STAR:
case $EQ:
case chars.$$:
case chars.$PIPE:
case chars.$CARET:
case chars.$TILDA:
case chars.$STAR:
case chars.$EQ:
return true;
default:
return false;
@ -559,17 +550,17 @@ function isValidSelectorCharacter(code: number): boolean {
// #id, .class, *+~>
// tag:PSEUDO
switch (code) {
case $HASH:
case $PERIOD:
case $TILDA:
case $STAR:
case $PLUS:
case $GT:
case $COLON:
case $PIPE:
case $COMMA:
case $LBRACKET:
case $RBRACKET:
case chars.$HASH:
case chars.$PERIOD:
case chars.$TILDA:
case chars.$STAR:
case chars.$PLUS:
case chars.$GT:
case chars.$COLON:
case chars.$PIPE:
case chars.$COMMA:
case chars.$LBRACKET:
case chars.$RBRACKET:
return true;
default:
return false;
@ -580,16 +571,16 @@ function isValidStyleBlockCharacter(code: number): boolean {
// key:value;
// key:calc(something ... )
switch (code) {
case $HASH:
case $SEMICOLON:
case $COLON:
case $PERCENT:
case $SLASH:
case $BACKSLASH:
case $BANG:
case $PERIOD:
case $LPAREN:
case $RPAREN:
case chars.$HASH:
case chars.$SEMICOLON:
case chars.$COLON:
case chars.$PERCENT:
case chars.$SLASH:
case chars.$BACKSLASH:
case chars.$BANG:
case chars.$PERIOD:
case chars.$LPAREN:
case chars.$RPAREN:
return true;
default:
return false;
@ -599,11 +590,11 @@ function isValidStyleBlockCharacter(code: number): boolean {
function isValidMediaQueryRuleCharacter(code: number): boolean {
// (min-width: 7.5em) and (orientation: landscape)
switch (code) {
case $LPAREN:
case $RPAREN:
case $COLON:
case $PERCENT:
case $PERIOD:
case chars.$LPAREN:
case chars.$RPAREN:
case chars.$COLON:
case chars.$PERCENT:
case chars.$PERIOD:
return true;
default:
return false;
@ -613,21 +604,21 @@ function isValidMediaQueryRuleCharacter(code: number): boolean {
function isValidAtRuleCharacter(code: number): boolean {
// @document url(http://www.w3.org/page?something=on#hash),
switch (code) {
case $LPAREN:
case $RPAREN:
case $COLON:
case $PERCENT:
case $PERIOD:
case $SLASH:
case $BACKSLASH:
case $HASH:
case $EQ:
case $QUESTION:
case $AMPERSAND:
case $STAR:
case $COMMA:
case $MINUS:
case $PLUS:
case chars.$LPAREN:
case chars.$RPAREN:
case chars.$COLON:
case chars.$PERCENT:
case chars.$PERIOD:
case chars.$SLASH:
case chars.$BACKSLASH:
case chars.$HASH:
case chars.$EQ:
case chars.$QUESTION:
case chars.$AMPERSAND:
case chars.$STAR:
case chars.$COMMA:
case chars.$MINUS:
case chars.$PLUS:
return true;
default:
return false;
@ -636,14 +627,14 @@ function isValidAtRuleCharacter(code: number): boolean {
function isValidStyleFunctionCharacter(code: number): boolean {
switch (code) {
case $PERIOD:
case $MINUS:
case $PLUS:
case $STAR:
case $SLASH:
case $LPAREN:
case $RPAREN:
case $COMMA:
case chars.$PERIOD:
case chars.$MINUS:
case chars.$PLUS:
case chars.$STAR:
case chars.$SLASH:
case chars.$LPAREN:
case chars.$RPAREN:
case chars.$COMMA:
return true;
default:
return false;
@ -653,7 +644,7 @@ function isValidStyleFunctionCharacter(code: number): boolean {
function isValidBlockCharacter(code: number): boolean {
// @something { }
// IDENT
return code == $AT;
return code == chars.$AT;
}
function isValidCssCharacter(code: number, mode: CssLexerMode): boolean {
@ -695,20 +686,20 @@ function isValidCssCharacter(code: number, mode: CssLexerMode): boolean {
}
}
function charCode(input: any /** TODO #9100 */, index: any /** TODO #9100 */): number {
return index >= input.length ? $EOF : StringWrapper.charCodeAt(input, index);
function charCode(input: string, index: number): number {
return index >= input.length ? chars.$EOF : StringWrapper.charCodeAt(input, index);
}
function charStr(code: number): string {
return StringWrapper.fromCharCode(code);
}
export function isNewline(code: any /** TODO #9100 */): boolean {
export function isNewline(code: number): boolean {
switch (code) {
case $FF:
case $CR:
case $LF:
case $VTAB:
case chars.$FF:
case chars.$CR:
case chars.$LF:
case chars.$VTAB:
return true;
default:

View File

@ -1,5 +1,6 @@
import {$AT, $COLON, $COMMA, $EOF, $GT, $LBRACE, $LBRACKET, $LPAREN, $PLUS, $RBRACE, $RBRACKET, $RPAREN, $SEMICOLON, $SLASH, $SPACE, $TAB, $TILDA, CssLexerMode, CssScanner, CssScannerError, CssToken, CssTokenType, generateErrorMessage, isNewline, isWhitespace} from './css_lexer';
import {NumberWrapper, StringWrapper, isPresent} from './facade/lang';
import * as chars from './chars';
import {CssLexerMode, CssScanner, CssToken, CssTokenType, generateErrorMessage, isNewline} from './css_lexer';
import {isPresent} from './facade/lang';
import {ParseError, ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util';
const SPACE_OPERATOR = ' ';
@ -43,13 +44,13 @@ function _pseudoSelectorSupportsInnerSelectors(name: string): boolean {
function isSelectorOperatorCharacter(code: number): boolean {
switch (code) {
case $SLASH:
case $TILDA:
case $PLUS:
case $GT:
case chars.$SLASH:
case chars.$TILDA:
case chars.$PLUS:
case chars.$GT:
return true;
default:
return isWhitespace(code);
return chars.isWhitespace(code);
}
}
@ -69,22 +70,22 @@ function getDelimFromToken(token: CssToken): number {
function getDelimFromCharacter(code: number): number {
switch (code) {
case $EOF:
case chars.$EOF:
return EOF_DELIM_FLAG;
case $COMMA:
case chars.$COMMA:
return COMMA_DELIM_FLAG;
case $COLON:
case chars.$COLON:
return COLON_DELIM_FLAG;
case $SEMICOLON:
case chars.$SEMICOLON:
return SEMICOLON_DELIM_FLAG;
case $RBRACE:
case chars.$RBRACE:
return RBRACE_DELIM_FLAG;
case $LBRACE:
case chars.$LBRACE:
return LBRACE_DELIM_FLAG;
case $RPAREN:
case chars.$RPAREN:
return RPAREN_DELIM_FLAG;
case $SPACE:
case $TAB:
case chars.$SPACE:
case chars.$TAB:
return SPACE_DELIM_FLAG;
default:
return isNewline(code) ? NEWLINE_DELIM_FLAG : 0;
@ -187,7 +188,7 @@ export class CssParser {
const start = this._getScannerIndex();
var results: any[] /** TODO #9100 */ = [];
this._scanner.consumeEmptyStatements();
while (this._scanner.peek != $EOF) {
while (this._scanner.peek != chars.$EOF) {
this._scanner.setMode(CssLexerMode.BLOCK);
results.push(this._parseRule(delimiters));
}
@ -197,7 +198,7 @@ export class CssParser {
/** @internal */
_parseRule(delimiters: number): CssRuleAst {
if (this._scanner.peek == $AT) {
if (this._scanner.peek == chars.$AT) {
return this._parseAtRule(delimiters);
}
return this._parseSelectorRule(delimiters);
@ -277,7 +278,7 @@ export class CssParser {
this._collectUntilDelim(delimiters | LBRACE_DELIM_FLAG | SEMICOLON_DELIM_FLAG)
.forEach((token) => { listOfTokens.push(token); });
if (this._scanner.peek == $LBRACE) {
if (this._scanner.peek == chars.$LBRACE) {
listOfTokens.push(this._consume(CssTokenType.Character, '{'));
this._collectUntilDelim(delimiters | RBRACE_DELIM_FLAG | LBRACE_DELIM_FLAG)
.forEach((token) => { listOfTokens.push(token); });
@ -387,7 +388,7 @@ export class CssParser {
delimiters |= LBRACE_DELIM_FLAG;
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
stepTokens.push(this._parseKeyframeLabel(delimiters | COMMA_DELIM_FLAG));
if (this._scanner.peek != $LBRACE) {
if (this._scanner.peek != chars.$LBRACE) {
this._consume(CssTokenType.Character, ',');
}
}
@ -415,7 +416,7 @@ export class CssParser {
var startToken = this._consume(CssTokenType.Character, ':');
var tokens = [startToken];
if (this._scanner.peek == $COLON) { // ::something
if (this._scanner.peek == chars.$COLON) { // ::something
startToken = this._consume(CssTokenType.Character, ':');
tokens.push(startToken);
}
@ -430,7 +431,7 @@ export class CssParser {
tokens.push(pseudoSelectorToken);
// host(), lang(), nth-child(), etc...
if (this._scanner.peek == $LPAREN) {
if (this._scanner.peek == chars.$LPAREN) {
this._scanner.setMode(CssLexerMode.PSEUDO_SELECTOR_WITH_ARGUMENTS);
var openParenToken = this._consume(CssTokenType.Character, '(');
@ -491,13 +492,13 @@ export class CssParser {
var peek = this._scanner.peek;
switch (peek) {
case $COLON:
case chars.$COLON:
var innerPseudo = this._parsePseudoSelector(delimiters);
pseudoSelectors.push(innerPseudo);
this._scanner.setMode(CssLexerMode.SELECTOR);
break;
case $LBRACKET:
case chars.$LBRACKET:
// we set the mode after the scan because attribute mode does not
// allow attribute [] values. And this also will catch any errors
// if an extra "[" is used inside.
@ -505,7 +506,7 @@ export class CssParser {
this._scanner.setMode(CssLexerMode.ATTRIBUTE_SELECTOR);
break;
case $RBRACKET:
case chars.$RBRACKET:
if (this._scanner.getMode() != CssLexerMode.ATTRIBUTE_SELECTOR) {
hasAttributeError = true;
}
@ -580,7 +581,7 @@ export class CssParser {
case GT_CHARACTER:
// >>> operator
if (this._scanner.peek == $GT && this._scanner.peekPeek == $GT) {
if (this._scanner.peek == chars.$GT && this._scanner.peekPeek == chars.$GT) {
this._consume(CssTokenType.Character, GT_CHARACTER);
this._consume(CssTokenType.Character, GT_CHARACTER);
token = new CssToken(
@ -601,7 +602,7 @@ export class CssParser {
// if we do come across one or more spaces inside of
// the operators loop then an empty space is still a
// valid operator to use if something else was not found
if (operator == null && operatorScanCount > 0 && this._scanner.peek != $LBRACE) {
if (operator == null && operatorScanCount > 0 && this._scanner.peek != chars.$LBRACE) {
operator = lastOperatorToken;
}
@ -647,7 +648,7 @@ export class CssParser {
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
var token: CssToken;
if (isPresent(previous) && previous.type == CssTokenType.Identifier &&
this._scanner.peek == $LPAREN) {
this._scanner.peek == chars.$LPAREN) {
token = this._consume(CssTokenType.Character, '(');
tokens.push(token);
@ -676,9 +677,9 @@ export class CssParser {
this._scanner.consumeWhitespace();
var code = this._scanner.peek;
if (code == $SEMICOLON) {
if (code == chars.$SEMICOLON) {
this._consume(CssTokenType.Character, ';');
} else if (code != $RBRACE) {
} else if (code != chars.$RBRACE) {
this._error(
generateErrorMessage(
this._scanner.input, `The CSS key/value definition did not end with a semicolon`,
@ -734,7 +735,7 @@ export class CssParser {
this._scanner.setMode(CssLexerMode.STYLE_BLOCK);
var result = this._consume(CssTokenType.Character, '{');
if (result.numValue != $LBRACE) {
if (result.numValue != chars.$LBRACE) {
return null;
}
@ -767,20 +768,20 @@ export class CssParser {
// there are a few cases as to what could happen if it
// is missing
switch (this._scanner.peek) {
case $COLON:
case chars.$COLON:
this._consume(CssTokenType.Character, ':');
parseValue = true;
break;
case $SEMICOLON:
case $RBRACE:
case $EOF:
case chars.$SEMICOLON:
case chars.$RBRACE:
case chars.$EOF:
parseValue = false;
break;
default:
var propStr = [prop.strValue];
if (this._scanner.peek != $COLON) {
if (this._scanner.peek != chars.$COLON) {
// this will throw the error
var nextValue = this._consume(CssTokenType.Character, ':');
propStr.push(nextValue.strValue);
@ -795,7 +796,7 @@ export class CssParser {
}
// this means we've reached the end of the definition and/or block
if (this._scanner.peek == $COLON) {
if (this._scanner.peek == chars.$COLON) {
this._consume(CssTokenType.Character, ':');
parseValue = true;
} else {
@ -960,8 +961,6 @@ export class CssSelectorAst extends CssSelectorPartAst {
}
export class CssSimpleSelectorAst extends CssSelectorPartAst {
public selectorStrValue: string;
constructor(
start: number, end: number, public tokens: CssToken[], public strValue: string,
public pseudoSelectors: CssPseudoSelectorAst[], public operator: CssToken) {

View File

@ -1,5 +1,5 @@
import {Injectable} from '@angular/core';
import * as chars from '../chars';
import {SetWrapper} from '../facade/collection';
import {BaseException} from '../facade/exceptions';
import {NumberWrapper, StringJoiner, StringWrapper, isPresent} from '../facade/lang';
@ -13,6 +13,8 @@ export enum TokenType {
Number
}
const KEYWORDS = ['var', 'let', 'null', 'undefined', 'true', 'false', 'if', 'else'];
@Injectable()
export class Lexer {
tokenize(text: string): any[] {
@ -109,58 +111,8 @@ function newNumberToken(index: number, n: number): Token {
return new Token(index, TokenType.Number, n, '');
}
export var EOF: Token = new Token(-1, TokenType.Character, 0, '');
export const $EOF = /*@ts2dart_const*/ 0;
export const $TAB = /*@ts2dart_const*/ 9;
export const $LF = /*@ts2dart_const*/ 10;
export const $VTAB = /*@ts2dart_const*/ 11;
export const $FF = /*@ts2dart_const*/ 12;
export const $CR = /*@ts2dart_const*/ 13;
export const $SPACE = /*@ts2dart_const*/ 32;
export const $BANG = /*@ts2dart_const*/ 33;
export const $DQ = /*@ts2dart_const*/ 34;
export const $HASH = /*@ts2dart_const*/ 35;
export const $$ = /*@ts2dart_const*/ 36;
export const $PERCENT = /*@ts2dart_const*/ 37;
export const $AMPERSAND = /*@ts2dart_const*/ 38;
export const $SQ = /*@ts2dart_const*/ 39;
export const $LPAREN = /*@ts2dart_const*/ 40;
export const $RPAREN = /*@ts2dart_const*/ 41;
export const $STAR = /*@ts2dart_const*/ 42;
export const $PLUS = /*@ts2dart_const*/ 43;
export const $COMMA = /*@ts2dart_const*/ 44;
export const $MINUS = /*@ts2dart_const*/ 45;
export const $PERIOD = /*@ts2dart_const*/ 46;
export const $SLASH = /*@ts2dart_const*/ 47;
export const $COLON = /*@ts2dart_const*/ 58;
export const $SEMICOLON = /*@ts2dart_const*/ 59;
export const $LT = /*@ts2dart_const*/ 60;
export const $EQ = /*@ts2dart_const*/ 61;
export const $GT = /*@ts2dart_const*/ 62;
export const $QUESTION = /*@ts2dart_const*/ 63;
const $0 = /*@ts2dart_const*/ 48;
const $9 = /*@ts2dart_const*/ 57;
const $A = /*@ts2dart_const*/ 65, $E = /*@ts2dart_const*/ 69, $Z = /*@ts2dart_const*/ 90;
export const $LBRACKET = /*@ts2dart_const*/ 91;
export const $BACKSLASH = /*@ts2dart_const*/ 92;
export const $RBRACKET = /*@ts2dart_const*/ 93;
const $CARET = /*@ts2dart_const*/ 94;
const $_ = /*@ts2dart_const*/ 95;
export const $BT = /*@ts2dart_const*/ 96;
const $a = /*@ts2dart_const*/ 97, $e = /*@ts2dart_const*/ 101, $f = /*@ts2dart_const*/ 102;
const $n = /*@ts2dart_const*/ 110, $r = /*@ts2dart_const*/ 114, $t = /*@ts2dart_const*/ 116,
$u = /*@ts2dart_const*/ 117, $v = /*@ts2dart_const*/ 118, $z = /*@ts2dart_const*/ 122;
export const $LBRACE = /*@ts2dart_const*/ 123;
export const $BAR = /*@ts2dart_const*/ 124;
export const $RBRACE = /*@ts2dart_const*/ 125;
const $NBSP = /*@ts2dart_const*/ 160;
export class ScannerError extends BaseException {
constructor(public message: string) { super(); }
@ -179,16 +131,16 @@ class _Scanner {
advance() {
this.peek =
++this.index >= this.length ? $EOF : StringWrapper.charCodeAt(this.input, this.index);
++this.index >= this.length ? chars.$EOF : StringWrapper.charCodeAt(this.input, this.index);
}
scanToken(): Token {
var input = this.input, length = this.length, peek = this.peek, index = this.index;
// Skip whitespace.
while (peek <= $SPACE) {
while (peek <= chars.$SPACE) {
if (++index >= length) {
peek = $EOF;
peek = chars.$EOF;
break;
} else {
peek = StringWrapper.charCodeAt(input, index);
@ -204,49 +156,50 @@ class _Scanner {
// Handle identifiers and numbers.
if (isIdentifierStart(peek)) return this.scanIdentifier();
if (isDigit(peek)) return this.scanNumber(index);
if (chars.isDigit(peek)) return this.scanNumber(index);
var start: number = index;
switch (peek) {
case $PERIOD:
case chars.$PERIOD:
this.advance();
return isDigit(this.peek) ? this.scanNumber(start) : newCharacterToken(start, $PERIOD);
case $LPAREN:
case $RPAREN:
case $LBRACE:
case $RBRACE:
case $LBRACKET:
case $RBRACKET:
case $COMMA:
case $COLON:
case $SEMICOLON:
return chars.isDigit(this.peek) ? this.scanNumber(start) :
newCharacterToken(start, chars.$PERIOD);
case chars.$LPAREN:
case chars.$RPAREN:
case chars.$LBRACE:
case chars.$RBRACE:
case chars.$LBRACKET:
case chars.$RBRACKET:
case chars.$COMMA:
case chars.$COLON:
case chars.$SEMICOLON:
return this.scanCharacter(start, peek);
case $SQ:
case $DQ:
case chars.$SQ:
case chars.$DQ:
return this.scanString();
case $HASH:
case $PLUS:
case $MINUS:
case $STAR:
case $SLASH:
case $PERCENT:
case $CARET:
case chars.$HASH:
case chars.$PLUS:
case chars.$MINUS:
case chars.$STAR:
case chars.$SLASH:
case chars.$PERCENT:
case chars.$CARET:
return this.scanOperator(start, StringWrapper.fromCharCode(peek));
case $QUESTION:
return this.scanComplexOperator(start, '?', $PERIOD, '.');
case $LT:
case $GT:
return this.scanComplexOperator(start, StringWrapper.fromCharCode(peek), $EQ, '=');
case $BANG:
case $EQ:
case chars.$QUESTION:
return this.scanComplexOperator(start, '?', chars.$PERIOD, '.');
case chars.$LT:
case chars.$GT:
return this.scanComplexOperator(start, StringWrapper.fromCharCode(peek), chars.$EQ, '=');
case chars.$BANG:
case chars.$EQ:
return this.scanComplexOperator(
start, StringWrapper.fromCharCode(peek), $EQ, '=', $EQ, '=');
case $AMPERSAND:
return this.scanComplexOperator(start, '&', $AMPERSAND, '&');
case $BAR:
return this.scanComplexOperator(start, '|', $BAR, '|');
case $NBSP:
while (isWhitespace(this.peek)) this.advance();
start, StringWrapper.fromCharCode(peek), chars.$EQ, '=', chars.$EQ, '=');
case chars.$AMPERSAND:
return this.scanComplexOperator(start, '&', chars.$AMPERSAND, '&');
case chars.$BAR:
return this.scanComplexOperator(start, '|', chars.$BAR, '|');
case chars.$NBSP:
while (chars.isWhitespace(this.peek)) this.advance();
return this.scanToken();
}
@ -297,25 +250,22 @@ class _Scanner {
this.advance();
while (isIdentifierPart(this.peek)) this.advance();
var str: string = this.input.substring(start, this.index);
if (SetWrapper.has(KEYWORDS, str)) {
return newKeywordToken(start, str);
} else {
return newIdentifierToken(start, str);
}
return KEYWORDS.indexOf(str) > -1 ? newKeywordToken(start, str) :
newIdentifierToken(start, str);
}
scanNumber(start: number): Token {
var simple: boolean = (this.index === start);
this.advance(); // Skip initial digit.
while (true) {
if (isDigit(this.peek)) {
if (chars.isDigit(this.peek)) {
// Do nothing.
} else if (this.peek == $PERIOD) {
} else if (this.peek == chars.$PERIOD) {
simple = false;
} else if (isExponentStart(this.peek)) {
this.advance();
if (isExponentSign(this.peek)) this.advance();
if (!isDigit(this.peek)) this.error('Invalid exponent', -1);
if (!chars.isDigit(this.peek)) this.error('Invalid exponent', -1);
simple = false;
} else {
break;
@ -323,7 +273,6 @@ class _Scanner {
this.advance();
}
var str: string = this.input.substring(start, this.index);
// TODO
var value: number =
simple ? NumberWrapper.parseIntAutoRadix(str) : NumberWrapper.parseFloat(str);
return newNumberToken(start, value);
@ -339,12 +288,12 @@ class _Scanner {
var input: string = this.input;
while (this.peek != quote) {
if (this.peek == $BACKSLASH) {
if (this.peek == chars.$BACKSLASH) {
if (buffer == null) buffer = new StringJoiner();
buffer.add(input.substring(marker, this.index));
this.advance();
var unescapedCode: number;
if (this.peek == $u) {
if (this.peek == chars.$u) {
// 4 character hex code for unicode character.
var hex: string = input.substring(this.index + 1, this.index + 5);
try {
@ -361,7 +310,7 @@ class _Scanner {
}
buffer.add(StringWrapper.fromCharCode(unescapedCode));
marker = this.index;
} else if (this.peek == $EOF) {
} else if (this.peek == chars.$EOF) {
this.error('Unterminated quote', 0);
} else {
this.advance();
@ -387,12 +336,9 @@ class _Scanner {
}
}
function isWhitespace(code: number): boolean {
return (code >= $TAB && code <= $SPACE) || (code == $NBSP);
}
function isIdentifierStart(code: number): boolean {
return ($a <= code && code <= $z) || ($A <= code && code <= $Z) || (code == $_) || (code == $$);
return (chars.$a <= code && code <= chars.$z) || (chars.$A <= code && code <= chars.$Z) ||
(code == chars.$_) || (code == chars.$$);
}
export function isIdentifier(input: string): boolean {
@ -400,7 +346,7 @@ export function isIdentifier(input: string): boolean {
var scanner = new _Scanner(input);
if (!isIdentifierStart(scanner.peek)) return false;
scanner.advance();
while (scanner.peek !== $EOF) {
while (scanner.peek !== chars.$EOF) {
if (!isIdentifierPart(scanner.peek)) return false;
scanner.advance();
}
@ -408,48 +354,35 @@ export function isIdentifier(input: string): boolean {
}
function isIdentifierPart(code: number): boolean {
return ($a <= code && code <= $z) || ($A <= code && code <= $Z) || ($0 <= code && code <= $9) ||
(code == $_) || (code == $$);
}
function isDigit(code: number): boolean {
return $0 <= code && code <= $9;
return chars.isAsciiLetter(code) || chars.isDigit(code) || (code == chars.$_) ||
(code == chars.$$);
}
function isExponentStart(code: number): boolean {
return code == $e || code == $E;
return code == chars.$e || code == chars.$E;
}
function isExponentSign(code: number): boolean {
return code == $MINUS || code == $PLUS;
return code == chars.$MINUS || code == chars.$PLUS;
}
export function isQuote(code: number): boolean {
return code === $SQ || code === $DQ || code === $BT;
return code === chars.$SQ || code === chars.$DQ || code === chars.$BT;
}
function unescape(code: number): number {
switch (code) {
case $n:
return $LF;
case $f:
return $FF;
case $r:
return $CR;
case $t:
return $TAB;
case $v:
return $VTAB;
case chars.$n:
return chars.$LF;
case chars.$f:
return chars.$FF;
case chars.$r:
return chars.$CR;
case chars.$t:
return chars.$TAB;
case chars.$v:
return chars.$VTAB;
default:
return code;
}
}
var OPERATORS = SetWrapper.createFromList([
'+', '-', '*', '/', '%', '^', '=', '==', '!=', '===', '!==', '<',
'>', '<=', '>=', '&&', '||', '&', '|', '!', '?', '#', '?.'
]);
var KEYWORDS =
SetWrapper.createFromList(['var', 'let', 'null', 'undefined', 'true', 'false', 'if', 'else']);
}

View File

@ -1,12 +1,13 @@
import {Injectable} from '@angular/core';
import * as chars from '../chars';
import {ListWrapper} from '../facade/collection';
import {BaseException} from '../facade/exceptions';
import {RegExpWrapper, StringWrapper, escapeRegExp, isBlank, isPresent} from '../facade/lang';
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../interpolation_config';
import {AST, ASTWithSource, AstVisitor, Binary, BindingPipe, Chain, Conditional, EmptyExpr, FunctionCall, ImplicitReceiver, Interpolation, KeyedRead, KeyedWrite, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, PrefixNot, PropertyRead, PropertyWrite, Quote, SafeMethodCall, SafePropertyRead, TemplateBinding} from './ast';
import {$COLON, $COMMA, $LBRACE, $LBRACKET, $LPAREN, $PERIOD, $RBRACE, $RBRACKET, $RPAREN, $SEMICOLON, $SLASH, EOF, Lexer, Token, isIdentifier, isQuote} from './lexer';
import {EOF, Lexer, Token, isIdentifier, isQuote} from './lexer';
var _implicitReceiver = new ImplicitReceiver();
@ -152,7 +153,7 @@ export class Parser {
let char = StringWrapper.charCodeAt(input, i);
let nextChar = StringWrapper.charCodeAt(input, i + 1);
if (char === $SLASH && nextChar == $SLASH && isBlank(outerQuote)) return i;
if (char === chars.$SLASH && nextChar == chars.$SLASH && isBlank(outerQuote)) return i;
if (outerQuote === char) {
outerQuote = null;
@ -267,11 +268,11 @@ export class _ParseAST {
var expr = this.parsePipe();
exprs.push(expr);
if (this.optionalCharacter($SEMICOLON)) {
if (this.optionalCharacter(chars.$SEMICOLON)) {
if (!this.parseAction) {
this.error('Binding expression cannot contain chained expression');
}
while (this.optionalCharacter($SEMICOLON)) {
while (this.optionalCharacter(chars.$SEMICOLON)) {
} // read all semicolons
} else if (this.index < this.tokens.length) {
this.error(`Unexpected token '${this.next}'`);
@ -292,7 +293,7 @@ export class _ParseAST {
do {
var name = this.expectIdentifierOrKeyword();
var args: AST[] = [];
while (this.optionalCharacter($COLON)) {
while (this.optionalCharacter(chars.$COLON)) {
args.push(this.parseExpression());
}
result = new BindingPipe(result, name, args);
@ -310,7 +311,7 @@ export class _ParseAST {
if (this.optionalOperator('?')) {
var yes = this.parsePipe();
if (!this.optionalCharacter($COLON)) {
if (!this.optionalCharacter(chars.$COLON)) {
var end = this.inputIndex;
var expression = this.input.substring(start, end);
this.error(`Conditional expression ${expression} requires all 3 expressions`);
@ -421,15 +422,15 @@ export class _ParseAST {
parseCallChain(): AST {
var result = this.parsePrimary();
while (true) {
if (this.optionalCharacter($PERIOD)) {
if (this.optionalCharacter(chars.$PERIOD)) {
result = this.parseAccessMemberOrMethodCall(result, false);
} else if (this.optionalOperator('?.')) {
result = this.parseAccessMemberOrMethodCall(result, true);
} else if (this.optionalCharacter($LBRACKET)) {
} else if (this.optionalCharacter(chars.$LBRACKET)) {
var key = this.parsePipe();
this.expectCharacter($RBRACKET);
this.expectCharacter(chars.$RBRACKET);
if (this.optionalOperator('=')) {
var value = this.parseConditional();
result = new KeyedWrite(result, key, value);
@ -437,9 +438,9 @@ export class _ParseAST {
result = new KeyedRead(result, key);
}
} else if (this.optionalCharacter($LPAREN)) {
} else if (this.optionalCharacter(chars.$LPAREN)) {
var args = this.parseCallArguments();
this.expectCharacter($RPAREN);
this.expectCharacter(chars.$RPAREN);
result = new FunctionCall(result, args);
} else {
@ -449,9 +450,9 @@ export class _ParseAST {
}
parsePrimary(): AST {
if (this.optionalCharacter($LPAREN)) {
if (this.optionalCharacter(chars.$LPAREN)) {
let result = this.parsePipe();
this.expectCharacter($RPAREN);
this.expectCharacter(chars.$RPAREN);
return result;
} else if (this.next.isKeywordNull() || this.next.isKeywordUndefined()) {
this.advance();
@ -465,12 +466,12 @@ export class _ParseAST {
this.advance();
return new LiteralPrimitive(false);
} else if (this.optionalCharacter($LBRACKET)) {
var elements = this.parseExpressionList($RBRACKET);
this.expectCharacter($RBRACKET);
} else if (this.optionalCharacter(chars.$LBRACKET)) {
var elements = this.parseExpressionList(chars.$RBRACKET);
this.expectCharacter(chars.$RBRACKET);
return new LiteralArray(elements);
} else if (this.next.isCharacter($LBRACE)) {
} else if (this.next.isCharacter(chars.$LBRACE)) {
return this.parseLiteralMap();
} else if (this.next.isIdentifier()) {
@ -501,7 +502,7 @@ export class _ParseAST {
if (!this.next.isCharacter(terminator)) {
do {
result.push(this.parsePipe());
} while (this.optionalCharacter($COMMA));
} while (this.optionalCharacter(chars.$COMMA));
}
return result;
}
@ -509,15 +510,15 @@ export class _ParseAST {
parseLiteralMap(): LiteralMap {
var keys: string[] = [];
var values: AST[] = [];
this.expectCharacter($LBRACE);
if (!this.optionalCharacter($RBRACE)) {
this.expectCharacter(chars.$LBRACE);
if (!this.optionalCharacter(chars.$RBRACE)) {
do {
var key = this.expectIdentifierOrKeywordOrString();
keys.push(key);
this.expectCharacter($COLON);
this.expectCharacter(chars.$COLON);
values.push(this.parsePipe());
} while (this.optionalCharacter($COMMA));
this.expectCharacter($RBRACE);
} while (this.optionalCharacter(chars.$COMMA));
this.expectCharacter(chars.$RBRACE);
}
return new LiteralMap(keys, values);
}
@ -525,9 +526,9 @@ export class _ParseAST {
parseAccessMemberOrMethodCall(receiver: AST, isSafe: boolean = false): AST {
let id = this.expectIdentifierOrKeyword();
if (this.optionalCharacter($LPAREN)) {
if (this.optionalCharacter(chars.$LPAREN)) {
let args = this.parseCallArguments();
this.expectCharacter($RPAREN);
this.expectCharacter(chars.$RPAREN);
return isSafe ? new SafeMethodCall(receiver, id, args) : new MethodCall(receiver, id, args);
} else {
@ -555,11 +556,11 @@ export class _ParseAST {
}
parseCallArguments(): BindingPipe[] {
if (this.next.isCharacter($RPAREN)) return [];
if (this.next.isCharacter(chars.$RPAREN)) return [];
var positionals: AST[] = [];
do {
positionals.push(this.parsePipe());
} while (this.optionalCharacter($COMMA));
} while (this.optionalCharacter(chars.$COMMA));
return positionals as BindingPipe[];
}
@ -605,7 +606,7 @@ export class _ParseAST {
key = prefix + key[0].toUpperCase() + key.substring(1);
}
}
this.optionalCharacter($COLON);
this.optionalCharacter(chars.$COLON);
var name: string = null;
var expression: ASTWithSource = null;
if (keyIsVar) {
@ -623,8 +624,8 @@ export class _ParseAST {
expression = new ASTWithSource(ast, source, this.location);
}
bindings.push(new TemplateBinding(key, keyIsVar, name, expression));
if (!this.optionalCharacter($SEMICOLON)) {
this.optionalCharacter($COMMA);
if (!this.optionalCharacter(chars.$SEMICOLON)) {
this.optionalCharacter(chars.$COMMA);
}
}
return new TemplateBindingParseResult(bindings, warnings);

View File

@ -266,13 +266,13 @@ class _HtmlTokenizer {
}
}
private _attemptCharCodeUntilFn(predicate: Function) {
private _attemptCharCodeUntilFn(predicate: (code: number) => boolean) {
while (!predicate(this._peek)) {
this._advance();
}
}
private _requireCharCodeUntilFn(predicate: Function, len: number) {
private _requireCharCodeUntilFn(predicate: (code: number) => boolean, len: number) {
var start = this._getLocation();
this._attemptCharCodeUntilFn(predicate);
if (this._index - start.offset < len) {
@ -402,7 +402,7 @@ class _HtmlTokenizer {
let savedPos = this._savePosition();
let lowercaseTagName: string;
try {
if (!isAsciiLetter(this._peek)) {
if (!chars.isAsciiLetter(this._peek)) {
throw this._createError(unexpectedCharacterErrorMsg(this._peek), this._getSpan());
}
var nameStart = this._index;
@ -635,16 +635,12 @@ class _HtmlTokenizer {
}
function isNotWhitespace(code: number): boolean {
return !isWhitespace(code) || code === chars.$EOF;
}
function isWhitespace(code: number): boolean {
return (code >= chars.$TAB && code <= chars.$SPACE) || (code === chars.$NBSP);
return !chars.isWhitespace(code) || code === chars.$EOF;
}
function isNameEnd(code: number): boolean {
return isWhitespace(code) || code === chars.$GT || code === chars.$SLASH || code === chars.$SQ ||
code === chars.$DQ || code === chars.$EQ;
return chars.isWhitespace(code) || code === chars.$GT || code === chars.$SLASH ||
code === chars.$SQ || code === chars.$DQ || code === chars.$EQ;
}
function isPrefixEnd(code: number): boolean {
@ -653,11 +649,11 @@ function isPrefixEnd(code: number): boolean {
}
function isDigitEntityEnd(code: number): boolean {
return code == chars.$SEMICOLON || code == chars.$EOF || !isAsciiHexDigit(code);
return code == chars.$SEMICOLON || code == chars.$EOF || !chars.isAsciiHexDigit(code);
}
function isNamedEntityEnd(code: number): boolean {
return code == chars.$SEMICOLON || code == chars.$EOF || !isAsciiLetter(code);
return code == chars.$SEMICOLON || code == chars.$EOF || !chars.isAsciiLetter(code);
}
function isExpansionFormStart(input: string, offset: number, interpolationStart: string): boolean {
@ -668,16 +664,7 @@ function isExpansionFormStart(input: string, offset: number, interpolationStart:
}
function isExpansionCaseStart(peek: number): boolean {
return peek === chars.$EQ || isAsciiLetter(peek);
}
function isAsciiLetter(code: number): boolean {
return code >= chars.$a && code <= chars.$z || code >= chars.$A && code <= chars.$Z;
}
function isAsciiHexDigit(code: number): boolean {
return code >= chars.$a && code <= chars.$f || code >= chars.$A && code <= chars.$F ||
code >= chars.$0 && code <= chars.$9;
return peek === chars.$EQ || chars.isAsciiLetter(peek);
}
function compareCharCodeCaseInsensitive(code1: number, code2: number): boolean {