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 $TILDA = 126;
export const $AT = 64; export const $AT = 64;
export const $BT = 96;
export function isWhitespace(code: number): boolean { export function isWhitespace(code: number): boolean {
return (code >= $TAB && code <= $SPACE) || (code == $NBSP); 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 {BaseException} from './facade/exceptions';
import {StringWrapper, isPresent, resolveEnumToken} from './facade/lang'; 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 { export enum CssTokenType {
EOF, EOF,
String, String,
@ -148,25 +146,25 @@ export class CssScanner {
} }
peekAt(index: number): number { 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 { consumeEmptyStatements(): void {
this.consumeWhitespace(); this.consumeWhitespace();
while (this.peek == $SEMICOLON) { while (this.peek == chars.$SEMICOLON) {
this.advance(); this.advance();
this.consumeWhitespace(); this.consumeWhitespace();
} }
} }
consumeWhitespace(): void { consumeWhitespace(): void {
while (isWhitespace(this.peek) || isNewline(this.peek)) { while (chars.isWhitespace(this.peek) || isNewline(this.peek)) {
this.advance(); this.advance();
if (!this._trackComments && isCommentStart(this.peek, this.peekPeek)) { if (!this._trackComments && isCommentStart(this.peek, this.peekPeek)) {
this.advance(); // / this.advance(); // /
this.advance(); // * this.advance(); // *
while (!isCommentEnd(this.peek, this.peekPeek)) { while (!isCommentEnd(this.peek, this.peekPeek)) {
if (this.peek == $EOF) { if (this.peek == chars.$EOF) {
this.error('Unterminated comment'); this.error('Unterminated comment');
} }
this.advance(); this.advance();
@ -255,7 +253,7 @@ export class CssScanner {
_scan(): CssToken { _scan(): CssToken {
var peek = this.peek; var peek = this.peek;
var peekPeek = this.peekPeek; var peekPeek = this.peekPeek;
if (peek == $EOF) return null; if (peek == chars.$EOF) return null;
if (isCommentStart(peek, peekPeek)) { if (isCommentStart(peek, peekPeek)) {
// even if comments are not tracked we still lex the // 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(); return this.scanWhitespace();
} }
peek = this.peek; peek = this.peek;
peekPeek = this.peekPeek; peekPeek = this.peekPeek;
if (peek == $EOF) return null; if (peek == chars.$EOF) return null;
if (isStringStart(peek, peekPeek)) { if (isStringStart(peek, peekPeek)) {
return this.scanString(); return this.scanString();
@ -283,14 +281,15 @@ export class CssScanner {
return this.scanCssValueFunction(); return this.scanCssValueFunction();
} }
var isModifier = peek == $PLUS || peek == $MINUS; var isModifier = peek == chars.$PLUS || peek == chars.$MINUS;
var digitA = isModifier ? false : isDigit(peek); var digitA = isModifier ? false : chars.isDigit(peek);
var digitB = isDigit(peekPeek); var digitB = chars.isDigit(peekPeek);
if (digitA || (isModifier && (peekPeek == $PERIOD || digitB)) || (peek == $PERIOD && digitB)) { if (digitA || (isModifier && (peekPeek == chars.$PERIOD || digitB)) ||
(peek == chars.$PERIOD && digitB)) {
return this.scanNumber(); return this.scanNumber();
} }
if (peek == $AT) { if (peek == chars.$AT) {
return this.scanAtExpression(); return this.scanAtExpression();
} }
@ -319,7 +318,7 @@ export class CssScanner {
this.advance(); // * this.advance(); // *
while (!isCommentEnd(this.peek, this.peekPeek)) { while (!isCommentEnd(this.peek, this.peekPeek)) {
if (this.peek == $EOF) { if (this.peek == chars.$EOF) {
this.error('Unterminated comment'); this.error('Unterminated comment');
} }
this.advance(); this.advance();
@ -336,7 +335,7 @@ export class CssScanner {
var start = this.index; var start = this.index;
var startingColumn = this.column; var startingColumn = this.column;
var startingLine = this.line; var startingLine = this.line;
while (isWhitespace(this.peek) && this.peek != $EOF) { while (chars.isWhitespace(this.peek) && this.peek != chars.$EOF) {
this.advance(); this.advance();
} }
var str = this.input.substring(start, this.index); var str = this.input.substring(start, this.index);
@ -357,7 +356,7 @@ export class CssScanner {
this.advance(); this.advance();
while (!isCharMatch(target, previous, this.peek)) { 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'); this.error('Unterminated quote');
} }
previous = this.peek; previous = this.peek;
@ -376,12 +375,12 @@ export class CssScanner {
scanNumber(): CssToken { scanNumber(): CssToken {
var start = this.index; var start = this.index;
var startingColumn = this.column; var startingColumn = this.column;
if (this.peek == $PLUS || this.peek == $MINUS) { if (this.peek == chars.$PLUS || this.peek == chars.$MINUS) {
this.advance(); this.advance();
} }
var periodUsed = false; var periodUsed = false;
while (isDigit(this.peek) || this.peek == $PERIOD) { while (chars.isDigit(this.peek) || this.peek == chars.$PERIOD) {
if (this.peek == $PERIOD) { if (this.peek == chars.$PERIOD) {
if (periodUsed) { if (periodUsed) {
this.error('Unexpected use of a second period value'); this.error('Unexpected use of a second period value');
} }
@ -412,11 +411,11 @@ export class CssScanner {
var start = this.index; var start = this.index;
var startingColumn = this.column; var startingColumn = this.column;
var parenBalance = 1; var parenBalance = 1;
while (this.peek != $EOF && parenBalance > 0) { while (this.peek != chars.$EOF && parenBalance > 0) {
this.advance(); this.advance();
if (this.peek == $LPAREN) { if (this.peek == chars.$LPAREN) {
parenBalance++; parenBalance++;
} else if (this.peek == $RPAREN) { } else if (this.peek == chars.$RPAREN) {
parenBalance--; parenBalance--;
} }
} }
@ -440,7 +439,7 @@ export class CssScanner {
} }
scanAtExpression(): CssToken { scanAtExpression(): CssToken {
if (this.assertCondition(this.peek == $AT, 'Expected @ value')) { if (this.assertCondition(this.peek == chars.$AT, 'Expected @ value')) {
return null; 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 { function isCharMatch(target: number, previous: number, code: number): boolean {
return code == target && previous != $BACKSLASH; return code == target && previous != chars.$BACKSLASH;
}
function isDigit(code: number): boolean {
return $0 <= code && code <= $9;
} }
function isCommentStart(code: number, next: number): boolean { 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 { 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 { function isStringStart(code: number, next: number): boolean {
var target = code; var target = code;
if (target == $BACKSLASH) { if (target == chars.$BACKSLASH) {
target = next; target = next;
} }
return target == $DQ || target == $SQ; return target == chars.$DQ || target == chars.$SQ;
} }
function isIdentifierStart(code: number, next: number): boolean { function isIdentifierStart(code: number, next: number): boolean {
var target = code; var target = code;
if (target == $MINUS) { if (target == chars.$MINUS) {
target = next; target = next;
} }
return ($a <= target && target <= $z) || ($A <= target && target <= $Z) || target == $BACKSLASH || return chars.isAsciiLetter(target) || target == chars.$BACKSLASH || target == chars.$MINUS ||
target == $MINUS || target == $_; target == chars.$_;
} }
function isIdentifierPart(target: number): boolean { function isIdentifierPart(target: number): boolean {
return ($a <= target && target <= $z) || ($A <= target && target <= $Z) || target == $BACKSLASH || return chars.isAsciiLetter(target) || target == chars.$BACKSLASH || target == chars.$MINUS ||
target == $MINUS || target == $_ || isDigit(target); target == chars.$_ || chars.isDigit(target);
} }
function isValidPseudoSelectorCharacter(code: number): boolean { function isValidPseudoSelectorCharacter(code: number): boolean {
switch (code) { switch (code) {
case $LPAREN: case chars.$LPAREN:
case $RPAREN: case chars.$RPAREN:
return true; return true;
default: default:
return false; return false;
@ -535,18 +526,18 @@ function isValidPseudoSelectorCharacter(code: number): boolean {
} }
function isValidKeyframeBlockCharacter(code: number): boolean { function isValidKeyframeBlockCharacter(code: number): boolean {
return code == $PERCENT; return code == chars.$PERCENT;
} }
function isValidAttributeSelectorCharacter(code: number): boolean { function isValidAttributeSelectorCharacter(code: number): boolean {
// value^*|$~=something // value^*|$~=something
switch (code) { switch (code) {
case $$: case chars.$$:
case $PIPE: case chars.$PIPE:
case $CARET: case chars.$CARET:
case $TILDA: case chars.$TILDA:
case $STAR: case chars.$STAR:
case $EQ: case chars.$EQ:
return true; return true;
default: default:
return false; return false;
@ -559,17 +550,17 @@ function isValidSelectorCharacter(code: number): boolean {
// #id, .class, *+~> // #id, .class, *+~>
// tag:PSEUDO // tag:PSEUDO
switch (code) { switch (code) {
case $HASH: case chars.$HASH:
case $PERIOD: case chars.$PERIOD:
case $TILDA: case chars.$TILDA:
case $STAR: case chars.$STAR:
case $PLUS: case chars.$PLUS:
case $GT: case chars.$GT:
case $COLON: case chars.$COLON:
case $PIPE: case chars.$PIPE:
case $COMMA: case chars.$COMMA:
case $LBRACKET: case chars.$LBRACKET:
case $RBRACKET: case chars.$RBRACKET:
return true; return true;
default: default:
return false; return false;
@ -580,16 +571,16 @@ function isValidStyleBlockCharacter(code: number): boolean {
// key:value; // key:value;
// key:calc(something ... ) // key:calc(something ... )
switch (code) { switch (code) {
case $HASH: case chars.$HASH:
case $SEMICOLON: case chars.$SEMICOLON:
case $COLON: case chars.$COLON:
case $PERCENT: case chars.$PERCENT:
case $SLASH: case chars.$SLASH:
case $BACKSLASH: case chars.$BACKSLASH:
case $BANG: case chars.$BANG:
case $PERIOD: case chars.$PERIOD:
case $LPAREN: case chars.$LPAREN:
case $RPAREN: case chars.$RPAREN:
return true; return true;
default: default:
return false; return false;
@ -599,11 +590,11 @@ function isValidStyleBlockCharacter(code: number): boolean {
function isValidMediaQueryRuleCharacter(code: number): boolean { function isValidMediaQueryRuleCharacter(code: number): boolean {
// (min-width: 7.5em) and (orientation: landscape) // (min-width: 7.5em) and (orientation: landscape)
switch (code) { switch (code) {
case $LPAREN: case chars.$LPAREN:
case $RPAREN: case chars.$RPAREN:
case $COLON: case chars.$COLON:
case $PERCENT: case chars.$PERCENT:
case $PERIOD: case chars.$PERIOD:
return true; return true;
default: default:
return false; return false;
@ -613,21 +604,21 @@ function isValidMediaQueryRuleCharacter(code: number): boolean {
function isValidAtRuleCharacter(code: number): boolean { function isValidAtRuleCharacter(code: number): boolean {
// @document url(http://www.w3.org/page?something=on#hash), // @document url(http://www.w3.org/page?something=on#hash),
switch (code) { switch (code) {
case $LPAREN: case chars.$LPAREN:
case $RPAREN: case chars.$RPAREN:
case $COLON: case chars.$COLON:
case $PERCENT: case chars.$PERCENT:
case $PERIOD: case chars.$PERIOD:
case $SLASH: case chars.$SLASH:
case $BACKSLASH: case chars.$BACKSLASH:
case $HASH: case chars.$HASH:
case $EQ: case chars.$EQ:
case $QUESTION: case chars.$QUESTION:
case $AMPERSAND: case chars.$AMPERSAND:
case $STAR: case chars.$STAR:
case $COMMA: case chars.$COMMA:
case $MINUS: case chars.$MINUS:
case $PLUS: case chars.$PLUS:
return true; return true;
default: default:
return false; return false;
@ -636,14 +627,14 @@ function isValidAtRuleCharacter(code: number): boolean {
function isValidStyleFunctionCharacter(code: number): boolean { function isValidStyleFunctionCharacter(code: number): boolean {
switch (code) { switch (code) {
case $PERIOD: case chars.$PERIOD:
case $MINUS: case chars.$MINUS:
case $PLUS: case chars.$PLUS:
case $STAR: case chars.$STAR:
case $SLASH: case chars.$SLASH:
case $LPAREN: case chars.$LPAREN:
case $RPAREN: case chars.$RPAREN:
case $COMMA: case chars.$COMMA:
return true; return true;
default: default:
return false; return false;
@ -653,7 +644,7 @@ function isValidStyleFunctionCharacter(code: number): boolean {
function isValidBlockCharacter(code: number): boolean { function isValidBlockCharacter(code: number): boolean {
// @something { } // @something { }
// IDENT // IDENT
return code == $AT; return code == chars.$AT;
} }
function isValidCssCharacter(code: number, mode: CssLexerMode): boolean { 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 { function charCode(input: string, index: number): number {
return index >= input.length ? $EOF : StringWrapper.charCodeAt(input, index); return index >= input.length ? chars.$EOF : StringWrapper.charCodeAt(input, index);
} }
function charStr(code: number): string { function charStr(code: number): string {
return StringWrapper.fromCharCode(code); return StringWrapper.fromCharCode(code);
} }
export function isNewline(code: any /** TODO #9100 */): boolean { export function isNewline(code: number): boolean {
switch (code) { switch (code) {
case $FF: case chars.$FF:
case $CR: case chars.$CR:
case $LF: case chars.$LF:
case $VTAB: case chars.$VTAB:
return true; return true;
default: 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 * as chars from './chars';
import {NumberWrapper, StringWrapper, isPresent} from './facade/lang'; import {CssLexerMode, CssScanner, CssToken, CssTokenType, generateErrorMessage, isNewline} from './css_lexer';
import {isPresent} from './facade/lang';
import {ParseError, ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util'; import {ParseError, ParseLocation, ParseSourceFile, ParseSourceSpan} from './parse_util';
const SPACE_OPERATOR = ' '; const SPACE_OPERATOR = ' ';
@ -43,13 +44,13 @@ function _pseudoSelectorSupportsInnerSelectors(name: string): boolean {
function isSelectorOperatorCharacter(code: number): boolean { function isSelectorOperatorCharacter(code: number): boolean {
switch (code) { switch (code) {
case $SLASH: case chars.$SLASH:
case $TILDA: case chars.$TILDA:
case $PLUS: case chars.$PLUS:
case $GT: case chars.$GT:
return true; return true;
default: default:
return isWhitespace(code); return chars.isWhitespace(code);
} }
} }
@ -69,22 +70,22 @@ function getDelimFromToken(token: CssToken): number {
function getDelimFromCharacter(code: number): number { function getDelimFromCharacter(code: number): number {
switch (code) { switch (code) {
case $EOF: case chars.$EOF:
return EOF_DELIM_FLAG; return EOF_DELIM_FLAG;
case $COMMA: case chars.$COMMA:
return COMMA_DELIM_FLAG; return COMMA_DELIM_FLAG;
case $COLON: case chars.$COLON:
return COLON_DELIM_FLAG; return COLON_DELIM_FLAG;
case $SEMICOLON: case chars.$SEMICOLON:
return SEMICOLON_DELIM_FLAG; return SEMICOLON_DELIM_FLAG;
case $RBRACE: case chars.$RBRACE:
return RBRACE_DELIM_FLAG; return RBRACE_DELIM_FLAG;
case $LBRACE: case chars.$LBRACE:
return LBRACE_DELIM_FLAG; return LBRACE_DELIM_FLAG;
case $RPAREN: case chars.$RPAREN:
return RPAREN_DELIM_FLAG; return RPAREN_DELIM_FLAG;
case $SPACE: case chars.$SPACE:
case $TAB: case chars.$TAB:
return SPACE_DELIM_FLAG; return SPACE_DELIM_FLAG;
default: default:
return isNewline(code) ? NEWLINE_DELIM_FLAG : 0; return isNewline(code) ? NEWLINE_DELIM_FLAG : 0;
@ -187,7 +188,7 @@ export class CssParser {
const start = this._getScannerIndex(); const start = this._getScannerIndex();
var results: any[] /** TODO #9100 */ = []; var results: any[] /** TODO #9100 */ = [];
this._scanner.consumeEmptyStatements(); this._scanner.consumeEmptyStatements();
while (this._scanner.peek != $EOF) { while (this._scanner.peek != chars.$EOF) {
this._scanner.setMode(CssLexerMode.BLOCK); this._scanner.setMode(CssLexerMode.BLOCK);
results.push(this._parseRule(delimiters)); results.push(this._parseRule(delimiters));
} }
@ -197,7 +198,7 @@ export class CssParser {
/** @internal */ /** @internal */
_parseRule(delimiters: number): CssRuleAst { _parseRule(delimiters: number): CssRuleAst {
if (this._scanner.peek == $AT) { if (this._scanner.peek == chars.$AT) {
return this._parseAtRule(delimiters); return this._parseAtRule(delimiters);
} }
return this._parseSelectorRule(delimiters); return this._parseSelectorRule(delimiters);
@ -277,7 +278,7 @@ export class CssParser {
this._collectUntilDelim(delimiters | LBRACE_DELIM_FLAG | SEMICOLON_DELIM_FLAG) this._collectUntilDelim(delimiters | LBRACE_DELIM_FLAG | SEMICOLON_DELIM_FLAG)
.forEach((token) => { listOfTokens.push(token); }); .forEach((token) => { listOfTokens.push(token); });
if (this._scanner.peek == $LBRACE) { if (this._scanner.peek == chars.$LBRACE) {
listOfTokens.push(this._consume(CssTokenType.Character, '{')); listOfTokens.push(this._consume(CssTokenType.Character, '{'));
this._collectUntilDelim(delimiters | RBRACE_DELIM_FLAG | LBRACE_DELIM_FLAG) this._collectUntilDelim(delimiters | RBRACE_DELIM_FLAG | LBRACE_DELIM_FLAG)
.forEach((token) => { listOfTokens.push(token); }); .forEach((token) => { listOfTokens.push(token); });
@ -387,7 +388,7 @@ export class CssParser {
delimiters |= LBRACE_DELIM_FLAG; delimiters |= LBRACE_DELIM_FLAG;
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) { while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
stepTokens.push(this._parseKeyframeLabel(delimiters | COMMA_DELIM_FLAG)); stepTokens.push(this._parseKeyframeLabel(delimiters | COMMA_DELIM_FLAG));
if (this._scanner.peek != $LBRACE) { if (this._scanner.peek != chars.$LBRACE) {
this._consume(CssTokenType.Character, ','); this._consume(CssTokenType.Character, ',');
} }
} }
@ -415,7 +416,7 @@ export class CssParser {
var startToken = this._consume(CssTokenType.Character, ':'); var startToken = this._consume(CssTokenType.Character, ':');
var tokens = [startToken]; var tokens = [startToken];
if (this._scanner.peek == $COLON) { // ::something if (this._scanner.peek == chars.$COLON) { // ::something
startToken = this._consume(CssTokenType.Character, ':'); startToken = this._consume(CssTokenType.Character, ':');
tokens.push(startToken); tokens.push(startToken);
} }
@ -430,7 +431,7 @@ export class CssParser {
tokens.push(pseudoSelectorToken); tokens.push(pseudoSelectorToken);
// host(), lang(), nth-child(), etc... // host(), lang(), nth-child(), etc...
if (this._scanner.peek == $LPAREN) { if (this._scanner.peek == chars.$LPAREN) {
this._scanner.setMode(CssLexerMode.PSEUDO_SELECTOR_WITH_ARGUMENTS); this._scanner.setMode(CssLexerMode.PSEUDO_SELECTOR_WITH_ARGUMENTS);
var openParenToken = this._consume(CssTokenType.Character, '('); var openParenToken = this._consume(CssTokenType.Character, '(');
@ -491,13 +492,13 @@ export class CssParser {
var peek = this._scanner.peek; var peek = this._scanner.peek;
switch (peek) { switch (peek) {
case $COLON: case chars.$COLON:
var innerPseudo = this._parsePseudoSelector(delimiters); var innerPseudo = this._parsePseudoSelector(delimiters);
pseudoSelectors.push(innerPseudo); pseudoSelectors.push(innerPseudo);
this._scanner.setMode(CssLexerMode.SELECTOR); this._scanner.setMode(CssLexerMode.SELECTOR);
break; break;
case $LBRACKET: case chars.$LBRACKET:
// we set the mode after the scan because attribute mode does not // we set the mode after the scan because attribute mode does not
// allow attribute [] values. And this also will catch any errors // allow attribute [] values. And this also will catch any errors
// if an extra "[" is used inside. // if an extra "[" is used inside.
@ -505,7 +506,7 @@ export class CssParser {
this._scanner.setMode(CssLexerMode.ATTRIBUTE_SELECTOR); this._scanner.setMode(CssLexerMode.ATTRIBUTE_SELECTOR);
break; break;
case $RBRACKET: case chars.$RBRACKET:
if (this._scanner.getMode() != CssLexerMode.ATTRIBUTE_SELECTOR) { if (this._scanner.getMode() != CssLexerMode.ATTRIBUTE_SELECTOR) {
hasAttributeError = true; hasAttributeError = true;
} }
@ -580,7 +581,7 @@ export class CssParser {
case GT_CHARACTER: case GT_CHARACTER:
// >>> operator // >>> 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);
this._consume(CssTokenType.Character, GT_CHARACTER); this._consume(CssTokenType.Character, GT_CHARACTER);
token = new CssToken( token = new CssToken(
@ -601,7 +602,7 @@ export class CssParser {
// if we do come across one or more spaces inside of // if we do come across one or more spaces inside of
// the operators loop then an empty space is still a // the operators loop then an empty space is still a
// valid operator to use if something else was not found // 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; operator = lastOperatorToken;
} }
@ -647,7 +648,7 @@ export class CssParser {
while (!characterContainsDelimiter(this._scanner.peek, delimiters)) { while (!characterContainsDelimiter(this._scanner.peek, delimiters)) {
var token: CssToken; var token: CssToken;
if (isPresent(previous) && previous.type == CssTokenType.Identifier && if (isPresent(previous) && previous.type == CssTokenType.Identifier &&
this._scanner.peek == $LPAREN) { this._scanner.peek == chars.$LPAREN) {
token = this._consume(CssTokenType.Character, '('); token = this._consume(CssTokenType.Character, '(');
tokens.push(token); tokens.push(token);
@ -676,9 +677,9 @@ export class CssParser {
this._scanner.consumeWhitespace(); this._scanner.consumeWhitespace();
var code = this._scanner.peek; var code = this._scanner.peek;
if (code == $SEMICOLON) { if (code == chars.$SEMICOLON) {
this._consume(CssTokenType.Character, ';'); this._consume(CssTokenType.Character, ';');
} else if (code != $RBRACE) { } else if (code != chars.$RBRACE) {
this._error( this._error(
generateErrorMessage( generateErrorMessage(
this._scanner.input, `The CSS key/value definition did not end with a semicolon`, 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); this._scanner.setMode(CssLexerMode.STYLE_BLOCK);
var result = this._consume(CssTokenType.Character, '{'); var result = this._consume(CssTokenType.Character, '{');
if (result.numValue != $LBRACE) { if (result.numValue != chars.$LBRACE) {
return null; return null;
} }
@ -767,20 +768,20 @@ export class CssParser {
// there are a few cases as to what could happen if it // there are a few cases as to what could happen if it
// is missing // is missing
switch (this._scanner.peek) { switch (this._scanner.peek) {
case $COLON: case chars.$COLON:
this._consume(CssTokenType.Character, ':'); this._consume(CssTokenType.Character, ':');
parseValue = true; parseValue = true;
break; break;
case $SEMICOLON: case chars.$SEMICOLON:
case $RBRACE: case chars.$RBRACE:
case $EOF: case chars.$EOF:
parseValue = false; parseValue = false;
break; break;
default: default:
var propStr = [prop.strValue]; var propStr = [prop.strValue];
if (this._scanner.peek != $COLON) { if (this._scanner.peek != chars.$COLON) {
// this will throw the error // this will throw the error
var nextValue = this._consume(CssTokenType.Character, ':'); var nextValue = this._consume(CssTokenType.Character, ':');
propStr.push(nextValue.strValue); propStr.push(nextValue.strValue);
@ -795,7 +796,7 @@ export class CssParser {
} }
// this means we've reached the end of the definition and/or block // 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, ':'); this._consume(CssTokenType.Character, ':');
parseValue = true; parseValue = true;
} else { } else {
@ -960,8 +961,6 @@ export class CssSelectorAst extends CssSelectorPartAst {
} }
export class CssSimpleSelectorAst extends CssSelectorPartAst { export class CssSimpleSelectorAst extends CssSelectorPartAst {
public selectorStrValue: string;
constructor( constructor(
start: number, end: number, public tokens: CssToken[], public strValue: string, start: number, end: number, public tokens: CssToken[], public strValue: string,
public pseudoSelectors: CssPseudoSelectorAst[], public operator: CssToken) { public pseudoSelectors: CssPseudoSelectorAst[], public operator: CssToken) {

View File

@ -1,5 +1,5 @@
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import * as chars from '../chars';
import {SetWrapper} from '../facade/collection'; import {SetWrapper} from '../facade/collection';
import {BaseException} from '../facade/exceptions'; import {BaseException} from '../facade/exceptions';
import {NumberWrapper, StringJoiner, StringWrapper, isPresent} from '../facade/lang'; import {NumberWrapper, StringJoiner, StringWrapper, isPresent} from '../facade/lang';
@ -13,6 +13,8 @@ export enum TokenType {
Number Number
} }
const KEYWORDS = ['var', 'let', 'null', 'undefined', 'true', 'false', 'if', 'else'];
@Injectable() @Injectable()
export class Lexer { export class Lexer {
tokenize(text: string): any[] { tokenize(text: string): any[] {
@ -109,58 +111,8 @@ function newNumberToken(index: number, n: number): Token {
return new Token(index, TokenType.Number, n, ''); return new Token(index, TokenType.Number, n, '');
} }
export var EOF: Token = new Token(-1, TokenType.Character, 0, ''); 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 { export class ScannerError extends BaseException {
constructor(public message: string) { super(); } constructor(public message: string) { super(); }
@ -179,16 +131,16 @@ class _Scanner {
advance() { advance() {
this.peek = 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 { scanToken(): Token {
var input = this.input, length = this.length, peek = this.peek, index = this.index; var input = this.input, length = this.length, peek = this.peek, index = this.index;
// Skip whitespace. // Skip whitespace.
while (peek <= $SPACE) { while (peek <= chars.$SPACE) {
if (++index >= length) { if (++index >= length) {
peek = $EOF; peek = chars.$EOF;
break; break;
} else { } else {
peek = StringWrapper.charCodeAt(input, index); peek = StringWrapper.charCodeAt(input, index);
@ -204,49 +156,50 @@ class _Scanner {
// Handle identifiers and numbers. // Handle identifiers and numbers.
if (isIdentifierStart(peek)) return this.scanIdentifier(); 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; var start: number = index;
switch (peek) { switch (peek) {
case $PERIOD: case chars.$PERIOD:
this.advance(); this.advance();
return isDigit(this.peek) ? this.scanNumber(start) : newCharacterToken(start, $PERIOD); return chars.isDigit(this.peek) ? this.scanNumber(start) :
case $LPAREN: newCharacterToken(start, chars.$PERIOD);
case $RPAREN: case chars.$LPAREN:
case $LBRACE: case chars.$RPAREN:
case $RBRACE: case chars.$LBRACE:
case $LBRACKET: case chars.$RBRACE:
case $RBRACKET: case chars.$LBRACKET:
case $COMMA: case chars.$RBRACKET:
case $COLON: case chars.$COMMA:
case $SEMICOLON: case chars.$COLON:
case chars.$SEMICOLON:
return this.scanCharacter(start, peek); return this.scanCharacter(start, peek);
case $SQ: case chars.$SQ:
case $DQ: case chars.$DQ:
return this.scanString(); return this.scanString();
case $HASH: case chars.$HASH:
case $PLUS: case chars.$PLUS:
case $MINUS: case chars.$MINUS:
case $STAR: case chars.$STAR:
case $SLASH: case chars.$SLASH:
case $PERCENT: case chars.$PERCENT:
case $CARET: case chars.$CARET:
return this.scanOperator(start, StringWrapper.fromCharCode(peek)); return this.scanOperator(start, StringWrapper.fromCharCode(peek));
case $QUESTION: case chars.$QUESTION:
return this.scanComplexOperator(start, '?', $PERIOD, '.'); return this.scanComplexOperator(start, '?', chars.$PERIOD, '.');
case $LT: case chars.$LT:
case $GT: case chars.$GT:
return this.scanComplexOperator(start, StringWrapper.fromCharCode(peek), $EQ, '='); return this.scanComplexOperator(start, StringWrapper.fromCharCode(peek), chars.$EQ, '=');
case $BANG: case chars.$BANG:
case $EQ: case chars.$EQ:
return this.scanComplexOperator( return this.scanComplexOperator(
start, StringWrapper.fromCharCode(peek), $EQ, '=', $EQ, '='); start, StringWrapper.fromCharCode(peek), chars.$EQ, '=', chars.$EQ, '=');
case $AMPERSAND: case chars.$AMPERSAND:
return this.scanComplexOperator(start, '&', $AMPERSAND, '&'); return this.scanComplexOperator(start, '&', chars.$AMPERSAND, '&');
case $BAR: case chars.$BAR:
return this.scanComplexOperator(start, '|', $BAR, '|'); return this.scanComplexOperator(start, '|', chars.$BAR, '|');
case $NBSP: case chars.$NBSP:
while (isWhitespace(this.peek)) this.advance(); while (chars.isWhitespace(this.peek)) this.advance();
return this.scanToken(); return this.scanToken();
} }
@ -297,25 +250,22 @@ class _Scanner {
this.advance(); this.advance();
while (isIdentifierPart(this.peek)) this.advance(); while (isIdentifierPart(this.peek)) this.advance();
var str: string = this.input.substring(start, this.index); var str: string = this.input.substring(start, this.index);
if (SetWrapper.has(KEYWORDS, str)) { return KEYWORDS.indexOf(str) > -1 ? newKeywordToken(start, str) :
return newKeywordToken(start, str); newIdentifierToken(start, str);
} else {
return newIdentifierToken(start, str);
}
} }
scanNumber(start: number): Token { scanNumber(start: number): Token {
var simple: boolean = (this.index === start); var simple: boolean = (this.index === start);
this.advance(); // Skip initial digit. this.advance(); // Skip initial digit.
while (true) { while (true) {
if (isDigit(this.peek)) { if (chars.isDigit(this.peek)) {
// Do nothing. // Do nothing.
} else if (this.peek == $PERIOD) { } else if (this.peek == chars.$PERIOD) {
simple = false; simple = false;
} else if (isExponentStart(this.peek)) { } else if (isExponentStart(this.peek)) {
this.advance(); this.advance();
if (isExponentSign(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; simple = false;
} else { } else {
break; break;
@ -323,7 +273,6 @@ class _Scanner {
this.advance(); this.advance();
} }
var str: string = this.input.substring(start, this.index); var str: string = this.input.substring(start, this.index);
// TODO
var value: number = var value: number =
simple ? NumberWrapper.parseIntAutoRadix(str) : NumberWrapper.parseFloat(str); simple ? NumberWrapper.parseIntAutoRadix(str) : NumberWrapper.parseFloat(str);
return newNumberToken(start, value); return newNumberToken(start, value);
@ -339,12 +288,12 @@ class _Scanner {
var input: string = this.input; var input: string = this.input;
while (this.peek != quote) { while (this.peek != quote) {
if (this.peek == $BACKSLASH) { if (this.peek == chars.$BACKSLASH) {
if (buffer == null) buffer = new StringJoiner(); if (buffer == null) buffer = new StringJoiner();
buffer.add(input.substring(marker, this.index)); buffer.add(input.substring(marker, this.index));
this.advance(); this.advance();
var unescapedCode: number; var unescapedCode: number;
if (this.peek == $u) { if (this.peek == chars.$u) {
// 4 character hex code for unicode character. // 4 character hex code for unicode character.
var hex: string = input.substring(this.index + 1, this.index + 5); var hex: string = input.substring(this.index + 1, this.index + 5);
try { try {
@ -361,7 +310,7 @@ class _Scanner {
} }
buffer.add(StringWrapper.fromCharCode(unescapedCode)); buffer.add(StringWrapper.fromCharCode(unescapedCode));
marker = this.index; marker = this.index;
} else if (this.peek == $EOF) { } else if (this.peek == chars.$EOF) {
this.error('Unterminated quote', 0); this.error('Unterminated quote', 0);
} else { } else {
this.advance(); 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 { 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 { export function isIdentifier(input: string): boolean {
@ -400,7 +346,7 @@ export function isIdentifier(input: string): boolean {
var scanner = new _Scanner(input); var scanner = new _Scanner(input);
if (!isIdentifierStart(scanner.peek)) return false; if (!isIdentifierStart(scanner.peek)) return false;
scanner.advance(); scanner.advance();
while (scanner.peek !== $EOF) { while (scanner.peek !== chars.$EOF) {
if (!isIdentifierPart(scanner.peek)) return false; if (!isIdentifierPart(scanner.peek)) return false;
scanner.advance(); scanner.advance();
} }
@ -408,48 +354,35 @@ export function isIdentifier(input: string): boolean {
} }
function isIdentifierPart(code: number): boolean { function isIdentifierPart(code: number): boolean {
return ($a <= code && code <= $z) || ($A <= code && code <= $Z) || ($0 <= code && code <= $9) || return chars.isAsciiLetter(code) || chars.isDigit(code) || (code == chars.$_) ||
(code == $_) || (code == $$); (code == chars.$$);
}
function isDigit(code: number): boolean {
return $0 <= code && code <= $9;
} }
function isExponentStart(code: number): boolean { function isExponentStart(code: number): boolean {
return code == $e || code == $E; return code == chars.$e || code == chars.$E;
} }
function isExponentSign(code: number): boolean { function isExponentSign(code: number): boolean {
return code == $MINUS || code == $PLUS; return code == chars.$MINUS || code == chars.$PLUS;
} }
export function isQuote(code: number): boolean { 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 { function unescape(code: number): number {
switch (code) { switch (code) {
case $n: case chars.$n:
return $LF; return chars.$LF;
case $f: case chars.$f:
return $FF; return chars.$FF;
case $r: case chars.$r:
return $CR; return chars.$CR;
case $t: case chars.$t:
return $TAB; return chars.$TAB;
case $v: case chars.$v:
return $VTAB; return chars.$VTAB;
default: default:
return code; 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 {Injectable} from '@angular/core';
import * as chars from '../chars';
import {ListWrapper} from '../facade/collection'; import {ListWrapper} from '../facade/collection';
import {BaseException} from '../facade/exceptions'; import {BaseException} from '../facade/exceptions';
import {RegExpWrapper, StringWrapper, escapeRegExp, isBlank, isPresent} from '../facade/lang'; import {RegExpWrapper, StringWrapper, escapeRegExp, isBlank, isPresent} from '../facade/lang';
import {DEFAULT_INTERPOLATION_CONFIG, InterpolationConfig} from '../interpolation_config'; 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 {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(); var _implicitReceiver = new ImplicitReceiver();
@ -152,7 +153,7 @@ export class Parser {
let char = StringWrapper.charCodeAt(input, i); let char = StringWrapper.charCodeAt(input, i);
let nextChar = StringWrapper.charCodeAt(input, i + 1); 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) { if (outerQuote === char) {
outerQuote = null; outerQuote = null;
@ -267,11 +268,11 @@ export class _ParseAST {
var expr = this.parsePipe(); var expr = this.parsePipe();
exprs.push(expr); exprs.push(expr);
if (this.optionalCharacter($SEMICOLON)) { if (this.optionalCharacter(chars.$SEMICOLON)) {
if (!this.parseAction) { if (!this.parseAction) {
this.error('Binding expression cannot contain chained expression'); this.error('Binding expression cannot contain chained expression');
} }
while (this.optionalCharacter($SEMICOLON)) { while (this.optionalCharacter(chars.$SEMICOLON)) {
} // read all semicolons } // read all semicolons
} else if (this.index < this.tokens.length) { } else if (this.index < this.tokens.length) {
this.error(`Unexpected token '${this.next}'`); this.error(`Unexpected token '${this.next}'`);
@ -292,7 +293,7 @@ export class _ParseAST {
do { do {
var name = this.expectIdentifierOrKeyword(); var name = this.expectIdentifierOrKeyword();
var args: AST[] = []; var args: AST[] = [];
while (this.optionalCharacter($COLON)) { while (this.optionalCharacter(chars.$COLON)) {
args.push(this.parseExpression()); args.push(this.parseExpression());
} }
result = new BindingPipe(result, name, args); result = new BindingPipe(result, name, args);
@ -310,7 +311,7 @@ export class _ParseAST {
if (this.optionalOperator('?')) { if (this.optionalOperator('?')) {
var yes = this.parsePipe(); var yes = this.parsePipe();
if (!this.optionalCharacter($COLON)) { if (!this.optionalCharacter(chars.$COLON)) {
var end = this.inputIndex; var end = this.inputIndex;
var expression = this.input.substring(start, end); var expression = this.input.substring(start, end);
this.error(`Conditional expression ${expression} requires all 3 expressions`); this.error(`Conditional expression ${expression} requires all 3 expressions`);
@ -421,15 +422,15 @@ export class _ParseAST {
parseCallChain(): AST { parseCallChain(): AST {
var result = this.parsePrimary(); var result = this.parsePrimary();
while (true) { while (true) {
if (this.optionalCharacter($PERIOD)) { if (this.optionalCharacter(chars.$PERIOD)) {
result = this.parseAccessMemberOrMethodCall(result, false); result = this.parseAccessMemberOrMethodCall(result, false);
} else if (this.optionalOperator('?.')) { } else if (this.optionalOperator('?.')) {
result = this.parseAccessMemberOrMethodCall(result, true); result = this.parseAccessMemberOrMethodCall(result, true);
} else if (this.optionalCharacter($LBRACKET)) { } else if (this.optionalCharacter(chars.$LBRACKET)) {
var key = this.parsePipe(); var key = this.parsePipe();
this.expectCharacter($RBRACKET); this.expectCharacter(chars.$RBRACKET);
if (this.optionalOperator('=')) { if (this.optionalOperator('=')) {
var value = this.parseConditional(); var value = this.parseConditional();
result = new KeyedWrite(result, key, value); result = new KeyedWrite(result, key, value);
@ -437,9 +438,9 @@ export class _ParseAST {
result = new KeyedRead(result, key); result = new KeyedRead(result, key);
} }
} else if (this.optionalCharacter($LPAREN)) { } else if (this.optionalCharacter(chars.$LPAREN)) {
var args = this.parseCallArguments(); var args = this.parseCallArguments();
this.expectCharacter($RPAREN); this.expectCharacter(chars.$RPAREN);
result = new FunctionCall(result, args); result = new FunctionCall(result, args);
} else { } else {
@ -449,9 +450,9 @@ export class _ParseAST {
} }
parsePrimary(): AST { parsePrimary(): AST {
if (this.optionalCharacter($LPAREN)) { if (this.optionalCharacter(chars.$LPAREN)) {
let result = this.parsePipe(); let result = this.parsePipe();
this.expectCharacter($RPAREN); this.expectCharacter(chars.$RPAREN);
return result; return result;
} else if (this.next.isKeywordNull() || this.next.isKeywordUndefined()) { } else if (this.next.isKeywordNull() || this.next.isKeywordUndefined()) {
this.advance(); this.advance();
@ -465,12 +466,12 @@ export class _ParseAST {
this.advance(); this.advance();
return new LiteralPrimitive(false); return new LiteralPrimitive(false);
} else if (this.optionalCharacter($LBRACKET)) { } else if (this.optionalCharacter(chars.$LBRACKET)) {
var elements = this.parseExpressionList($RBRACKET); var elements = this.parseExpressionList(chars.$RBRACKET);
this.expectCharacter($RBRACKET); this.expectCharacter(chars.$RBRACKET);
return new LiteralArray(elements); return new LiteralArray(elements);
} else if (this.next.isCharacter($LBRACE)) { } else if (this.next.isCharacter(chars.$LBRACE)) {
return this.parseLiteralMap(); return this.parseLiteralMap();
} else if (this.next.isIdentifier()) { } else if (this.next.isIdentifier()) {
@ -501,7 +502,7 @@ export class _ParseAST {
if (!this.next.isCharacter(terminator)) { if (!this.next.isCharacter(terminator)) {
do { do {
result.push(this.parsePipe()); result.push(this.parsePipe());
} while (this.optionalCharacter($COMMA)); } while (this.optionalCharacter(chars.$COMMA));
} }
return result; return result;
} }
@ -509,15 +510,15 @@ export class _ParseAST {
parseLiteralMap(): LiteralMap { parseLiteralMap(): LiteralMap {
var keys: string[] = []; var keys: string[] = [];
var values: AST[] = []; var values: AST[] = [];
this.expectCharacter($LBRACE); this.expectCharacter(chars.$LBRACE);
if (!this.optionalCharacter($RBRACE)) { if (!this.optionalCharacter(chars.$RBRACE)) {
do { do {
var key = this.expectIdentifierOrKeywordOrString(); var key = this.expectIdentifierOrKeywordOrString();
keys.push(key); keys.push(key);
this.expectCharacter($COLON); this.expectCharacter(chars.$COLON);
values.push(this.parsePipe()); values.push(this.parsePipe());
} while (this.optionalCharacter($COMMA)); } while (this.optionalCharacter(chars.$COMMA));
this.expectCharacter($RBRACE); this.expectCharacter(chars.$RBRACE);
} }
return new LiteralMap(keys, values); return new LiteralMap(keys, values);
} }
@ -525,9 +526,9 @@ export class _ParseAST {
parseAccessMemberOrMethodCall(receiver: AST, isSafe: boolean = false): AST { parseAccessMemberOrMethodCall(receiver: AST, isSafe: boolean = false): AST {
let id = this.expectIdentifierOrKeyword(); let id = this.expectIdentifierOrKeyword();
if (this.optionalCharacter($LPAREN)) { if (this.optionalCharacter(chars.$LPAREN)) {
let args = this.parseCallArguments(); let args = this.parseCallArguments();
this.expectCharacter($RPAREN); this.expectCharacter(chars.$RPAREN);
return isSafe ? new SafeMethodCall(receiver, id, args) : new MethodCall(receiver, id, args); return isSafe ? new SafeMethodCall(receiver, id, args) : new MethodCall(receiver, id, args);
} else { } else {
@ -555,11 +556,11 @@ export class _ParseAST {
} }
parseCallArguments(): BindingPipe[] { parseCallArguments(): BindingPipe[] {
if (this.next.isCharacter($RPAREN)) return []; if (this.next.isCharacter(chars.$RPAREN)) return [];
var positionals: AST[] = []; var positionals: AST[] = [];
do { do {
positionals.push(this.parsePipe()); positionals.push(this.parsePipe());
} while (this.optionalCharacter($COMMA)); } while (this.optionalCharacter(chars.$COMMA));
return positionals as BindingPipe[]; return positionals as BindingPipe[];
} }
@ -605,7 +606,7 @@ export class _ParseAST {
key = prefix + key[0].toUpperCase() + key.substring(1); key = prefix + key[0].toUpperCase() + key.substring(1);
} }
} }
this.optionalCharacter($COLON); this.optionalCharacter(chars.$COLON);
var name: string = null; var name: string = null;
var expression: ASTWithSource = null; var expression: ASTWithSource = null;
if (keyIsVar) { if (keyIsVar) {
@ -623,8 +624,8 @@ export class _ParseAST {
expression = new ASTWithSource(ast, source, this.location); expression = new ASTWithSource(ast, source, this.location);
} }
bindings.push(new TemplateBinding(key, keyIsVar, name, expression)); bindings.push(new TemplateBinding(key, keyIsVar, name, expression));
if (!this.optionalCharacter($SEMICOLON)) { if (!this.optionalCharacter(chars.$SEMICOLON)) {
this.optionalCharacter($COMMA); this.optionalCharacter(chars.$COMMA);
} }
} }
return new TemplateBindingParseResult(bindings, warnings); 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)) { while (!predicate(this._peek)) {
this._advance(); this._advance();
} }
} }
private _requireCharCodeUntilFn(predicate: Function, len: number) { private _requireCharCodeUntilFn(predicate: (code: number) => boolean, len: number) {
var start = this._getLocation(); var start = this._getLocation();
this._attemptCharCodeUntilFn(predicate); this._attemptCharCodeUntilFn(predicate);
if (this._index - start.offset < len) { if (this._index - start.offset < len) {
@ -402,7 +402,7 @@ class _HtmlTokenizer {
let savedPos = this._savePosition(); let savedPos = this._savePosition();
let lowercaseTagName: string; let lowercaseTagName: string;
try { try {
if (!isAsciiLetter(this._peek)) { if (!chars.isAsciiLetter(this._peek)) {
throw this._createError(unexpectedCharacterErrorMsg(this._peek), this._getSpan()); throw this._createError(unexpectedCharacterErrorMsg(this._peek), this._getSpan());
} }
var nameStart = this._index; var nameStart = this._index;
@ -635,16 +635,12 @@ class _HtmlTokenizer {
} }
function isNotWhitespace(code: number): boolean { function isNotWhitespace(code: number): boolean {
return !isWhitespace(code) || code === chars.$EOF; return !chars.isWhitespace(code) || code === chars.$EOF;
}
function isWhitespace(code: number): boolean {
return (code >= chars.$TAB && code <= chars.$SPACE) || (code === chars.$NBSP);
} }
function isNameEnd(code: number): boolean { function isNameEnd(code: number): boolean {
return isWhitespace(code) || code === chars.$GT || code === chars.$SLASH || code === chars.$SQ || return chars.isWhitespace(code) || code === chars.$GT || code === chars.$SLASH ||
code === chars.$DQ || code === chars.$EQ; code === chars.$SQ || code === chars.$DQ || code === chars.$EQ;
} }
function isPrefixEnd(code: number): boolean { function isPrefixEnd(code: number): boolean {
@ -653,11 +649,11 @@ function isPrefixEnd(code: number): boolean {
} }
function isDigitEntityEnd(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 { 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 { 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 { function isExpansionCaseStart(peek: number): boolean {
return peek === chars.$EQ || isAsciiLetter(peek); return peek === chars.$EQ || chars.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;
} }
function compareCharCodeCaseInsensitive(code1: number, code2: number): boolean { function compareCharCodeCaseInsensitive(code1: number, code2: number): boolean {