| var EOF = 0; |
| |
| // https://drafts.csswg.org/css-syntax-3/ |
| // § 4.2. Definitions |
| |
| // digit |
| // A code point between U+0030 DIGIT ZERO (0) and U+0039 DIGIT NINE (9). |
| function isDigit(code) { |
| return code >= 0x0030 && code <= 0x0039; |
| } |
| |
| // hex digit |
| // A digit, or a code point between U+0041 LATIN CAPITAL LETTER A (A) and U+0046 LATIN CAPITAL LETTER F (F), |
| // or a code point between U+0061 LATIN SMALL LETTER A (a) and U+0066 LATIN SMALL LETTER F (f). |
| function isHexDigit(code) { |
| return ( |
| isDigit(code) || // 0 .. 9 |
| (code >= 0x0041 && code <= 0x0046) || // A .. F |
| (code >= 0x0061 && code <= 0x0066) // a .. f |
| ); |
| } |
| |
| // uppercase letter |
| // A code point between U+0041 LATIN CAPITAL LETTER A (A) and U+005A LATIN CAPITAL LETTER Z (Z). |
| function isUppercaseLetter(code) { |
| return code >= 0x0041 && code <= 0x005A; |
| } |
| |
| // lowercase letter |
| // A code point between U+0061 LATIN SMALL LETTER A (a) and U+007A LATIN SMALL LETTER Z (z). |
| function isLowercaseLetter(code) { |
| return code >= 0x0061 && code <= 0x007A; |
| } |
| |
| // letter |
| // An uppercase letter or a lowercase letter. |
| function isLetter(code) { |
| return isUppercaseLetter(code) || isLowercaseLetter(code); |
| } |
| |
| // non-ASCII code point |
| // A code point with a value equal to or greater than U+0080 <control>. |
| function isNonAscii(code) { |
| return code >= 0x0080; |
| } |
| |
| // name-start code point |
| // A letter, a non-ASCII code point, or U+005F LOW LINE (_). |
| function isNameStart(code) { |
| return isLetter(code) || isNonAscii(code) || code === 0x005F; |
| } |
| |
| // name code point |
| // A name-start code point, a digit, or U+002D HYPHEN-MINUS (-). |
| function isName(code) { |
| return isNameStart(code) || isDigit(code) || code === 0x002D; |
| } |
| |
| // non-printable code point |
| // A code point between U+0000 NULL and U+0008 BACKSPACE, or U+000B LINE TABULATION, |
| // or a code point between U+000E SHIFT OUT and U+001F INFORMATION SEPARATOR ONE, or U+007F DELETE. |
| function isNonPrintable(code) { |
| return ( |
| (code >= 0x0000 && code <= 0x0008) || |
| (code === 0x000B) || |
| (code >= 0x000E && code <= 0x001F) || |
| (code === 0x007F) |
| ); |
| } |
| |
| // newline |
| // U+000A LINE FEED. Note that U+000D CARRIAGE RETURN and U+000C FORM FEED are not included in this definition, |
| // as they are converted to U+000A LINE FEED during preprocessing. |
| // TODO: we doesn't do a preprocessing, so check a code point for U+000D CARRIAGE RETURN and U+000C FORM FEED |
| function isNewline(code) { |
| return code === 0x000A || code === 0x000D || code === 0x000C; |
| } |
| |
| // whitespace |
| // A newline, U+0009 CHARACTER TABULATION, or U+0020 SPACE. |
| function isWhiteSpace(code) { |
| return isNewline(code) || code === 0x0020 || code === 0x0009; |
| } |
| |
| // § 4.3.8. Check if two code points are a valid escape |
| function isValidEscape(first, second) { |
| // If the first code point is not U+005C REVERSE SOLIDUS (\), return false. |
| if (first !== 0x005C) { |
| return false; |
| } |
| |
| // Otherwise, if the second code point is a newline or EOF, return false. |
| if (isNewline(second) || second === EOF) { |
| return false; |
| } |
| |
| // Otherwise, return true. |
| return true; |
| } |
| |
| // § 4.3.9. Check if three code points would start an identifier |
| function isIdentifierStart(first, second, third) { |
| // Look at the first code point: |
| |
| // U+002D HYPHEN-MINUS |
| if (first === 0x002D) { |
| // If the second code point is a name-start code point or a U+002D HYPHEN-MINUS, |
| // or the second and third code points are a valid escape, return true. Otherwise, return false. |
| return ( |
| isNameStart(second) || |
| second === 0x002D || |
| isValidEscape(second, third) |
| ); |
| } |
| |
| // name-start code point |
| if (isNameStart(first)) { |
| // Return true. |
| return true; |
| } |
| |
| // U+005C REVERSE SOLIDUS (\) |
| if (first === 0x005C) { |
| // If the first and second code points are a valid escape, return true. Otherwise, return false. |
| return isValidEscape(first, second); |
| } |
| |
| // anything else |
| // Return false. |
| return false; |
| } |
| |
| // § 4.3.10. Check if three code points would start a number |
| function isNumberStart(first, second, third) { |
| // Look at the first code point: |
| |
| // U+002B PLUS SIGN (+) |
| // U+002D HYPHEN-MINUS (-) |
| if (first === 0x002B || first === 0x002D) { |
| // If the second code point is a digit, return true. |
| if (isDigit(second)) { |
| return 2; |
| } |
| |
| // Otherwise, if the second code point is a U+002E FULL STOP (.) |
| // and the third code point is a digit, return true. |
| // Otherwise, return false. |
| return second === 0x002E && isDigit(third) ? 3 : 0; |
| } |
| |
| // U+002E FULL STOP (.) |
| if (first === 0x002E) { |
| // If the second code point is a digit, return true. Otherwise, return false. |
| return isDigit(second) ? 2 : 0; |
| } |
| |
| // digit |
| if (isDigit(first)) { |
| // Return true. |
| return 1; |
| } |
| |
| // anything else |
| // Return false. |
| return 0; |
| } |
| |
| // |
| // Misc |
| // |
| |
| // detect BOM (https://en.wikipedia.org/wiki/Byte_order_mark) |
| function isBOM(code) { |
| // UTF-16BE |
| if (code === 0xFEFF) { |
| return 1; |
| } |
| |
| // UTF-16LE |
| if (code === 0xFFFE) { |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| // Fast code category |
| // |
| // https://drafts.csswg.org/css-syntax/#tokenizer-definitions |
| // > non-ASCII code point |
| // > A code point with a value equal to or greater than U+0080 <control> |
| // > name-start code point |
| // > A letter, a non-ASCII code point, or U+005F LOW LINE (_). |
| // > name code point |
| // > A name-start code point, a digit, or U+002D HYPHEN-MINUS (-) |
| // That means only ASCII code points has a special meaning and we define a maps for 0..127 codes only |
| var CATEGORY = new Array(0x80); |
| charCodeCategory.Eof = 0x80; |
| charCodeCategory.WhiteSpace = 0x82; |
| charCodeCategory.Digit = 0x83; |
| charCodeCategory.NameStart = 0x84; |
| charCodeCategory.NonPrintable = 0x85; |
| |
| for (var i = 0; i < CATEGORY.length; i++) { |
| switch (true) { |
| case isWhiteSpace(i): |
| CATEGORY[i] = charCodeCategory.WhiteSpace; |
| break; |
| |
| case isDigit(i): |
| CATEGORY[i] = charCodeCategory.Digit; |
| break; |
| |
| case isNameStart(i): |
| CATEGORY[i] = charCodeCategory.NameStart; |
| break; |
| |
| case isNonPrintable(i): |
| CATEGORY[i] = charCodeCategory.NonPrintable; |
| break; |
| |
| default: |
| CATEGORY[i] = i || charCodeCategory.Eof; |
| } |
| } |
| |
| function charCodeCategory(code) { |
| return code < 0x80 ? CATEGORY[code] : charCodeCategory.NameStart; |
| }; |
| |
| module.exports = { |
| isDigit: isDigit, |
| isHexDigit: isHexDigit, |
| isUppercaseLetter: isUppercaseLetter, |
| isLowercaseLetter: isLowercaseLetter, |
| isLetter: isLetter, |
| isNonAscii: isNonAscii, |
| isNameStart: isNameStart, |
| isName: isName, |
| isNonPrintable: isNonPrintable, |
| isNewline: isNewline, |
| isWhiteSpace: isWhiteSpace, |
| isValidEscape: isValidEscape, |
| isIdentifierStart: isIdentifierStart, |
| isNumberStart: isNumberStart, |
| |
| isBOM: isBOM, |
| charCodeCategory: charCodeCategory |
| }; |