|
|
@@ -91,6 +91,408 @@ this.parseCss = this.parseCss || (() => {
|
|
|
return String.fromCharCode(lead) + String.fromCharCode(trail);
|
|
|
}
|
|
|
|
|
|
+ function consumeAToken(consume, next, eof, reconsume, parseerror, donothing) {
|
|
|
+ consumeComments(consume, next, eof, parseerror);
|
|
|
+ let code = consume();
|
|
|
+ if (whitespace(code)) {
|
|
|
+ while (whitespace(next())) code = consume();
|
|
|
+ return new Token(WHITESPACE_TOKEN_TYPE);
|
|
|
+ }
|
|
|
+ else if (code == 0x22) return consumeAStringToken(consume, next, eof, reconsume, parseerror, donothing, code);
|
|
|
+ else if (code == 0x23) {
|
|
|
+ if (namechar(next()) || areAValidEscape(next(1), next(2))) {
|
|
|
+ const token = new Token(HASH_TOKEN_TYPE);
|
|
|
+ if (wouldStartAnIdentifier(next(1), next(2), next(3))) token.type = "id";
|
|
|
+ token.value = consumeAName(consume, next, eof, reconsume);
|
|
|
+ return token;
|
|
|
+ } else {
|
|
|
+ return new Token(DELIM_TOKEN_TYPE, stringFromCode(code));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (code == 0x24) {
|
|
|
+ if (next() == 0x3d) {
|
|
|
+ code = consume();
|
|
|
+ return new Token(SUFFIX_MATCH_TOKEN_TYPE);
|
|
|
+ } else {
|
|
|
+ return new Token(DELIM_TOKEN_TYPE, stringFromCode(code));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (code == 0x27) return consumeAStringToken(consume, next, eof, reconsume, parseerror, donothing, code);
|
|
|
+ else if (code == 0x28) return new Token(OPEN_PAREN_TOKEN_TYPE);
|
|
|
+ else if (code == 0x29) return new Token(CLOSE_PAREN_TOKEN_TYPE);
|
|
|
+ else if (code == 0x2a) {
|
|
|
+ if (next() == 0x3d) {
|
|
|
+ code = consume();
|
|
|
+ return new Token(SUBSTRING_MATCH_TOKEN_TYPE);
|
|
|
+ } else {
|
|
|
+ return new Token(DELIM_TOKEN_TYPE, stringFromCode(code));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (code == 0x2b) {
|
|
|
+ if (startsWithANumber(next, code)) {
|
|
|
+ reconsume();
|
|
|
+ return consumeANumericToken(consume, next, eof, reconsume);
|
|
|
+ } else {
|
|
|
+ return new Token(DELIM_TOKEN_TYPE, stringFromCode(code));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (code == 0x2c) return new Token(COMMA_TOKEN_TYPE);
|
|
|
+ else if (code == 0x2d) {
|
|
|
+ if (startsWithANumber(next, code)) {
|
|
|
+ reconsume();
|
|
|
+ return consumeANumericToken(consume, next, eof, reconsume);
|
|
|
+ } else if (next(1) == 0x2d && next(2) == 0x3e) {
|
|
|
+ consume(2);
|
|
|
+ return new Token(CDC_TOKEN_TYPE);
|
|
|
+ } else if (wouldStartAnIdentifier(code, next(1), next(2))) {
|
|
|
+ reconsume();
|
|
|
+ return consumeAnIdentlikeToken(consume, next, eof, reconsume, parseerror, donothing);
|
|
|
+ } else {
|
|
|
+ return new Token(DELIM_TOKEN_TYPE, stringFromCode(code));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (code == 0x2e) {
|
|
|
+ if (startsWithANumber(next, code)) {
|
|
|
+ reconsume();
|
|
|
+ return consumeANumericToken(consume, next, eof, reconsume);
|
|
|
+ } else {
|
|
|
+ return new Token(DELIM_TOKEN_TYPE, stringFromCode(code));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (code == 0x3a) return new Token(COLON_TOKEN_TYPE);
|
|
|
+ else if (code == 0x3b) return new Token(SEMICOLON_TOKEN_TYPE);
|
|
|
+ else if (code == 0x3c) {
|
|
|
+ if (next(1) == 0x21 && next(2) == 0x2d && next(3) == 0x2d) {
|
|
|
+ consume(3);
|
|
|
+ return new Token(CDO_TOKEN_TYPE);
|
|
|
+ } else {
|
|
|
+ return new Token(DELIM_TOKEN_TYPE, stringFromCode(code));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (code == 0x40) {
|
|
|
+ return new Token(DELIM_TOKEN_TYPE, stringFromCode(code));
|
|
|
+ }
|
|
|
+ else if (code == 0x5b) new Token(OPEN_SQUARE_TOKEN_TYPE);
|
|
|
+ else if (code == 0x5c) {
|
|
|
+ if (startsWithAValidEscape(next, code)) {
|
|
|
+ reconsume();
|
|
|
+ return consumeAnIdentlikeToken(consume, next, eof, reconsume, parseerror, donothing);
|
|
|
+ } else {
|
|
|
+ parseerror();
|
|
|
+ return new Token(DELIM_TOKEN_TYPE, stringFromCode(code));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (code == 0x5d) new Token(CLOSE_SQUARE_TOKEN_TYPE);
|
|
|
+ else if (code == 0x5e) {
|
|
|
+ if (next() == 0x3d) {
|
|
|
+ code = consume();
|
|
|
+ return new Token(PREFIX_MATCH_TOKEN_TYPE);
|
|
|
+ } else {
|
|
|
+ return new Token(DELIM_TOKEN_TYPE, stringFromCode(code));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (code == 0x7b) return new Token(OPEN_CURLY_TOKEN_TYPE);
|
|
|
+ else if (code == 0x7c) {
|
|
|
+ if (next() == 0x3d) {
|
|
|
+ code = consume();
|
|
|
+ return new Token(DASH_MATCH_TOKEN_TYPE);
|
|
|
+ } else if (next() == 0x7c) {
|
|
|
+ code = consume();
|
|
|
+ return new Token(COLUMN_TOKEN_TYPE);
|
|
|
+ } else {
|
|
|
+ return new Token(DELIM_TOKEN_TYPE, stringFromCode(code));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (code == 0x7d) return new Token(CLOSE_CURLY_TOKEN_TYPE);
|
|
|
+ else if (code == 0x7e) {
|
|
|
+ if (next() == 0x3d) {
|
|
|
+ code = consume();
|
|
|
+ return new Token(INCLUDE_MATCH_TOKEN_TYPE);
|
|
|
+ } else {
|
|
|
+ return new Token(DELIM_TOKEN_TYPE, stringFromCode(code));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (digit(code)) {
|
|
|
+ reconsume();
|
|
|
+ return consumeANumericToken(consume, next, eof, reconsume);
|
|
|
+ }
|
|
|
+ else if (namestartchar(code)) {
|
|
|
+ reconsume();
|
|
|
+ return consumeAnIdentlikeToken(consume, next, eof, reconsume, parseerror, donothing);
|
|
|
+ }
|
|
|
+ else if (eof()) return new Token(EOF_TOKEN_TYPE);
|
|
|
+ else return new Token(DELIM_TOKEN_TYPE, stringFromCode(code));
|
|
|
+ }
|
|
|
+
|
|
|
+ function consumeComments(consume, next, eof, parseerror) {
|
|
|
+ while (next(1) == 0x2f && next(2) == 0x2a) {
|
|
|
+ consume(2);
|
|
|
+ while (true) { // eslint-disable-line no-constant-condition
|
|
|
+ let code = consume();
|
|
|
+ if (code == 0x2a && next() == 0x2f) {
|
|
|
+ code = consume();
|
|
|
+ break;
|
|
|
+ } else if (eof()) {
|
|
|
+ parseerror();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function consumeANumericToken(consume, next, eof, reconsume) {
|
|
|
+ const num = consumeANumber(consume, next);
|
|
|
+ if (wouldStartAnIdentifier(next(1), next(2), next(3))) {
|
|
|
+ const token = new Token(DIMENSION_TOKEN_TYPE, num.value);
|
|
|
+ token.repr = num.repr;
|
|
|
+ token.type = num.type;
|
|
|
+ token.unit = consumeAName(consume, next, eof, reconsume);
|
|
|
+ return token;
|
|
|
+ } else if (next() == 0x25) {
|
|
|
+ consume();
|
|
|
+ const token = new Token(PERCENTAGE_TOKEN_TYPE, num.value);
|
|
|
+ token.repr = num.repr;
|
|
|
+ return token;
|
|
|
+ } else {
|
|
|
+ const token = new Token(NUMBER_TOKEN_TYPE, num.value);
|
|
|
+ token.type = "integer";
|
|
|
+ token.repr = num.repr;
|
|
|
+ token.type = num.type;
|
|
|
+ return token;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function consumeAnIdentlikeToken(consume, next, eof, reconsume, parseerror, donothing) {
|
|
|
+ const str = consumeAName(consume, next, eof, reconsume);
|
|
|
+ if (str.toLowerCase() == "url" && next() == 0x28) {
|
|
|
+ consume();
|
|
|
+ while (whitespace(next(1)) && whitespace(next(2))) consume();
|
|
|
+ if (next() == 0x22 || next() == 0x27) {
|
|
|
+ return new Token(FUNCTION_TOKEN_TYPE, str);
|
|
|
+ } else if (whitespace(next()) && (next(2) == 0x22 || next(2) == 0x27)) {
|
|
|
+ return new Token(FUNCTION_TOKEN_TYPE, str);
|
|
|
+ } else {
|
|
|
+ return consumeAURLToken(consume, next, eof, parseerror, donothing);
|
|
|
+ }
|
|
|
+ } else if (next() == 0x28) {
|
|
|
+ consume();
|
|
|
+ return new Token(FUNCTION_TOKEN_TYPE, str);
|
|
|
+ } else {
|
|
|
+ return new Token(IDENT_TOKEN_TYPE, str);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function consumeAStringToken(consume, next, eof, reconsume, parseerror, donothing, code) {
|
|
|
+ const endingCodePoint = code;
|
|
|
+ let string = "";
|
|
|
+ while (code = consume()) { // eslint-disable-line no-cond-assign
|
|
|
+ if (code == endingCodePoint || eof()) {
|
|
|
+ return new Token(STRING_TOKEN_TYPE, string);
|
|
|
+ } else if (newline(code)) {
|
|
|
+ parseerror();
|
|
|
+ reconsume();
|
|
|
+ return new Token(BAD_STRING_TOKEN_TYPE);
|
|
|
+ } else if (code == 0x5c) {
|
|
|
+ if (eof(next())) {
|
|
|
+ donothing();
|
|
|
+ } else if (newline(next())) {
|
|
|
+ code = consume();
|
|
|
+ } else {
|
|
|
+ string += stringFromCode(consumeEscape(consume, next, eof));
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ string += stringFromCode(code);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function consumeAURLToken(consume, next, eof, parseerror, donothing) {
|
|
|
+ const token = new Token(URL_TOKEN_TYPE, "");
|
|
|
+ while (whitespace(next())) consume();
|
|
|
+ if (eof(next())) return token;
|
|
|
+ let code;
|
|
|
+ while (code = consume()) { // eslint-disable-line no-cond-assign
|
|
|
+ if (code == 0x29 || eof()) {
|
|
|
+ return token;
|
|
|
+ } else if (whitespace(code)) {
|
|
|
+ while (whitespace(next())) code = consume();
|
|
|
+ if (next() == 0x29 || eof(next())) {
|
|
|
+ code = consume();
|
|
|
+ return token;
|
|
|
+ } else {
|
|
|
+ consumeTheRemnantsOfABadURL(consume, next, eof, donothing);
|
|
|
+ return new Token(BAD_URL_TOKEN_TYPE);
|
|
|
+ }
|
|
|
+ } else if (code == 0x22 || code == 0x27 || code == 0x28 || nonprintable(code)) {
|
|
|
+ parseerror();
|
|
|
+ consumeTheRemnantsOfABadURL(consume, next, eof, donothing);
|
|
|
+ return new Token(BAD_URL_TOKEN_TYPE);
|
|
|
+ } else if (code == 0x5c) {
|
|
|
+ if (startsWithAValidEscape(next, code)) {
|
|
|
+ token.value += stringFromCode(consumeEscape(consume, next, eof));
|
|
|
+ } else {
|
|
|
+ parseerror();
|
|
|
+ consumeTheRemnantsOfABadURL(consume, next, eof, donothing);
|
|
|
+ return new Token(BAD_URL_TOKEN_TYPE);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ token.value += stringFromCode(code);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function consumeEscape(consume, next, eof) {
|
|
|
+ // Assume the the current character is the \
|
|
|
+ // and the next code point is not a newline.
|
|
|
+ let code = consume();
|
|
|
+ if (hexdigit(code)) {
|
|
|
+ // Consume 1-6 hex digits
|
|
|
+ const digits = [code];
|
|
|
+ for (let total = 0; total < 5; total++) {
|
|
|
+ if (hexdigit(next())) {
|
|
|
+ code = consume();
|
|
|
+ digits.push(code);
|
|
|
+ } else {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (whitespace(next())) code = consume();
|
|
|
+ let value = parseInt(digits.map(function (x) { return String.fromCharCode(x); }).join(""), 16);
|
|
|
+ if (value > maximumallowedcodepoint) value = 0xfffd;
|
|
|
+ return value;
|
|
|
+ } else if (eof()) {
|
|
|
+ return 0xfffd;
|
|
|
+ } else {
|
|
|
+ return code;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function areAValidEscape(c1, c2) {
|
|
|
+ if (c1 != 0x5c) return false;
|
|
|
+ if (newline(c2)) return false;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ function startsWithAValidEscape(next, code) {
|
|
|
+ return areAValidEscape(code, next());
|
|
|
+ }
|
|
|
+
|
|
|
+ function wouldStartAnIdentifier(c1, c2, c3) {
|
|
|
+ if (c1 == 0x2d) {
|
|
|
+ return namestartchar(c2) || c2 == 0x2d || areAValidEscape(c2, c3);
|
|
|
+ } else if (namestartchar(c1)) {
|
|
|
+ return true;
|
|
|
+ } else if (c1 == 0x5c) {
|
|
|
+ return areAValidEscape(c1, c2);
|
|
|
+ } else {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function wouldStartANumber(c1, c2, c3) {
|
|
|
+ if (c1 == 0x2b || c1 == 0x2d) {
|
|
|
+ if (digit(c2)) return true;
|
|
|
+ if (c2 == 0x2e && digit(c3)) return true;
|
|
|
+ return false;
|
|
|
+ } else if (c1 == 0x2e) {
|
|
|
+ if (digit(c2)) return true;
|
|
|
+ return false;
|
|
|
+ } else if (digit(c1)) {
|
|
|
+ return true;
|
|
|
+ } else {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function startsWithANumber(next, code) {
|
|
|
+ return wouldStartANumber(code, next(1), next(2));
|
|
|
+ }
|
|
|
+
|
|
|
+ function consumeAName(consume, next, eof, reconsume) {
|
|
|
+ let result = "";
|
|
|
+ let code;
|
|
|
+ while (code = consume()) { // eslint-disable-line no-cond-assign
|
|
|
+ if (namechar(code)) {
|
|
|
+ result += stringFromCode(code);
|
|
|
+ } else if (startsWithAValidEscape(next, code)) {
|
|
|
+ result += stringFromCode(consumeEscape(consume, next, eof));
|
|
|
+ } else {
|
|
|
+ reconsume();
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function consumeANumber(consume, next) {
|
|
|
+ let repr = [];
|
|
|
+ let type = "integer";
|
|
|
+ let code;
|
|
|
+ if (next() == 0x2b || next() == 0x2d) {
|
|
|
+ code = consume();
|
|
|
+ repr += stringFromCode(code);
|
|
|
+ }
|
|
|
+ while (digit(next())) {
|
|
|
+ code = consume();
|
|
|
+ repr += stringFromCode(code);
|
|
|
+ }
|
|
|
+ if (next(1) == 0x2e && digit(next(2))) {
|
|
|
+ code = consume();
|
|
|
+ repr += stringFromCode(code);
|
|
|
+ code = consume();
|
|
|
+ repr += stringFromCode(code);
|
|
|
+ type = "number";
|
|
|
+ while (digit(next())) {
|
|
|
+ code = consume();
|
|
|
+ repr += stringFromCode(code);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const c1 = next(1), c2 = next(2), c3 = next(3);
|
|
|
+ if ((c1 == 0x45 || c1 == 0x65) && digit(c2)) {
|
|
|
+ code = consume();
|
|
|
+ repr += stringFromCode(code);
|
|
|
+ code = consume();
|
|
|
+ repr += stringFromCode(code);
|
|
|
+ type = "number";
|
|
|
+ while (digit(next())) {
|
|
|
+ code = consume();
|
|
|
+ repr += stringFromCode(code);
|
|
|
+ }
|
|
|
+ } else if ((c1 == 0x45 || c1 == 0x65) && (c2 == 0x2b || c2 == 0x2d) && digit(c3)) {
|
|
|
+ code = consume();
|
|
|
+ repr += stringFromCode(code);
|
|
|
+ code = consume();
|
|
|
+ repr += stringFromCode(code);
|
|
|
+ code = consume();
|
|
|
+ repr += stringFromCode(code);
|
|
|
+ type = "number";
|
|
|
+ while (digit(next())) {
|
|
|
+ code = consume();
|
|
|
+ repr += stringFromCode(code);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const value = convertAStringToANumber(repr);
|
|
|
+ return { type: type, value: value, repr: repr };
|
|
|
+ }
|
|
|
+
|
|
|
+ function convertAStringToANumber(string) {
|
|
|
+ // CSS's number rules are identical to JS, afaik.
|
|
|
+ return Number(string);
|
|
|
+ }
|
|
|
+
|
|
|
+ function consumeTheRemnantsOfABadURL(consume, next, eof, donothing) {
|
|
|
+ let code;
|
|
|
+ while (code = consume()) { // eslint-disable-line no-cond-assign
|
|
|
+ if (code == 0x29 || eof()) {
|
|
|
+ return;
|
|
|
+ } else if (startsWithAValidEscape(next, code)) {
|
|
|
+ consumeEscape(consume, next, eof);
|
|
|
+ donothing();
|
|
|
+ } else {
|
|
|
+ donothing();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
function tokenize(str) {
|
|
|
str = preprocess(str);
|
|
|
let i = -1;
|
|
|
@@ -100,6 +502,7 @@ this.parseCss = this.parseCss || (() => {
|
|
|
// Line number information.
|
|
|
let line = 0;
|
|
|
let column = 0;
|
|
|
+
|
|
|
// The only use of lastLineLength is in reconsume().
|
|
|
let lastLineLength = 0;
|
|
|
const incrLineno = function () {
|
|
|
@@ -126,11 +529,11 @@ this.parseCss = this.parseCss || (() => {
|
|
|
if (num === undefined)
|
|
|
num = 1;
|
|
|
i += num;
|
|
|
- code = codepoint(i);
|
|
|
+ const code = codepoint(i);
|
|
|
if (newline(code)) incrLineno();
|
|
|
else column += num;
|
|
|
//console.log('Consume '+i+' '+String.fromCharCode(code) + ' 0x' + code.toString(16));
|
|
|
- return true;
|
|
|
+ return code;
|
|
|
};
|
|
|
const reconsume = function () {
|
|
|
i -= 1;
|
|
|
@@ -151,410 +554,9 @@ this.parseCss = this.parseCss || (() => {
|
|
|
const donothing = function () { };
|
|
|
const parseerror = function () { throw new Error("Parse error at index " + i + ", processing codepoint 0x" + code.toString(16) + "."); };
|
|
|
|
|
|
- const consumeAToken = function () {
|
|
|
- consumeComments();
|
|
|
- consume();
|
|
|
- if (whitespace(code)) {
|
|
|
- while (whitespace(next())) consume();
|
|
|
- return new Token(WHITESPACE_TOKEN_TYPE);
|
|
|
- }
|
|
|
- else if (code == 0x22) return consumeAStringToken();
|
|
|
- else if (code == 0x23) {
|
|
|
- if (namechar(next()) || areAValidEscape(next(1), next(2))) {
|
|
|
- const token = new Token(HASH_TOKEN_TYPE);
|
|
|
- if (wouldStartAnIdentifier(next(1), next(2), next(3))) token.type = "id";
|
|
|
- token.value = consumeAName();
|
|
|
- return token;
|
|
|
- } else {
|
|
|
- return new Token(DELIM_TOKEN_TYPE, stringFromCode(code));
|
|
|
- }
|
|
|
- }
|
|
|
- else if (code == 0x24) {
|
|
|
- if (next() == 0x3d) {
|
|
|
- consume();
|
|
|
- return new Token(SUFFIX_MATCH_TOKEN_TYPE);
|
|
|
- } else {
|
|
|
- return new Token(DELIM_TOKEN_TYPE, stringFromCode(code));
|
|
|
- }
|
|
|
- }
|
|
|
- else if (code == 0x27) return consumeAStringToken();
|
|
|
- else if (code == 0x28) return new Token(OPEN_PAREN_TOKEN_TYPE);
|
|
|
- else if (code == 0x29) return new Token(CLOSE_PAREN_TOKEN_TYPE);
|
|
|
- else if (code == 0x2a) {
|
|
|
- if (next() == 0x3d) {
|
|
|
- consume();
|
|
|
- return new Token(SUBSTRING_MATCH_TOKEN_TYPE);
|
|
|
- } else {
|
|
|
- return new Token(DELIM_TOKEN_TYPE, stringFromCode(code));
|
|
|
- }
|
|
|
- }
|
|
|
- else if (code == 0x2b) {
|
|
|
- if (startsWithANumber()) {
|
|
|
- reconsume();
|
|
|
- return consumeANumericToken();
|
|
|
- } else {
|
|
|
- return new Token(DELIM_TOKEN_TYPE, stringFromCode(code));
|
|
|
- }
|
|
|
- }
|
|
|
- else if (code == 0x2c) return new Token(COMMA_TOKEN_TYPE);
|
|
|
- else if (code == 0x2d) {
|
|
|
- if (startsWithANumber()) {
|
|
|
- reconsume();
|
|
|
- return consumeANumericToken();
|
|
|
- } else if (next(1) == 0x2d && next(2) == 0x3e) {
|
|
|
- consume(2);
|
|
|
- return new Token(CDC_TOKEN_TYPE);
|
|
|
- } else if (startsWithAnIdentifier()) {
|
|
|
- reconsume();
|
|
|
- return consumeAnIdentlikeToken();
|
|
|
- } else {
|
|
|
- return new Token(DELIM_TOKEN_TYPE, stringFromCode(code));
|
|
|
- }
|
|
|
- }
|
|
|
- else if (code == 0x2e) {
|
|
|
- if (startsWithANumber()) {
|
|
|
- reconsume();
|
|
|
- return consumeANumericToken();
|
|
|
- } else {
|
|
|
- return new Token(DELIM_TOKEN_TYPE, stringFromCode(code));
|
|
|
- }
|
|
|
- }
|
|
|
- else if (code == 0x3a) return new Token(COLON_TOKEN_TYPE);
|
|
|
- else if (code == 0x3b) return new Token(SEMICOLON_TOKEN_TYPE);
|
|
|
- else if (code == 0x3c) {
|
|
|
- if (next(1) == 0x21 && next(2) == 0x2d && next(3) == 0x2d) {
|
|
|
- consume(3);
|
|
|
- return new Token(CDO_TOKEN_TYPE);
|
|
|
- } else {
|
|
|
- return new Token(DELIM_TOKEN_TYPE, stringFromCode(code));
|
|
|
- }
|
|
|
- }
|
|
|
- else if (code == 0x40) {
|
|
|
- return new Token(DELIM_TOKEN_TYPE, stringFromCode(code));
|
|
|
- }
|
|
|
- else if (code == 0x5b) new Token(OPEN_SQUARE_TOKEN_TYPE);
|
|
|
- else if (code == 0x5c) {
|
|
|
- if (startsWithAValidEscape()) {
|
|
|
- reconsume();
|
|
|
- return consumeAnIdentlikeToken();
|
|
|
- } else {
|
|
|
- parseerror();
|
|
|
- return new Token(DELIM_TOKEN_TYPE, stringFromCode(code));
|
|
|
- }
|
|
|
- }
|
|
|
- else if (code == 0x5d) new Token(CLOSE_SQUARE_TOKEN_TYPE);
|
|
|
- else if (code == 0x5e) {
|
|
|
- if (next() == 0x3d) {
|
|
|
- consume();
|
|
|
- return new Token(PREFIX_MATCH_TOKEN_TYPE);
|
|
|
- } else {
|
|
|
- return new Token(DELIM_TOKEN_TYPE, stringFromCode(code));
|
|
|
- }
|
|
|
- }
|
|
|
- else if (code == 0x7b) return new Token(OPEN_CURLY_TOKEN_TYPE);
|
|
|
- else if (code == 0x7c) {
|
|
|
- if (next() == 0x3d) {
|
|
|
- consume();
|
|
|
- return new Token(DASH_MATCH_TOKEN_TYPE);
|
|
|
- } else if (next() == 0x7c) {
|
|
|
- consume();
|
|
|
- return new Token(COLUMN_TOKEN_TYPE);
|
|
|
- } else {
|
|
|
- return new Token(DELIM_TOKEN_TYPE, stringFromCode(code));
|
|
|
- }
|
|
|
- }
|
|
|
- else if (code == 0x7d) return new Token(CLOSE_CURLY_TOKEN_TYPE);
|
|
|
- else if (code == 0x7e) {
|
|
|
- if (next() == 0x3d) {
|
|
|
- consume();
|
|
|
- return new Token(INCLUDE_MATCH_TOKEN_TYPE);
|
|
|
- } else {
|
|
|
- return new Token(DELIM_TOKEN_TYPE, stringFromCode(code));
|
|
|
- }
|
|
|
- }
|
|
|
- else if (digit(code)) {
|
|
|
- reconsume();
|
|
|
- return consumeANumericToken();
|
|
|
- }
|
|
|
- else if (namestartchar(code)) {
|
|
|
- reconsume();
|
|
|
- return consumeAnIdentlikeToken();
|
|
|
- }
|
|
|
- else if (eof()) return new Token(EOF_TOKEN_TYPE);
|
|
|
- else return new Token(DELIM_TOKEN_TYPE, stringFromCode(code));
|
|
|
- };
|
|
|
-
|
|
|
- const consumeComments = function () {
|
|
|
- while (next(1) == 0x2f && next(2) == 0x2a) {
|
|
|
- consume(2);
|
|
|
- while (true) { // eslint-disable-line no-constant-condition
|
|
|
- consume();
|
|
|
- if (code == 0x2a && next() == 0x2f) {
|
|
|
- consume();
|
|
|
- break;
|
|
|
- } else if (eof()) {
|
|
|
- parseerror();
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- const consumeANumericToken = function () {
|
|
|
- const num = consumeANumber();
|
|
|
- if (wouldStartAnIdentifier(next(1), next(2), next(3))) {
|
|
|
- const token = new Token(DIMENSION_TOKEN_TYPE, num.value);
|
|
|
- token.repr = num.repr;
|
|
|
- token.type = num.type;
|
|
|
- token.unit = consumeAName();
|
|
|
- return token;
|
|
|
- } else if (next() == 0x25) {
|
|
|
- consume();
|
|
|
- const token = new Token(PERCENTAGE_TOKEN_TYPE, num.value);
|
|
|
- token.repr = num.repr;
|
|
|
- return token;
|
|
|
- } else {
|
|
|
- const token = new Token(NUMBER_TOKEN_TYPE, num.value);
|
|
|
- token.type = "integer";
|
|
|
- token.repr = num.repr;
|
|
|
- token.type = num.type;
|
|
|
- return token;
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- const consumeAnIdentlikeToken = function () {
|
|
|
- const str = consumeAName();
|
|
|
- if (str.toLowerCase() == "url" && next() == 0x28) {
|
|
|
- consume();
|
|
|
- while (whitespace(next(1)) && whitespace(next(2))) consume();
|
|
|
- if (next() == 0x22 || next() == 0x27) {
|
|
|
- return new Token(FUNCTION_TOKEN_TYPE, str);
|
|
|
- } else if (whitespace(next()) && (next(2) == 0x22 || next(2) == 0x27)) {
|
|
|
- return new Token(FUNCTION_TOKEN_TYPE, str);
|
|
|
- } else {
|
|
|
- return consumeAURLToken();
|
|
|
- }
|
|
|
- } else if (next() == 0x28) {
|
|
|
- consume();
|
|
|
- return new Token(FUNCTION_TOKEN_TYPE, str);
|
|
|
- } else {
|
|
|
- return new Token(IDENT_TOKEN_TYPE, str);
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- const consumeAStringToken = function (endingCodePoint) {
|
|
|
- if (endingCodePoint === undefined) endingCodePoint = code;
|
|
|
- let string = "";
|
|
|
- while (consume()) {
|
|
|
- if (code == endingCodePoint || eof()) {
|
|
|
- return new Token(STRING_TOKEN_TYPE, string);
|
|
|
- } else if (newline(code)) {
|
|
|
- parseerror();
|
|
|
- reconsume();
|
|
|
- return new Token(BAD_STRING_TOKEN_TYPE);
|
|
|
- } else if (code == 0x5c) {
|
|
|
- if (eof(next())) {
|
|
|
- donothing();
|
|
|
- } else if (newline(next())) {
|
|
|
- consume();
|
|
|
- } else {
|
|
|
- string += stringFromCode(consumeEscape());
|
|
|
- }
|
|
|
- } else {
|
|
|
- string += stringFromCode(code);
|
|
|
- }
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- const consumeAURLToken = function () {
|
|
|
- const token = new Token(URL_TOKEN_TYPE, "");
|
|
|
- while (whitespace(next())) consume();
|
|
|
- if (eof(next())) return token;
|
|
|
- while (consume()) {
|
|
|
- if (code == 0x29 || eof()) {
|
|
|
- return token;
|
|
|
- } else if (whitespace(code)) {
|
|
|
- while (whitespace(next())) consume();
|
|
|
- if (next() == 0x29 || eof(next())) {
|
|
|
- consume();
|
|
|
- return token;
|
|
|
- } else {
|
|
|
- consumeTheRemnantsOfABadURL();
|
|
|
- return new Token(BAD_URL_TOKEN_TYPE);
|
|
|
- }
|
|
|
- } else if (code == 0x22 || code == 0x27 || code == 0x28 || nonprintable(code)) {
|
|
|
- parseerror();
|
|
|
- consumeTheRemnantsOfABadURL();
|
|
|
- return new Token(BAD_URL_TOKEN_TYPE);
|
|
|
- } else if (code == 0x5c) {
|
|
|
- if (startsWithAValidEscape()) {
|
|
|
- token.value += stringFromCode(consumeEscape());
|
|
|
- } else {
|
|
|
- parseerror();
|
|
|
- consumeTheRemnantsOfABadURL();
|
|
|
- return new Token(BAD_URL_TOKEN_TYPE);
|
|
|
- }
|
|
|
- } else {
|
|
|
- token.value += stringFromCode(code);
|
|
|
- }
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- const consumeEscape = function () {
|
|
|
- // Assume the the current character is the \
|
|
|
- // and the next code point is not a newline.
|
|
|
- consume();
|
|
|
- if (hexdigit(code)) {
|
|
|
- // Consume 1-6 hex digits
|
|
|
- const digits = [code];
|
|
|
- for (let total = 0; total < 5; total++) {
|
|
|
- if (hexdigit(next())) {
|
|
|
- consume();
|
|
|
- digits.push(code);
|
|
|
- } else {
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- if (whitespace(next())) consume();
|
|
|
- let value = parseInt(digits.map(function (x) { return String.fromCharCode(x); }).join(""), 16);
|
|
|
- if (value > maximumallowedcodepoint) value = 0xfffd;
|
|
|
- return value;
|
|
|
- } else if (eof()) {
|
|
|
- return 0xfffd;
|
|
|
- } else {
|
|
|
- return code;
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- const areAValidEscape = function (c1, c2) {
|
|
|
- if (c1 != 0x5c) return false;
|
|
|
- if (newline(c2)) return false;
|
|
|
- return true;
|
|
|
- };
|
|
|
- const startsWithAValidEscape = function () {
|
|
|
- return areAValidEscape(code, next());
|
|
|
- };
|
|
|
-
|
|
|
- const wouldStartAnIdentifier = function (c1, c2, c3) {
|
|
|
- if (c1 == 0x2d) {
|
|
|
- return namestartchar(c2) || c2 == 0x2d || areAValidEscape(c2, c3);
|
|
|
- } else if (namestartchar(c1)) {
|
|
|
- return true;
|
|
|
- } else if (c1 == 0x5c) {
|
|
|
- return areAValidEscape(c1, c2);
|
|
|
- } else {
|
|
|
- return false;
|
|
|
- }
|
|
|
- };
|
|
|
- const startsWithAnIdentifier = function () {
|
|
|
- return wouldStartAnIdentifier(code, next(1), next(2));
|
|
|
- };
|
|
|
-
|
|
|
- const wouldStartANumber = function (c1, c2, c3) {
|
|
|
- if (c1 == 0x2b || c1 == 0x2d) {
|
|
|
- if (digit(c2)) return true;
|
|
|
- if (c2 == 0x2e && digit(c3)) return true;
|
|
|
- return false;
|
|
|
- } else if (c1 == 0x2e) {
|
|
|
- if (digit(c2)) return true;
|
|
|
- return false;
|
|
|
- } else if (digit(c1)) {
|
|
|
- return true;
|
|
|
- } else {
|
|
|
- return false;
|
|
|
- }
|
|
|
- };
|
|
|
- const startsWithANumber = function () {
|
|
|
- return wouldStartANumber(code, next(1), next(2));
|
|
|
- };
|
|
|
-
|
|
|
- const consumeAName = function () {
|
|
|
- let result = "";
|
|
|
- while (consume()) {
|
|
|
- if (namechar(code)) {
|
|
|
- result += stringFromCode(code);
|
|
|
- } else if (startsWithAValidEscape()) {
|
|
|
- result += stringFromCode(consumeEscape());
|
|
|
- } else {
|
|
|
- reconsume();
|
|
|
- return result;
|
|
|
- }
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- const consumeANumber = function () {
|
|
|
- let repr = [];
|
|
|
- let type = "integer";
|
|
|
- if (next() == 0x2b || next() == 0x2d) {
|
|
|
- consume();
|
|
|
- repr += stringFromCode(code);
|
|
|
- }
|
|
|
- while (digit(next())) {
|
|
|
- consume();
|
|
|
- repr += stringFromCode(code);
|
|
|
- }
|
|
|
- if (next(1) == 0x2e && digit(next(2))) {
|
|
|
- consume();
|
|
|
- repr += stringFromCode(code);
|
|
|
- consume();
|
|
|
- repr += stringFromCode(code);
|
|
|
- type = "number";
|
|
|
- while (digit(next())) {
|
|
|
- consume();
|
|
|
- repr += stringFromCode(code);
|
|
|
- }
|
|
|
- }
|
|
|
- const c1 = next(1), c2 = next(2), c3 = next(3);
|
|
|
- if ((c1 == 0x45 || c1 == 0x65) && digit(c2)) {
|
|
|
- consume();
|
|
|
- repr += stringFromCode(code);
|
|
|
- consume();
|
|
|
- repr += stringFromCode(code);
|
|
|
- type = "number";
|
|
|
- while (digit(next())) {
|
|
|
- consume();
|
|
|
- repr += stringFromCode(code);
|
|
|
- }
|
|
|
- } else if ((c1 == 0x45 || c1 == 0x65) && (c2 == 0x2b || c2 == 0x2d) && digit(c3)) {
|
|
|
- consume();
|
|
|
- repr += stringFromCode(code);
|
|
|
- consume();
|
|
|
- repr += stringFromCode(code);
|
|
|
- consume();
|
|
|
- repr += stringFromCode(code);
|
|
|
- type = "number";
|
|
|
- while (digit(next())) {
|
|
|
- consume();
|
|
|
- repr += stringFromCode(code);
|
|
|
- }
|
|
|
- }
|
|
|
- const value = convertAStringToANumber(repr);
|
|
|
- return { type: type, value: value, repr: repr };
|
|
|
- };
|
|
|
-
|
|
|
- const convertAStringToANumber = function (string) {
|
|
|
- // CSS's number rules are identical to JS, afaik.
|
|
|
- return Number(string);
|
|
|
- };
|
|
|
-
|
|
|
- const consumeTheRemnantsOfABadURL = function () {
|
|
|
- while (consume()) {
|
|
|
- if (code == 0x29 || eof()) {
|
|
|
- return;
|
|
|
- } else if (startsWithAValidEscape()) {
|
|
|
- consumeEscape();
|
|
|
- donothing();
|
|
|
- } else {
|
|
|
- donothing();
|
|
|
- }
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
let iterationCount = 0;
|
|
|
while (!eof(next())) {
|
|
|
- tokens.push(consumeAToken());
|
|
|
+ tokens.push(consumeAToken(consume, next, eof, reconsume, parseerror, donothing));
|
|
|
iterationCount++;
|
|
|
if (iterationCount > str.length * 2) return "I'm infinite-looping!";
|
|
|
}
|