2016-05-31 18:22:59 -04:00
|
|
|
import {NumberWrapper, StringWrapper, isPresent, resolveEnumToken} from '../facade/lang';
|
|
|
|
import {BaseException} from '../facade/exceptions';
|
2016-02-02 03:37:08 -05:00
|
|
|
|
2016-04-12 12:40:37 -04:00
|
|
|
import {
|
|
|
|
isWhitespace,
|
|
|
|
$EOF,
|
|
|
|
$HASH,
|
|
|
|
$TILDA,
|
|
|
|
$CARET,
|
|
|
|
$PERCENT,
|
|
|
|
$$,
|
|
|
|
$_,
|
|
|
|
$COLON,
|
|
|
|
$SQ,
|
|
|
|
$DQ,
|
|
|
|
$EQ,
|
|
|
|
$SLASH,
|
|
|
|
$BACKSLASH,
|
|
|
|
$PERIOD,
|
|
|
|
$STAR,
|
|
|
|
$PLUS,
|
|
|
|
$LPAREN,
|
|
|
|
$RPAREN,
|
|
|
|
$LBRACE,
|
|
|
|
$RBRACE,
|
|
|
|
$LBRACKET,
|
|
|
|
$RBRACKET,
|
|
|
|
$PIPE,
|
|
|
|
$COMMA,
|
|
|
|
$SEMICOLON,
|
|
|
|
$MINUS,
|
|
|
|
$BANG,
|
|
|
|
$QUESTION,
|
|
|
|
$AT,
|
|
|
|
$AMPERSAND,
|
|
|
|
$GT,
|
|
|
|
$a,
|
|
|
|
$A,
|
|
|
|
$z,
|
|
|
|
$Z,
|
|
|
|
$0,
|
|
|
|
$9,
|
|
|
|
$FF,
|
|
|
|
$CR,
|
|
|
|
$LF,
|
|
|
|
$VTAB
|
2016-04-28 20:50:03 -04:00
|
|
|
} from '@angular/compiler/src/chars';
|
2016-04-12 12:40:37 -04:00
|
|
|
|
|
|
|
export {
|
|
|
|
$EOF,
|
|
|
|
$AT,
|
|
|
|
$RBRACE,
|
|
|
|
$LBRACE,
|
|
|
|
$LBRACKET,
|
|
|
|
$RBRACKET,
|
|
|
|
$LPAREN,
|
|
|
|
$RPAREN,
|
|
|
|
$COMMA,
|
|
|
|
$COLON,
|
|
|
|
$SEMICOLON,
|
|
|
|
isWhitespace
|
2016-04-28 20:50:03 -04:00
|
|
|
} from '@angular/compiler/src/chars';
|
2016-02-02 03:37:08 -05:00
|
|
|
|
|
|
|
export enum CssTokenType {
|
|
|
|
EOF,
|
|
|
|
String,
|
|
|
|
Comment,
|
|
|
|
Identifier,
|
|
|
|
Number,
|
|
|
|
IdentifierOrNumber,
|
|
|
|
AtKeyword,
|
|
|
|
Character,
|
|
|
|
Whitespace,
|
|
|
|
Invalid
|
|
|
|
}
|
|
|
|
|
|
|
|
export enum CssLexerMode {
|
|
|
|
ALL,
|
|
|
|
ALL_TRACK_WS,
|
|
|
|
SELECTOR,
|
|
|
|
PSEUDO_SELECTOR,
|
|
|
|
ATTRIBUTE_SELECTOR,
|
|
|
|
AT_RULE_QUERY,
|
|
|
|
MEDIA_QUERY,
|
|
|
|
BLOCK,
|
|
|
|
KEYFRAME_BLOCK,
|
|
|
|
STYLE_BLOCK,
|
|
|
|
STYLE_VALUE,
|
|
|
|
STYLE_VALUE_FUNCTION,
|
|
|
|
STYLE_CALC_FUNCTION
|
|
|
|
}
|
|
|
|
|
|
|
|
export class LexedCssResult {
|
|
|
|
constructor(public error: CssScannerError, public token: CssToken) {}
|
|
|
|
}
|
|
|
|
|
2016-04-12 12:40:37 -04:00
|
|
|
export function generateErrorMessage(input: string, message: string, errorValue: string,
|
|
|
|
index: number, row: number, column: number): string {
|
2016-02-02 03:37:08 -05:00
|
|
|
return `${message} at column ${row}:${column} in expression [` +
|
2016-04-12 12:40:37 -04:00
|
|
|
findProblemCode(input, errorValue, index, column) + ']';
|
2016-02-02 03:37:08 -05:00
|
|
|
}
|
|
|
|
|
2016-04-12 12:40:37 -04:00
|
|
|
export function findProblemCode(input: string, errorValue: string, index: number,
|
|
|
|
column: number): string {
|
2016-02-02 03:37:08 -05:00
|
|
|
var endOfProblemLine = index;
|
|
|
|
var current = charCode(input, index);
|
|
|
|
while (current > 0 && !isNewline(current)) {
|
|
|
|
current = charCode(input, ++endOfProblemLine);
|
|
|
|
}
|
|
|
|
var choppedString = input.substring(0, endOfProblemLine);
|
2016-04-12 12:40:37 -04:00
|
|
|
var pointerPadding = "";
|
2016-02-02 03:37:08 -05:00
|
|
|
for (var i = 0; i < column; i++) {
|
2016-04-12 12:40:37 -04:00
|
|
|
pointerPadding += " ";
|
2016-02-02 03:37:08 -05:00
|
|
|
}
|
2016-04-12 12:40:37 -04:00
|
|
|
var pointerString = "";
|
2016-02-02 03:37:08 -05:00
|
|
|
for (var i = 0; i < errorValue.length; i++) {
|
2016-04-12 12:40:37 -04:00
|
|
|
pointerString += "^";
|
2016-02-02 03:37:08 -05:00
|
|
|
}
|
2016-04-12 12:40:37 -04:00
|
|
|
return choppedString + "\n" + pointerPadding + pointerString + "\n";
|
2016-02-02 03:37:08 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
export class CssToken {
|
|
|
|
numValue: number;
|
2016-04-12 12:40:37 -04:00
|
|
|
constructor(public index: number, public column: number, public line: number,
|
|
|
|
public type: CssTokenType, public strValue: string) {
|
2016-02-02 03:37:08 -05:00
|
|
|
this.numValue = charCode(strValue, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class CssLexer {
|
|
|
|
scan(text: string, trackComments: boolean = false): CssScanner {
|
|
|
|
return new CssScanner(text, trackComments);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class CssScannerError extends BaseException {
|
|
|
|
public rawMessage: string;
|
|
|
|
public message: string;
|
|
|
|
|
|
|
|
constructor(public token: CssToken, message) {
|
|
|
|
super('Css Parse Error: ' + message);
|
|
|
|
this.rawMessage = message;
|
|
|
|
}
|
|
|
|
|
|
|
|
toString(): string { return this.message; }
|
|
|
|
}
|
|
|
|
|
|
|
|
function _trackWhitespace(mode: CssLexerMode) {
|
|
|
|
switch (mode) {
|
|
|
|
case CssLexerMode.SELECTOR:
|
|
|
|
case CssLexerMode.ALL_TRACK_WS:
|
|
|
|
case CssLexerMode.STYLE_VALUE:
|
|
|
|
return true;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class CssScanner {
|
|
|
|
peek: number;
|
|
|
|
peekPeek: number;
|
|
|
|
length: number = 0;
|
|
|
|
index: number = -1;
|
|
|
|
column: number = -1;
|
|
|
|
line: number = 0;
|
|
|
|
|
2016-03-28 17:25:22 -04:00
|
|
|
/** @internal */
|
2016-02-02 03:37:08 -05:00
|
|
|
_currentMode: CssLexerMode = CssLexerMode.BLOCK;
|
2016-03-28 17:25:22 -04:00
|
|
|
/** @internal */
|
2016-02-02 03:37:08 -05:00
|
|
|
_currentError: CssScannerError = null;
|
|
|
|
|
|
|
|
constructor(public input: string, private _trackComments: boolean = false) {
|
|
|
|
this.length = this.input.length;
|
|
|
|
this.peekPeek = this.peekAt(0);
|
|
|
|
this.advance();
|
|
|
|
}
|
|
|
|
|
|
|
|
getMode(): CssLexerMode { return this._currentMode; }
|
|
|
|
|
|
|
|
setMode(mode: CssLexerMode) {
|
|
|
|
if (this._currentMode != mode) {
|
|
|
|
if (_trackWhitespace(this._currentMode)) {
|
|
|
|
this.consumeWhitespace();
|
|
|
|
}
|
|
|
|
this._currentMode = mode;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
advance(): void {
|
|
|
|
if (isNewline(this.peek)) {
|
|
|
|
this.column = 0;
|
|
|
|
this.line++;
|
|
|
|
} else {
|
|
|
|
this.column++;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.index++;
|
|
|
|
this.peek = this.peekPeek;
|
|
|
|
this.peekPeek = this.peekAt(this.index + 1);
|
|
|
|
}
|
|
|
|
|
2016-03-28 17:25:22 -04:00
|
|
|
peekAt(index: number): number {
|
2016-02-02 03:37:08 -05:00
|
|
|
return index >= this.length ? $EOF : StringWrapper.charCodeAt(this.input, index);
|
|
|
|
}
|
|
|
|
|
|
|
|
consumeEmptyStatements(): void {
|
|
|
|
this.consumeWhitespace();
|
|
|
|
while (this.peek == $SEMICOLON) {
|
|
|
|
this.advance();
|
|
|
|
this.consumeWhitespace();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
consumeWhitespace(): void {
|
|
|
|
while (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) {
|
|
|
|
this.error('Unterminated comment');
|
|
|
|
}
|
|
|
|
this.advance();
|
|
|
|
}
|
|
|
|
this.advance(); // *
|
|
|
|
this.advance(); // /
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
consume(type: CssTokenType, value: string = null): LexedCssResult {
|
|
|
|
var mode = this._currentMode;
|
|
|
|
this.setMode(CssLexerMode.ALL);
|
|
|
|
|
|
|
|
var previousIndex = this.index;
|
|
|
|
var previousLine = this.line;
|
|
|
|
var previousColumn = this.column;
|
|
|
|
|
|
|
|
var output = this.scan();
|
|
|
|
|
|
|
|
// just incase the inner scan method returned an error
|
|
|
|
if (isPresent(output.error)) {
|
|
|
|
this.setMode(mode);
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
|
|
|
var next = output.token;
|
|
|
|
if (!isPresent(next)) {
|
2016-04-12 12:40:37 -04:00
|
|
|
next = new CssToken(0, 0, 0, CssTokenType.EOF, "end of file");
|
2016-02-02 03:37:08 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
var isMatchingType;
|
|
|
|
if (type == CssTokenType.IdentifierOrNumber) {
|
|
|
|
// TODO (matsko): implement array traversal for lookup here
|
|
|
|
isMatchingType = next.type == CssTokenType.Number || next.type == CssTokenType.Identifier;
|
|
|
|
} else {
|
|
|
|
isMatchingType = next.type == type;
|
|
|
|
}
|
|
|
|
|
|
|
|
// before throwing the error we need to bring back the former
|
|
|
|
// mode so that the parser can recover...
|
|
|
|
this.setMode(mode);
|
|
|
|
|
|
|
|
var error = null;
|
|
|
|
if (!isMatchingType || (isPresent(value) && value != next.strValue)) {
|
2016-04-12 12:40:37 -04:00
|
|
|
var errorMessage = resolveEnumToken(CssTokenType, next.type) + " does not match expected " +
|
|
|
|
resolveEnumToken(CssTokenType, type) + " value";
|
2016-02-02 03:37:08 -05:00
|
|
|
|
|
|
|
if (isPresent(value)) {
|
|
|
|
errorMessage += ' ("' + next.strValue + '" should match "' + value + '")';
|
|
|
|
}
|
|
|
|
|
|
|
|
error = new CssScannerError(
|
2016-04-12 12:40:37 -04:00
|
|
|
next, generateErrorMessage(this.input, errorMessage, next.strValue, previousIndex,
|
|
|
|
previousLine, previousColumn));
|
2016-02-02 03:37:08 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return new LexedCssResult(error, next);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
scan(): LexedCssResult {
|
|
|
|
var trackWS = _trackWhitespace(this._currentMode);
|
|
|
|
if (this.index == 0 && !trackWS) { // first scan
|
|
|
|
this.consumeWhitespace();
|
|
|
|
}
|
|
|
|
|
|
|
|
var token = this._scan();
|
|
|
|
if (token == null) return null;
|
|
|
|
|
|
|
|
var error = this._currentError;
|
|
|
|
this._currentError = null;
|
|
|
|
|
|
|
|
if (!trackWS) {
|
|
|
|
this.consumeWhitespace();
|
|
|
|
}
|
|
|
|
return new LexedCssResult(error, token);
|
|
|
|
}
|
|
|
|
|
2016-03-28 17:25:22 -04:00
|
|
|
/** @internal */
|
2016-02-02 03:37:08 -05:00
|
|
|
_scan(): CssToken {
|
|
|
|
var peek = this.peek;
|
|
|
|
var peekPeek = this.peekPeek;
|
|
|
|
if (peek == $EOF) return null;
|
|
|
|
|
|
|
|
if (isCommentStart(peek, peekPeek)) {
|
|
|
|
// even if comments are not tracked we still lex the
|
|
|
|
// comment so we can move the pointer forward
|
|
|
|
var commentToken = this.scanComment();
|
|
|
|
if (this._trackComments) {
|
|
|
|
return commentToken;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_trackWhitespace(this._currentMode) && (isWhitespace(peek) || isNewline(peek))) {
|
|
|
|
return this.scanWhitespace();
|
|
|
|
}
|
|
|
|
|
|
|
|
peek = this.peek;
|
|
|
|
peekPeek = this.peekPeek;
|
|
|
|
if (peek == $EOF) return null;
|
|
|
|
|
|
|
|
if (isStringStart(peek, peekPeek)) {
|
|
|
|
return this.scanString();
|
|
|
|
}
|
|
|
|
|
|
|
|
// something like url(cool)
|
|
|
|
if (this._currentMode == CssLexerMode.STYLE_VALUE_FUNCTION) {
|
|
|
|
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)) {
|
|
|
|
return this.scanNumber();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (peek == $AT) {
|
|
|
|
return this.scanAtExpression();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isIdentifierStart(peek, peekPeek)) {
|
|
|
|
return this.scanIdentifier();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isValidCssCharacter(peek, this._currentMode)) {
|
|
|
|
return this.scanCharacter();
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.error(`Unexpected character [${StringWrapper.fromCharCode(peek)}]`);
|
|
|
|
}
|
|
|
|
|
2016-03-28 17:25:22 -04:00
|
|
|
scanComment(): CssToken {
|
2016-04-12 12:40:37 -04:00
|
|
|
if (this.assertCondition(isCommentStart(this.peek, this.peekPeek),
|
|
|
|
"Expected comment start value")) {
|
2016-02-02 03:37:08 -05:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
var start = this.index;
|
|
|
|
var startingColumn = this.column;
|
|
|
|
var startingLine = this.line;
|
|
|
|
|
|
|
|
this.advance(); // /
|
|
|
|
this.advance(); // *
|
|
|
|
|
|
|
|
while (!isCommentEnd(this.peek, this.peekPeek)) {
|
|
|
|
if (this.peek == $EOF) {
|
|
|
|
this.error('Unterminated comment');
|
|
|
|
}
|
|
|
|
this.advance();
|
|
|
|
}
|
|
|
|
|
|
|
|
this.advance(); // *
|
|
|
|
this.advance(); // /
|
|
|
|
|
|
|
|
var str = this.input.substring(start, this.index);
|
|
|
|
return new CssToken(start, startingColumn, startingLine, CssTokenType.Comment, str);
|
|
|
|
}
|
|
|
|
|
2016-03-28 17:25:22 -04:00
|
|
|
scanWhitespace(): CssToken {
|
2016-02-02 03:37:08 -05:00
|
|
|
var start = this.index;
|
|
|
|
var startingColumn = this.column;
|
|
|
|
var startingLine = this.line;
|
|
|
|
while (isWhitespace(this.peek) && this.peek != $EOF) {
|
|
|
|
this.advance();
|
|
|
|
}
|
|
|
|
var str = this.input.substring(start, this.index);
|
|
|
|
return new CssToken(start, startingColumn, startingLine, CssTokenType.Whitespace, str);
|
|
|
|
}
|
|
|
|
|
2016-03-28 17:25:22 -04:00
|
|
|
scanString(): CssToken {
|
2016-04-12 12:40:37 -04:00
|
|
|
if (this.assertCondition(isStringStart(this.peek, this.peekPeek),
|
|
|
|
"Unexpected non-string starting value")) {
|
2016-02-02 03:37:08 -05:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
var target = this.peek;
|
|
|
|
var start = this.index;
|
|
|
|
var startingColumn = this.column;
|
|
|
|
var startingLine = this.line;
|
|
|
|
var previous = target;
|
|
|
|
this.advance();
|
|
|
|
|
|
|
|
while (!isCharMatch(target, previous, this.peek)) {
|
|
|
|
if (this.peek == $EOF || isNewline(this.peek)) {
|
|
|
|
this.error('Unterminated quote');
|
|
|
|
}
|
|
|
|
previous = this.peek;
|
|
|
|
this.advance();
|
|
|
|
}
|
|
|
|
|
2016-04-12 12:40:37 -04:00
|
|
|
if (this.assertCondition(this.peek == target, "Unterminated quote")) {
|
2016-02-02 03:37:08 -05:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
this.advance();
|
|
|
|
|
|
|
|
var str = this.input.substring(start, this.index);
|
|
|
|
return new CssToken(start, startingColumn, startingLine, CssTokenType.String, str);
|
|
|
|
}
|
|
|
|
|
2016-03-28 17:25:22 -04:00
|
|
|
scanNumber(): CssToken {
|
2016-02-02 03:37:08 -05:00
|
|
|
var start = this.index;
|
|
|
|
var startingColumn = this.column;
|
|
|
|
if (this.peek == $PLUS || this.peek == $MINUS) {
|
|
|
|
this.advance();
|
|
|
|
}
|
|
|
|
var periodUsed = false;
|
|
|
|
while (isDigit(this.peek) || this.peek == $PERIOD) {
|
|
|
|
if (this.peek == $PERIOD) {
|
|
|
|
if (periodUsed) {
|
|
|
|
this.error('Unexpected use of a second period value');
|
|
|
|
}
|
|
|
|
periodUsed = true;
|
|
|
|
}
|
|
|
|
this.advance();
|
|
|
|
}
|
|
|
|
var strValue = this.input.substring(start, this.index);
|
|
|
|
return new CssToken(start, startingColumn, this.line, CssTokenType.Number, strValue);
|
|
|
|
}
|
|
|
|
|
2016-03-28 17:25:22 -04:00
|
|
|
scanIdentifier(): CssToken {
|
2016-04-12 12:40:37 -04:00
|
|
|
if (this.assertCondition(isIdentifierStart(this.peek, this.peekPeek),
|
|
|
|
'Expected identifier starting value')) {
|
2016-02-02 03:37:08 -05:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
var start = this.index;
|
|
|
|
var startingColumn = this.column;
|
|
|
|
while (isIdentifierPart(this.peek)) {
|
|
|
|
this.advance();
|
|
|
|
}
|
|
|
|
var strValue = this.input.substring(start, this.index);
|
|
|
|
return new CssToken(start, startingColumn, this.line, CssTokenType.Identifier, strValue);
|
|
|
|
}
|
|
|
|
|
2016-03-28 17:25:22 -04:00
|
|
|
scanCssValueFunction(): CssToken {
|
2016-02-02 03:37:08 -05:00
|
|
|
var start = this.index;
|
|
|
|
var startingColumn = this.column;
|
2016-03-13 04:56:20 -04:00
|
|
|
var parenBalance = 1;
|
|
|
|
while (this.peek != $EOF && parenBalance > 0) {
|
2016-02-02 03:37:08 -05:00
|
|
|
this.advance();
|
2016-03-13 04:56:20 -04:00
|
|
|
if (this.peek == $LPAREN) {
|
|
|
|
parenBalance++;
|
|
|
|
} else if (this.peek == $RPAREN) {
|
|
|
|
parenBalance--;
|
|
|
|
}
|
2016-02-02 03:37:08 -05:00
|
|
|
}
|
|
|
|
var strValue = this.input.substring(start, this.index);
|
|
|
|
return new CssToken(start, startingColumn, this.line, CssTokenType.Identifier, strValue);
|
|
|
|
}
|
|
|
|
|
2016-03-28 17:25:22 -04:00
|
|
|
scanCharacter(): CssToken {
|
2016-02-02 03:37:08 -05:00
|
|
|
var start = this.index;
|
|
|
|
var startingColumn = this.column;
|
2016-04-12 12:40:37 -04:00
|
|
|
if (this.assertCondition(isValidCssCharacter(this.peek, this._currentMode),
|
|
|
|
charStr(this.peek) + ' is not a valid CSS character')) {
|
2016-02-02 03:37:08 -05:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
var c = this.input.substring(start, start + 1);
|
|
|
|
this.advance();
|
|
|
|
|
|
|
|
return new CssToken(start, startingColumn, this.line, CssTokenType.Character, c);
|
|
|
|
}
|
|
|
|
|
2016-03-28 17:25:22 -04:00
|
|
|
scanAtExpression(): CssToken {
|
2016-02-02 03:37:08 -05:00
|
|
|
if (this.assertCondition(this.peek == $AT, 'Expected @ value')) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
var start = this.index;
|
|
|
|
var startingColumn = this.column;
|
|
|
|
this.advance();
|
|
|
|
if (isIdentifierStart(this.peek, this.peekPeek)) {
|
|
|
|
var ident = this.scanIdentifier();
|
|
|
|
var strValue = '@' + ident.strValue;
|
|
|
|
return new CssToken(start, startingColumn, this.line, CssTokenType.AtKeyword, strValue);
|
|
|
|
} else {
|
|
|
|
return this.scanCharacter();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
assertCondition(status: boolean, errorMessage: string): boolean {
|
|
|
|
if (!status) {
|
|
|
|
this.error(errorMessage);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
error(message: string, errorTokenValue: string = null, doNotAdvance: boolean = false): CssToken {
|
|
|
|
var index: number = this.index;
|
|
|
|
var column: number = this.column;
|
|
|
|
var line: number = this.line;
|
|
|
|
errorTokenValue =
|
|
|
|
isPresent(errorTokenValue) ? errorTokenValue : StringWrapper.fromCharCode(this.peek);
|
|
|
|
var invalidToken = new CssToken(index, column, line, CssTokenType.Invalid, errorTokenValue);
|
|
|
|
var errorMessage =
|
|
|
|
generateErrorMessage(this.input, message, errorTokenValue, index, line, column);
|
|
|
|
if (!doNotAdvance) {
|
|
|
|
this.advance();
|
|
|
|
}
|
|
|
|
this._currentError = new CssScannerError(invalidToken, errorMessage);
|
|
|
|
return invalidToken;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function isAtKeyword(current: CssToken, next: CssToken): boolean {
|
|
|
|
return current.numValue == $AT && next.type == CssTokenType.Identifier;
|
|
|
|
}
|
|
|
|
|
2016-03-28 17:25:22 -04:00
|
|
|
function isCharMatch(target: number, previous: number, code: number): boolean {
|
2016-02-02 03:37:08 -05:00
|
|
|
return code == target && previous != $BACKSLASH;
|
|
|
|
}
|
|
|
|
|
|
|
|
function isDigit(code: number): boolean {
|
|
|
|
return $0 <= code && code <= $9;
|
|
|
|
}
|
|
|
|
|
2016-03-28 17:25:22 -04:00
|
|
|
function isCommentStart(code: number, next: number): boolean {
|
2016-02-02 03:37:08 -05:00
|
|
|
return code == $SLASH && next == $STAR;
|
|
|
|
}
|
|
|
|
|
2016-03-28 17:25:22 -04:00
|
|
|
function isCommentEnd(code: number, next: number): boolean {
|
2016-02-02 03:37:08 -05:00
|
|
|
return code == $STAR && next == $SLASH;
|
|
|
|
}
|
|
|
|
|
|
|
|
function isStringStart(code: number, next: number): boolean {
|
|
|
|
var target = code;
|
|
|
|
if (target == $BACKSLASH) {
|
|
|
|
target = next;
|
|
|
|
}
|
|
|
|
return target == $DQ || target == $SQ;
|
|
|
|
}
|
|
|
|
|
|
|
|
function isIdentifierStart(code: number, next: number): boolean {
|
|
|
|
var target = code;
|
|
|
|
if (target == $MINUS) {
|
|
|
|
target = next;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ($a <= target && target <= $z) || ($A <= target && target <= $Z) || target == $BACKSLASH ||
|
2016-04-12 12:40:37 -04:00
|
|
|
target == $MINUS || target == $_;
|
2016-02-02 03:37:08 -05:00
|
|
|
}
|
|
|
|
|
2016-03-28 17:25:22 -04:00
|
|
|
function isIdentifierPart(target: number): boolean {
|
2016-02-02 03:37:08 -05:00
|
|
|
return ($a <= target && target <= $z) || ($A <= target && target <= $Z) || target == $BACKSLASH ||
|
2016-04-12 12:40:37 -04:00
|
|
|
target == $MINUS || target == $_ || isDigit(target);
|
2016-02-02 03:37:08 -05:00
|
|
|
}
|
|
|
|
|
2016-03-28 17:25:22 -04:00
|
|
|
function isValidPseudoSelectorCharacter(code: number): boolean {
|
2016-02-02 03:37:08 -05:00
|
|
|
switch (code) {
|
|
|
|
case $LPAREN:
|
|
|
|
case $RPAREN:
|
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-28 17:25:22 -04:00
|
|
|
function isValidKeyframeBlockCharacter(code: number): boolean {
|
2016-02-02 03:37:08 -05:00
|
|
|
return code == $PERCENT;
|
|
|
|
}
|
|
|
|
|
2016-03-28 17:25:22 -04:00
|
|
|
function isValidAttributeSelectorCharacter(code: number): boolean {
|
2016-02-02 03:37:08 -05:00
|
|
|
// value^*|$~=something
|
|
|
|
switch (code) {
|
|
|
|
case $$:
|
|
|
|
case $PIPE:
|
|
|
|
case $CARET:
|
|
|
|
case $TILDA:
|
|
|
|
case $STAR:
|
|
|
|
case $EQ:
|
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-28 17:25:22 -04:00
|
|
|
function isValidSelectorCharacter(code: number): boolean {
|
2016-02-02 03:37:08 -05:00
|
|
|
// selector [ key = value ]
|
|
|
|
// IDENT C IDENT C IDENT C
|
|
|
|
// #id, .class, *+~>
|
|
|
|
// tag:PSEUDO
|
|
|
|
switch (code) {
|
|
|
|
case $HASH:
|
|
|
|
case $PERIOD:
|
|
|
|
case $TILDA:
|
|
|
|
case $STAR:
|
|
|
|
case $PLUS:
|
|
|
|
case $GT:
|
|
|
|
case $COLON:
|
|
|
|
case $PIPE:
|
|
|
|
case $COMMA:
|
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-28 17:25:22 -04:00
|
|
|
function isValidStyleBlockCharacter(code: number): boolean {
|
2016-02-02 03:37:08 -05:00
|
|
|
// 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:
|
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-28 17:25:22 -04:00
|
|
|
function isValidMediaQueryRuleCharacter(code: number): boolean {
|
2016-02-02 03:37:08 -05:00
|
|
|
// (min-width: 7.5em) and (orientation: landscape)
|
|
|
|
switch (code) {
|
|
|
|
case $LPAREN:
|
|
|
|
case $RPAREN:
|
|
|
|
case $COLON:
|
|
|
|
case $PERCENT:
|
|
|
|
case $PERIOD:
|
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-28 17:25:22 -04:00
|
|
|
function isValidAtRuleCharacter(code: number): boolean {
|
2016-02-02 03:37:08 -05:00
|
|
|
// @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:
|
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-28 17:25:22 -04:00
|
|
|
function isValidStyleFunctionCharacter(code: number): boolean {
|
2016-02-02 03:37:08 -05:00
|
|
|
switch (code) {
|
|
|
|
case $PERIOD:
|
|
|
|
case $MINUS:
|
|
|
|
case $PLUS:
|
|
|
|
case $STAR:
|
|
|
|
case $SLASH:
|
|
|
|
case $LPAREN:
|
|
|
|
case $RPAREN:
|
|
|
|
case $COMMA:
|
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-28 17:25:22 -04:00
|
|
|
function isValidBlockCharacter(code: number): boolean {
|
2016-02-02 03:37:08 -05:00
|
|
|
// @something { }
|
|
|
|
// IDENT
|
|
|
|
return code == $AT;
|
|
|
|
}
|
|
|
|
|
|
|
|
function isValidCssCharacter(code: number, mode: CssLexerMode): boolean {
|
|
|
|
switch (mode) {
|
|
|
|
case CssLexerMode.ALL:
|
|
|
|
case CssLexerMode.ALL_TRACK_WS:
|
|
|
|
return true;
|
|
|
|
|
|
|
|
case CssLexerMode.SELECTOR:
|
|
|
|
return isValidSelectorCharacter(code);
|
|
|
|
|
|
|
|
case CssLexerMode.PSEUDO_SELECTOR:
|
|
|
|
return isValidPseudoSelectorCharacter(code);
|
|
|
|
|
|
|
|
case CssLexerMode.ATTRIBUTE_SELECTOR:
|
|
|
|
return isValidAttributeSelectorCharacter(code);
|
|
|
|
|
|
|
|
case CssLexerMode.MEDIA_QUERY:
|
|
|
|
return isValidMediaQueryRuleCharacter(code);
|
|
|
|
|
|
|
|
case CssLexerMode.AT_RULE_QUERY:
|
|
|
|
return isValidAtRuleCharacter(code);
|
|
|
|
|
|
|
|
case CssLexerMode.KEYFRAME_BLOCK:
|
|
|
|
return isValidKeyframeBlockCharacter(code);
|
|
|
|
|
|
|
|
case CssLexerMode.STYLE_BLOCK:
|
|
|
|
case CssLexerMode.STYLE_VALUE:
|
|
|
|
return isValidStyleBlockCharacter(code);
|
|
|
|
|
|
|
|
case CssLexerMode.STYLE_CALC_FUNCTION:
|
|
|
|
return isValidStyleFunctionCharacter(code);
|
|
|
|
|
|
|
|
case CssLexerMode.BLOCK:
|
|
|
|
return isValidBlockCharacter(code);
|
|
|
|
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function charCode(input, index): number {
|
|
|
|
return index >= input.length ? $EOF : StringWrapper.charCodeAt(input, index);
|
|
|
|
}
|
|
|
|
|
|
|
|
function charStr(code: number): string {
|
|
|
|
return StringWrapper.fromCharCode(code);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function isNewline(code): boolean {
|
|
|
|
switch (code) {
|
|
|
|
case $FF:
|
|
|
|
case $CR:
|
|
|
|
case $LF:
|
|
|
|
case $VTAB:
|
|
|
|
return true;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|