2024-09-18 08:27:46 -07:00
/ *
MIT License http : //www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @ sokra
* /
"use strict" ;
/ * *
* @ typedef { object } CssTokenCallbacks
2024-11-04 11:06:36 -08:00
* @ property { ( function ( string , number , number , number , number ) : number ) = } url
* @ property { ( function ( string , number , number ) : number ) = } comment
* @ property { ( function ( string , number , number ) : number ) = } string
* @ property { ( function ( string , number , number ) : number ) = } leftParenthesis
* @ property { ( function ( string , number , number ) : number ) = } rightParenthesis
* @ property { ( function ( string , number , number ) : number ) = } function
* @ property { ( function ( string , number , number ) : number ) = } colon
* @ property { ( function ( string , number , number ) : number ) = } atKeyword
* @ property { ( function ( string , number , number ) : number ) = } delim
* @ property { ( function ( string , number , number ) : number ) = } identifier
* @ property { ( function ( string , number , number , boolean ) : number ) = } hash
* @ property { ( function ( string , number , number ) : number ) = } leftCurlyBracket
* @ property { ( function ( string , number , number ) : number ) = } rightCurlyBracket
* @ property { ( function ( string , number , number ) : number ) = } semicolon
* @ property { ( function ( string , number , number ) : number ) = } comma
2024-09-18 08:27:46 -07:00
* /
/** @typedef {function(string, number, CssTokenCallbacks): number} CharHandler */
// spec: https://drafts.csswg.org/css-syntax/
const CC _LINE _FEED = "\n" . charCodeAt ( 0 ) ;
const CC _CARRIAGE _RETURN = "\r" . charCodeAt ( 0 ) ;
const CC _FORM _FEED = "\f" . charCodeAt ( 0 ) ;
const CC _TAB = "\t" . charCodeAt ( 0 ) ;
const CC _SPACE = " " . charCodeAt ( 0 ) ;
const CC _SOLIDUS = "/" . charCodeAt ( 0 ) ;
const CC _REVERSE _SOLIDUS = "\\" . charCodeAt ( 0 ) ;
const CC _ASTERISK = "*" . charCodeAt ( 0 ) ;
const CC _LEFT _PARENTHESIS = "(" . charCodeAt ( 0 ) ;
const CC _RIGHT _PARENTHESIS = ")" . charCodeAt ( 0 ) ;
const CC _LEFT _CURLY = "{" . charCodeAt ( 0 ) ;
const CC _RIGHT _CURLY = "}" . charCodeAt ( 0 ) ;
const CC _LEFT _SQUARE = "[" . charCodeAt ( 0 ) ;
const CC _RIGHT _SQUARE = "]" . charCodeAt ( 0 ) ;
const CC _QUOTATION _MARK = '"' . charCodeAt ( 0 ) ;
const CC _APOSTROPHE = "'" . charCodeAt ( 0 ) ;
const CC _FULL _STOP = "." . charCodeAt ( 0 ) ;
const CC _COLON = ":" . charCodeAt ( 0 ) ;
const CC _SEMICOLON = ";" . charCodeAt ( 0 ) ;
const CC _COMMA = "," . charCodeAt ( 0 ) ;
const CC _PERCENTAGE = "%" . charCodeAt ( 0 ) ;
const CC _AT _SIGN = "@" . charCodeAt ( 0 ) ;
const CC _LOW _LINE = "_" . charCodeAt ( 0 ) ;
const CC _LOWER _A = "a" . charCodeAt ( 0 ) ;
2024-11-04 11:06:36 -08:00
const CC _LOWER _F = "f" . charCodeAt ( 0 ) ;
2024-09-18 08:27:46 -07:00
const CC _LOWER _E = "e" . charCodeAt ( 0 ) ;
2024-11-04 11:06:36 -08:00
const CC _LOWER _U = "u" . charCodeAt ( 0 ) ;
2024-09-18 08:27:46 -07:00
const CC _LOWER _Z = "z" . charCodeAt ( 0 ) ;
const CC _UPPER _A = "A" . charCodeAt ( 0 ) ;
2024-11-04 11:06:36 -08:00
const CC _UPPER _F = "F" . charCodeAt ( 0 ) ;
2024-09-18 08:27:46 -07:00
const CC _UPPER _E = "E" . charCodeAt ( 0 ) ;
2024-11-04 11:06:36 -08:00
const CC _UPPER _U = "E" . charCodeAt ( 0 ) ;
2024-09-18 08:27:46 -07:00
const CC _UPPER _Z = "Z" . charCodeAt ( 0 ) ;
const CC _0 = "0" . charCodeAt ( 0 ) ;
const CC _9 = "9" . charCodeAt ( 0 ) ;
const CC _NUMBER _SIGN = "#" . charCodeAt ( 0 ) ;
const CC _PLUS _SIGN = "+" . charCodeAt ( 0 ) ;
const CC _HYPHEN _MINUS = "-" . charCodeAt ( 0 ) ;
const CC _LESS _THAN _SIGN = "<" . charCodeAt ( 0 ) ;
const CC _GREATER _THAN _SIGN = ">" . charCodeAt ( 0 ) ;
/ * *
* @ param { number } cc char code
* @ returns { boolean } true , if cc is a newline
* /
const _isNewLine = cc =>
cc === CC _LINE _FEED || cc === CC _CARRIAGE _RETURN || cc === CC _FORM _FEED ;
/** @type {CharHandler} */
const consumeSpace = ( input , pos , _callbacks ) => {
2024-11-04 11:06:36 -08:00
// Consume as much whitespace as possible.
while ( _isWhiteSpace ( input . charCodeAt ( pos ) ) ) {
2024-09-18 08:27:46 -07:00
pos ++ ;
2024-11-04 11:06:36 -08:00
}
// Return a <whitespace-token>.
2024-09-18 08:27:46 -07:00
return pos ;
} ;
/ * *
* @ param { number } cc char code
* @ returns { boolean } true , if cc is a newline
* /
const _isNewline = cc =>
cc === CC _LINE _FEED || cc === CC _CARRIAGE _RETURN || cc === CC _FORM _FEED ;
/ * *
* @ param { number } cc char code
* @ returns { boolean } true , if cc is a space ( U + 0009 CHARACTER TABULATION or U + 0020 SPACE )
* /
const _isSpace = cc => cc === CC _TAB || cc === CC _SPACE ;
/ * *
* @ param { number } cc char code
* @ returns { boolean } true , if cc is a whitespace
* /
const _isWhiteSpace = cc => _isNewline ( cc ) || _isSpace ( cc ) ;
/ * *
* ident - start code point
*
* A letter , a non - ASCII code point , or U + 005 F LOW LINE ( _ ) .
* @ param { number } cc char code
* @ returns { boolean } true , if cc is a start code point of an identifier
* /
const isIdentStartCodePoint = cc =>
( cc >= CC _LOWER _A && cc <= CC _LOWER _Z ) ||
( cc >= CC _UPPER _A && cc <= CC _UPPER _Z ) ||
cc === CC _LOW _LINE ||
cc >= 0x80 ;
/** @type {CharHandler} */
2024-11-04 11:06:36 -08:00
const consumeDelimToken = ( input , pos , _callbacks ) =>
// Return a <delim-token> with its value set to the current input code point.
pos ;
2024-09-18 08:27:46 -07:00
/** @type {CharHandler} */
2024-11-04 11:06:36 -08:00
const consumeComments = ( input , pos , callbacks ) => {
// This section describes how to consume comments from a stream of code points. It returns nothing.
// If the next two input code point are U+002F SOLIDUS (/) followed by a U+002A ASTERISK (*),
// consume them and all following code points up to and including the first U+002A ASTERISK (*)
// followed by a U+002F SOLIDUS (/), or up to an EOF code point.
// Return to the start of this step.
while (
2024-09-18 08:27:46 -07:00
input . charCodeAt ( pos ) === CC _SOLIDUS &&
input . charCodeAt ( pos + 1 ) === CC _ASTERISK
) {
2024-11-04 11:06:36 -08:00
const start = pos ;
pos += 2 ;
for ( ; ; ) {
if ( pos === input . length ) {
// If the preceding paragraph ended by consuming an EOF code point, this is a parse error.
return pos ;
}
2024-09-18 08:27:46 -07:00
if (
input . charCodeAt ( pos ) === CC _ASTERISK &&
input . charCodeAt ( pos + 1 ) === CC _SOLIDUS
) {
pos += 2 ;
2024-11-04 11:06:36 -08:00
if ( callbacks . comment ) {
pos = callbacks . comment ( input , start , pos ) ;
}
2024-09-18 08:27:46 -07:00
break ;
}
2024-11-04 11:06:36 -08:00
2024-09-18 08:27:46 -07:00
pos ++ ;
}
}
return pos ;
} ;
2024-11-04 11:06:36 -08:00
/ * *
* @ param { number } cc char code
* @ returns { boolean } true , if cc is a hex digit
* /
const _isHexDigit = cc =>
_isDigit ( cc ) ||
( cc >= CC _UPPER _A && cc <= CC _UPPER _F ) ||
( cc >= CC _LOWER _A && cc <= CC _LOWER _F ) ;
2024-09-18 08:27:46 -07:00
/ * *
* @ param { string } input input
* @ param { number } pos position
2024-11-04 11:06:36 -08:00
* @ returns { number } position
2024-09-18 08:27:46 -07:00
* /
2024-11-04 11:06:36 -08:00
const _consumeAnEscapedCodePoint = ( input , pos ) => {
// This section describes how to consume an escaped code point.
// It assumes that the U+005C REVERSE SOLIDUS (\) has already been consumed and that the next input code point has already been verified to be part of a valid escape.
// It will return a code point.
// Consume the next input code point.
const cc = input . charCodeAt ( pos ) ;
2024-09-18 08:27:46 -07:00
pos ++ ;
2024-11-04 11:06:36 -08:00
// EOF
// This is a parse error. Return U+FFFD REPLACEMENT CHARACTER (<28> ).
if ( pos === input . length ) {
return pos ;
}
// hex digit
// Consume as many hex digits as possible, but no more than 5.
// Note that this means 1-6 hex digits have been consumed in total.
// If the next input code point is whitespace, consume it as well.
// Interpret the hex digits as a hexadecimal number.
// If this number is zero, or is for a surrogate, or is greater than the maximum allowed code point, return U+FFFD REPLACEMENT CHARACTER (<28> ).
// Otherwise, return the code point with that value.
if ( _isHexDigit ( cc ) ) {
for ( let i = 0 ; i < 5 ; i ++ ) {
if ( _isHexDigit ( input . charCodeAt ( pos ) ) ) {
pos ++ ;
}
}
if ( _isWhiteSpace ( input . charCodeAt ( pos ) ) ) {
pos ++ ;
}
return pos ;
}
// anything else
// Return the current input code point.
return pos ;
} ;
/** @type {CharHandler} */
const consumeAStringToken = ( input , pos , callbacks ) => {
// This section describes how to consume a string token from a stream of code points.
// It returns either a <string-token> or <bad-string-token>.
//
// This algorithm may be called with an ending code point, which denotes the code point that ends the string.
// If an ending code point is not specified, the current input code point is used.
const start = pos - 1 ;
const endingCodePoint = input . charCodeAt ( pos - 1 ) ;
// Initially create a <string-token> with its value set to the empty string.
// Repeatedly consume the next input code point from the stream:
2024-09-18 08:27:46 -07:00
for ( ; ; ) {
2024-11-04 11:06:36 -08:00
// EOF
// This is a parse error. Return the <string-token>.
if ( pos === input . length ) {
if ( callbacks . string !== undefined ) {
return callbacks . string ( input , start , pos ) ;
}
return pos ;
}
2024-09-18 08:27:46 -07:00
const cc = input . charCodeAt ( pos ) ;
2024-11-04 11:06:36 -08:00
pos ++ ;
// ending code point
// Return the <string-token>.
if ( cc === endingCodePoint ) {
if ( callbacks . string !== undefined ) {
return callbacks . string ( input , start , pos ) ;
}
return pos ;
}
// newline
// This is a parse error.
// Reconsume the current input code point, create a <bad-string-token>, and return it.
else if ( _isNewLine ( cc ) ) {
pos -- ;
2024-09-18 08:27:46 -07:00
// bad string
return pos ;
}
2024-11-04 11:06:36 -08:00
// U+005C REVERSE SOLIDUS (\)
else if ( cc === CC _REVERSE _SOLIDUS ) {
// If the next input code point is EOF, do nothing.
if ( pos === input . length ) {
return pos ;
}
// Otherwise, if the next input code point is a newline, consume it.
else if ( _isNewLine ( input . charCodeAt ( pos ) ) ) {
pos ++ ;
}
// Otherwise, (the stream starts with a valid escape) consume an escaped code point and append the returned code point to the <string-token>’ s value.
else if ( _ifTwoCodePointsAreValidEscape ( input , pos ) ) {
pos = _consumeAnEscapedCodePoint ( input , pos ) ;
}
}
// anything else
// Append the current input code point to the <string-token>’ s value.
else {
// Append
2024-09-18 08:27:46 -07:00
}
}
} ;
/ * *
* @ param { number } cc char code
2024-11-04 11:06:36 -08:00
* @ param { number } q char code
* @ returns { boolean } is non - ASCII code point
2024-09-18 08:27:46 -07:00
* /
2024-11-04 11:06:36 -08:00
const isNonASCIICodePoint = ( cc , q ) =>
// Simplify
2024-09-18 08:27:46 -07:00
cc > 0x80 ;
2024-11-04 11:06:36 -08:00
/ * *
* @ param { number } cc char code
* @ returns { boolean } is letter
* /
const isLetter = cc =>
( cc >= CC _LOWER _A && cc <= CC _LOWER _Z ) ||
( cc >= CC _UPPER _A && cc <= CC _UPPER _Z ) ;
/ * *
* @ param { number } cc char code
* @ param { number } q char code
* @ returns { boolean } is identifier start code
* /
const _isIdentStartCodePoint = ( cc , q ) =>
isLetter ( cc ) || isNonASCIICodePoint ( cc , q ) || cc === CC _LOW _LINE ;
/ * *
* @ param { number } cc char code
* @ param { number } q char code
* @ returns { boolean } is identifier code
* /
const _isIdentCodePoint = ( cc , q ) =>
_isIdentStartCodePoint ( cc , q ) || _isDigit ( cc ) || cc === CC _HYPHEN _MINUS ;
/ * *
* @ param { number } cc char code
* @ returns { boolean } is digit
* /
const _isDigit = cc => cc >= CC _0 && cc <= CC _9 ;
2024-09-18 08:27:46 -07:00
/ * *
2024-11-04 11:06:36 -08:00
* @ param { string } input input
* @ param { number } pos position
* @ param { number = } f first code point
* @ param { number = } s second code point
2024-09-18 08:27:46 -07:00
* @ returns { boolean } true if two code points are a valid escape
* /
2024-11-04 11:06:36 -08:00
const _ifTwoCodePointsAreValidEscape = ( input , pos , f , s ) => {
// This section describes how to check if two code points are a valid escape.
// The algorithm described here can be called explicitly with two code points, or can be called with the input stream itself.
// In the latter case, the two code points in question are the current input code point and the next input code point, in that order.
// Note: This algorithm will not consume any additional code point.
const first = f || input . charCodeAt ( pos - 1 ) ;
const second = s || input . charCodeAt ( pos ) ;
// If the first code point is not U+005C REVERSE SOLIDUS (\), return false.
2024-09-18 08:27:46 -07:00
if ( first !== CC _REVERSE _SOLIDUS ) return false ;
2024-11-04 11:06:36 -08:00
// Otherwise, if the second code point is a newline, return false.
2024-09-18 08:27:46 -07:00
if ( _isNewLine ( second ) ) return false ;
2024-11-04 11:06:36 -08:00
// Otherwise, return true.
2024-09-18 08:27:46 -07:00
return true ;
} ;
/ * *
2024-11-04 11:06:36 -08:00
* @ param { string } input input
* @ param { number } pos position
* @ param { number = } f first
* @ param { number = } s second
* @ param { number = } t third
* @ returns { boolean } true , if input at pos starts an identifier
2024-09-18 08:27:46 -07:00
* /
2024-11-04 11:06:36 -08:00
const _ifThreeCodePointsWouldStartAnIdentSequence = ( input , pos , f , s , t ) => {
// This section describes how to check if three code points would start an ident sequence.
// The algorithm described here can be called explicitly with three code points, or can be called with the input stream itself.
// In the latter case, the three code points in question are the current input code point and the next two input code points, in that order.
// Note: This algorithm will not consume any additional code points.
const first = f || input . charCodeAt ( pos - 1 ) ;
const second = s || input . charCodeAt ( pos ) ;
const third = t || input . charCodeAt ( pos + 1 ) ;
// Look at the first code point:
// U+002D HYPHEN-MINUS
if ( first === CC _HYPHEN _MINUS ) {
// If the second code point is an ident-start code point or a U+002D HYPHEN-MINUS
// or a U+002D HYPHEN-MINUS, or the second and third code points are a valid escape, return true.
if (
_isIdentStartCodePoint ( second , pos ) ||
second === CC _HYPHEN _MINUS ||
_ifTwoCodePointsAreValidEscape ( input , pos , second , third )
) {
return true ;
}
return false ;
}
// ident-start code point
else if ( _isIdentStartCodePoint ( first , pos - 1 ) ) {
return true ;
}
// U+005C REVERSE SOLIDUS (\)
// If the first and second code points are a valid escape, return true. Otherwise, return false.
else if ( first === CC _REVERSE _SOLIDUS ) {
if ( _ifTwoCodePointsAreValidEscape ( input , pos , first , second ) ) {
return true ;
}
return false ;
}
// anything else
// Return false.
return false ;
} ;
2024-09-18 08:27:46 -07:00
/ * *
* @ param { string } input input
* @ param { number } pos position
2024-11-04 11:06:36 -08:00
* @ param { number = } f first
* @ param { number = } s second
* @ param { number = } t third
2024-09-18 08:27:46 -07:00
* @ returns { boolean } true , if input at pos starts an identifier
* /
2024-11-04 11:06:36 -08:00
const _ifThreeCodePointsWouldStartANumber = ( input , pos , f , s , t ) => {
// This section describes how to check if three code points would start a number.
// The algorithm described here can be called explicitly with three code points, or can be called with the input stream itself.
// In the latter case, the three code points in question are the current input code point and the next two input code points, in that order.
// Note: This algorithm will not consume any additional code points.
const first = f || input . charCodeAt ( pos - 1 ) ;
const second = s || input . charCodeAt ( pos ) ;
const third = t || input . charCodeAt ( pos ) ;
// Look at the first code point:
// U+002B PLUS SIGN (+)
// U+002D HYPHEN-MINUS (-)
//
// If the second code point is a digit, return true.
// 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.
if ( first === CC _PLUS _SIGN || first === CC _HYPHEN _MINUS ) {
if ( _isDigit ( second ) ) {
return true ;
} else if ( second === CC _FULL _STOP && _isDigit ( third ) ) {
return true ;
}
return false ;
}
// U+002E FULL STOP (.)
// If the second code point is a digit, return true. Otherwise, return false.
else if ( first === CC _FULL _STOP ) {
if ( _isDigit ( second ) ) {
return true ;
2024-09-18 08:27:46 -07:00
}
2024-11-04 11:06:36 -08:00
return false ;
2024-09-18 08:27:46 -07:00
}
2024-11-04 11:06:36 -08:00
// digit
// Return true.
else if ( _isDigit ( first ) ) {
return true ;
2024-09-18 08:27:46 -07:00
}
2024-11-04 11:06:36 -08:00
// anything else
// Return false.
return false ;
2024-09-18 08:27:46 -07:00
} ;
/** @type {CharHandler} */
const consumeNumberSign = ( input , pos , callbacks ) => {
2024-11-04 11:06:36 -08:00
// If the next input code point is an ident code point or the next two input code points are a valid escape, then:
// - Create a <hash-token>.
// - If the next 3 input code points would start an ident sequence, set the <hash-token>’ s type flag to "id".
// - Consume an ident sequence, and set the <hash-token>’ s value to the returned string.
// - Return the <hash-token>.
const start = pos - 1 ;
const first = input . charCodeAt ( pos ) ;
const second = input . charCodeAt ( pos + 1 ) ;
2024-09-18 08:27:46 -07:00
if (
2024-11-04 11:06:36 -08:00
_isIdentCodePoint ( first , pos - 1 ) ||
_ifTwoCodePointsAreValidEscape ( input , pos , first , second )
2024-09-18 08:27:46 -07:00
) {
2024-11-04 11:06:36 -08:00
const third = input . charCodeAt ( pos + 2 ) ;
let isId = false ;
if (
_ifThreeCodePointsWouldStartAnIdentSequence (
input ,
pos ,
first ,
second ,
third
)
) {
isId = true ;
}
pos = _consumeAnIdentSequence ( input , pos , callbacks ) ;
if ( callbacks . hash !== undefined ) {
return callbacks . hash ( input , start , pos , isId ) ;
2024-09-18 08:27:46 -07:00
}
2024-11-04 11:06:36 -08:00
return pos ;
2024-09-18 08:27:46 -07:00
}
2024-11-04 11:06:36 -08:00
// Otherwise, return a <delim-token> with its value set to the current input code point.
2024-09-18 08:27:46 -07:00
return pos ;
} ;
/** @type {CharHandler} */
2024-11-04 11:06:36 -08:00
const consumeHyphenMinus = ( input , pos , callbacks ) => {
2024-09-18 08:27:46 -07:00
// If the input stream starts with a number, reconsume the current input code point, consume a numeric token, and return it.
2024-11-04 11:06:36 -08:00
if ( _ifThreeCodePointsWouldStartANumber ( input , pos ) ) {
pos -- ;
return consumeANumericToken ( input , pos , callbacks ) ;
}
// Otherwise, if the next 2 input code points are U+002D HYPHEN-MINUS U+003E GREATER-THAN SIGN (->), consume them and return a <CDC-token>.
else if (
input . charCodeAt ( pos ) === CC _HYPHEN _MINUS &&
input . charCodeAt ( pos + 1 ) === CC _GREATER _THAN _SIGN
) {
return pos + 2 ;
}
// Otherwise, if the input stream starts with an ident sequence, reconsume the current input code point, consume an ident-like token, and return it.
else if ( _ifThreeCodePointsWouldStartAnIdentSequence ( input , pos ) ) {
pos -- ;
return consumeAnIdentLikeToken ( input , pos , callbacks ) ;
2024-09-18 08:27:46 -07:00
}
2024-11-04 11:06:36 -08:00
// Otherwise, return a <delim-token> with its value set to the current input code point.
2024-09-18 08:27:46 -07:00
return pos ;
} ;
/** @type {CharHandler} */
2024-11-04 11:06:36 -08:00
const consumeFullStop = ( input , pos , callbacks ) => {
const start = pos - 1 ;
// If the input stream starts with a number, reconsume the current input code point, consume a numeric token, and return it.
if ( _ifThreeCodePointsWouldStartANumber ( input , pos ) ) {
pos -- ;
return consumeANumericToken ( input , pos , callbacks ) ;
}
// Otherwise, return a <delim-token> with its value set to the current input code point.
if ( callbacks . delim !== undefined ) {
return callbacks . delim ( input , start , pos ) ;
}
2024-09-18 08:27:46 -07:00
return pos ;
} ;
/** @type {CharHandler} */
2024-11-04 11:06:36 -08:00
const consumePlusSign = ( input , pos , callbacks ) => {
// If the input stream starts with a number, reconsume the current input code point, consume a numeric token, and return it.
if ( _ifThreeCodePointsWouldStartANumber ( input , pos ) ) {
pos -- ;
return consumeANumericToken ( input , pos , callbacks ) ;
}
// Otherwise, return a <delim-token> with its value set to the current input code point.
2024-09-18 08:27:46 -07:00
return pos ;
} ;
/** @type {CharHandler} */
2024-11-04 11:06:36 -08:00
const _consumeANumber = ( input , pos ) => {
// This section describes how to consume a number from a stream of code points.
// It returns a numeric value, and a type which is either "integer" or "number".
// Execute the following steps in order:
// Initially set type to "integer". Let repr be the empty string.
// If the next input code point is U+002B PLUS SIGN (+) or U+002D HYPHEN-MINUS (-), consume it and append it to repr.
if (
input . charCodeAt ( pos ) === CC _HYPHEN _MINUS ||
input . charCodeAt ( pos ) === CC _PLUS _SIGN
) {
2024-09-18 08:27:46 -07:00
pos ++ ;
}
2024-11-04 11:06:36 -08:00
// While the next input code point is a digit, consume it and append it to repr.
while ( _isDigit ( input . charCodeAt ( pos ) ) ) {
pos ++ ;
}
// If the next 2 input code points are U+002E FULL STOP (.) followed by a digit, then:
// 1. Consume the next input code point and append it to number part.
// 2. While the next input code point is a digit, consume it and append it to number part.
// 3. Set type to "number".
2024-09-18 08:27:46 -07:00
if (
2024-11-04 11:06:36 -08:00
input . charCodeAt ( pos ) === CC _FULL _STOP &&
_isDigit ( input . charCodeAt ( pos + 1 ) )
2024-09-18 08:27:46 -07:00
) {
pos ++ ;
2024-11-04 11:06:36 -08:00
while ( _isDigit ( input . charCodeAt ( pos ) ) ) {
2024-09-18 08:27:46 -07:00
pos ++ ;
}
2024-11-04 11:06:36 -08:00
}
// If the next 2 or 3 input code points are U+0045 LATIN CAPITAL LETTER E (E) or U+0065 LATIN SMALL LETTER E (e), optionally followed by U+002D HYPHEN-MINUS (-) or U+002B PLUS SIGN (+), followed by a digit, then:
// 1. Consume the next input code point.
// 2. If the next input code point is "+" or "-", consume it and append it to exponent part.
// 3. While the next input code point is a digit, consume it and append it to exponent part.
// 4. Set type to "number".
if (
( input . charCodeAt ( pos ) === CC _LOWER _E ||
input . charCodeAt ( pos ) === CC _UPPER _E ) &&
( ( ( input . charCodeAt ( pos + 1 ) === CC _HYPHEN _MINUS ||
input . charCodeAt ( pos + 1 ) === CC _PLUS _SIGN ) &&
_isDigit ( input . charCodeAt ( pos + 2 ) ) ) ||
_isDigit ( input . charCodeAt ( pos + 1 ) ) )
) {
pos ++ ;
if (
input . charCodeAt ( pos ) === CC _PLUS _SIGN ||
input . charCodeAt ( pos ) === CC _HYPHEN _MINUS
) {
pos ++ ;
2024-09-18 08:27:46 -07:00
}
2024-11-04 11:06:36 -08:00
while ( _isDigit ( input . charCodeAt ( pos ) ) ) {
pos ++ ;
2024-09-18 08:27:46 -07:00
}
}
2024-11-04 11:06:36 -08:00
// Let value be the result of interpreting number part as a base-10 number.
// If exponent part is non-empty, interpret it as a base-10 integer, then raise 10 to the power of the result, multiply it by value, and set value to that result.
// Return value and type.
return pos ;
2024-09-18 08:27:46 -07:00
} ;
/** @type {CharHandler} */
2024-11-04 11:06:36 -08:00
const consumeANumericToken = ( input , pos , callbacks ) => {
// This section describes how to consume a numeric token from a stream of code points.
// It returns either a <number-token>, <percentage-token>, or <dimension-token>.
// Consume a number and let number be the result.
pos = _consumeANumber ( input , pos , callbacks ) ;
// If the next 3 input code points would start an ident sequence, then:
//
// - Create a <dimension-token> with the same value and type flag as number, and a unit set initially to the empty string.
// - Consume an ident sequence. Set the <dimension-token>’ s unit to the returned value.
// - Return the <dimension-token>.
const first = input . charCodeAt ( pos ) ;
const second = input . charCodeAt ( pos + 1 ) ;
const third = input . charCodeAt ( pos + 2 ) ;
2024-09-18 08:27:46 -07:00
if (
2024-11-04 11:06:36 -08:00
_ifThreeCodePointsWouldStartAnIdentSequence (
input ,
pos ,
first ,
second ,
third
)
) {
return _consumeAnIdentSequence ( input , pos , callbacks ) ;
2024-09-18 08:27:46 -07:00
}
2024-11-04 11:06:36 -08:00
// Otherwise, if the next input code point is U+0025 PERCENTAGE SIGN (%), consume it.
// Create a <percentage-token> with the same value as number, and return it.
else if ( first === CC _PERCENTAGE ) {
return pos + 1 ;
}
// Otherwise, create a <number-token> with the same value and type flag as number, and return it.
return pos ;
} ;
/** @type {CharHandler} */
const consumeColon = ( input , pos , callbacks ) => {
// Return a <colon-token>.
if ( callbacks . colon !== undefined ) {
return callbacks . colon ( input , pos - 1 , pos ) ;
2024-09-18 08:27:46 -07:00
}
return pos ;
} ;
/** @type {CharHandler} */
const consumeLeftParenthesis = ( input , pos , callbacks ) => {
2024-11-04 11:06:36 -08:00
// Return a <(-token>.
2024-09-18 08:27:46 -07:00
if ( callbacks . leftParenthesis !== undefined ) {
return callbacks . leftParenthesis ( input , pos - 1 , pos ) ;
}
return pos ;
} ;
/** @type {CharHandler} */
const consumeRightParenthesis = ( input , pos , callbacks ) => {
2024-11-04 11:06:36 -08:00
// Return a <)-token>.
2024-09-18 08:27:46 -07:00
if ( callbacks . rightParenthesis !== undefined ) {
return callbacks . rightParenthesis ( input , pos - 1 , pos ) ;
}
return pos ;
} ;
2024-11-04 11:06:36 -08:00
/** @type {CharHandler} */
const consumeLeftSquareBracket = ( input , pos , callbacks ) =>
// Return a <]-token>.
pos ;
/** @type {CharHandler} */
const consumeRightSquareBracket = ( input , pos , callbacks ) =>
// Return a <]-token>.
pos ;
2024-09-18 08:27:46 -07:00
/** @type {CharHandler} */
const consumeLeftCurlyBracket = ( input , pos , callbacks ) => {
2024-11-04 11:06:36 -08:00
// Return a <{-token>.
2024-09-18 08:27:46 -07:00
if ( callbacks . leftCurlyBracket !== undefined ) {
return callbacks . leftCurlyBracket ( input , pos - 1 , pos ) ;
}
return pos ;
} ;
/** @type {CharHandler} */
const consumeRightCurlyBracket = ( input , pos , callbacks ) => {
2024-11-04 11:06:36 -08:00
// Return a <}-token>.
2024-09-18 08:27:46 -07:00
if ( callbacks . rightCurlyBracket !== undefined ) {
return callbacks . rightCurlyBracket ( input , pos - 1 , pos ) ;
}
return pos ;
} ;
/** @type {CharHandler} */
const consumeSemicolon = ( input , pos , callbacks ) => {
2024-11-04 11:06:36 -08:00
// Return a <semicolon-token>.
2024-09-18 08:27:46 -07:00
if ( callbacks . semicolon !== undefined ) {
return callbacks . semicolon ( input , pos - 1 , pos ) ;
}
return pos ;
} ;
/** @type {CharHandler} */
const consumeComma = ( input , pos , callbacks ) => {
2024-11-04 11:06:36 -08:00
// Return a <comma-token>.
2024-09-18 08:27:46 -07:00
if ( callbacks . comma !== undefined ) {
return callbacks . comma ( input , pos - 1 , pos ) ;
}
return pos ;
} ;
/** @type {CharHandler} */
2024-11-04 11:06:36 -08:00
const _consumeAnIdentSequence = ( input , pos ) => {
// This section describes how to consume an ident sequence from a stream of code points.
// It returns a string containing the largest name that can be formed from adjacent code points in the stream, starting from the first.
// Note: This algorithm does not do the verification of the first few code points that are necessary to ensure the returned code points would constitute an <ident-token>.
// If that is the intended use, ensure that the stream starts with an ident sequence before calling this algorithm.
// Let result initially be an empty string.
// Repeatedly consume the next input code point from the stream:
2024-09-18 08:27:46 -07:00
for ( ; ; ) {
const cc = input . charCodeAt ( pos ) ;
2024-11-04 11:06:36 -08:00
pos ++ ;
// ident code point
// Append the code point to result.
if ( _isIdentCodePoint ( cc , pos - 1 ) ) {
// Nothing
2024-09-18 08:27:46 -07:00
}
2024-11-04 11:06:36 -08:00
// the stream starts with a valid escape
// Consume an escaped code point. Append the returned code point to result.
else if ( _ifTwoCodePointsAreValidEscape ( input , pos ) ) {
pos = _consumeAnEscapedCodePoint ( input , pos ) ;
}
// anything else
// Reconsume the current input code point. Return result.
else {
return pos - 1 ;
}
}
2024-09-18 08:27:46 -07:00
} ;
2024-11-04 11:06:36 -08:00
/ * *
* @ param { number } cc char code
* @ returns { boolean } true , when cc is the non - printable code point , otherwise false
* /
const _isNonPrintableCodePoint = cc =>
( cc >= 0x00 && cc <= 0x08 ) ||
cc === 0x0b ||
( cc >= 0x0e && cc <= 0x1f ) ||
cc === 0x7f ;
/ * *
* @ param { string } input input
* @ param { number } pos position
* @ returns { number } position
* /
const consumeTheRemnantsOfABadUrl = ( input , pos ) => {
// This section describes how to consume the remnants of a bad url from a stream of code points,
// "cleaning up" after the tokenizer realizes that it’ s in the middle of a <bad-url-token> rather than a <url-token>.
// It returns nothing; its sole use is to consume enough of the input stream to reach a recovery point where normal tokenizing can resume.
// Repeatedly consume the next input code point from the stream:
for ( ; ; ) {
// EOF
// Return.
if ( pos === input . length ) {
return pos ;
}
const cc = input . charCodeAt ( pos ) ;
2024-09-18 08:27:46 -07:00
pos ++ ;
2024-11-04 11:06:36 -08:00
// U+0029 RIGHT PARENTHESIS ())
// Return.
if ( cc === CC _RIGHT _PARENTHESIS ) {
return pos ;
}
// the input stream starts with a valid escape
// Consume an escaped code point.
// This allows an escaped right parenthesis ("\)") to be encountered without ending the <bad-url-token>.
// This is otherwise identical to the "anything else" clause.
else if ( _ifTwoCodePointsAreValidEscape ( input , pos ) ) {
pos = _consumeAnEscapedCodePoint ( input , pos ) ;
}
// anything else
// Do nothing.
else {
// Do nothing.
2024-09-18 08:27:46 -07:00
}
}
2024-11-04 11:06:36 -08:00
} ;
/ * *
* @ param { string } input input
* @ param { number } pos position
* @ param { number } fnStart start
* @ param { CssTokenCallbacks } callbacks callbacks
* @ returns { pos } pos
* /
const consumeAUrlToken = ( input , pos , fnStart , callbacks ) => {
// This section describes how to consume a url token from a stream of code points.
// It returns either a <url-token> or a <bad-url-token>.
// Note: This algorithm assumes that the initial "url(" has already been consumed.
// This algorithm also assumes that it’ s being called to consume an "unquoted" value, like url(foo).
// A quoted value, like url("foo"), is parsed as a <function-token>.
// Consume an ident-like token automatically handles this distinction; this algorithm shouldn’ t be called directly otherwise.
// Initially create a <url-token> with its value set to the empty string.
// Consume as much whitespace as possible.
while ( _isWhiteSpace ( input . charCodeAt ( pos ) ) ) {
pos ++ ;
}
const contentStart = pos ;
// Repeatedly consume the next input code point from the stream:
for ( ; ; ) {
// EOF
// This is a parse error. Return the <url-token>.
if ( pos === input . length ) {
if ( callbacks . url !== undefined ) {
return callbacks . url ( input , fnStart , pos , contentStart , pos - 1 ) ;
}
return pos ;
}
const cc = input . charCodeAt ( pos ) ;
pos ++ ;
// U+0029 RIGHT PARENTHESIS ())
// Return the <url-token>.
if ( cc === CC _RIGHT _PARENTHESIS ) {
if ( callbacks . url !== undefined ) {
return callbacks . url ( input , fnStart , pos , contentStart , pos - 1 ) ;
}
return pos ;
}
// whitespace
// Consume as much whitespace as possible.
// If the next input code point is U+0029 RIGHT PARENTHESIS ()) or EOF, consume it and return the <url-token>
// (if EOF was encountered, this is a parse error); otherwise, consume the remnants of a bad url, create a <bad-url-token>, and return it.
else if ( _isWhiteSpace ( cc ) ) {
const end = pos - 1 ;
while ( _isWhiteSpace ( input . charCodeAt ( pos ) ) ) {
pos ++ ;
}
if ( pos === input . length ) {
if ( callbacks . url !== undefined ) {
return callbacks . url ( input , fnStart , pos , contentStart , end ) ;
2024-09-18 08:27:46 -07:00
}
2024-11-04 11:06:36 -08:00
2024-09-18 08:27:46 -07:00
return pos ;
}
2024-11-04 11:06:36 -08:00
if ( input . charCodeAt ( pos ) === CC _RIGHT _PARENTHESIS ) {
pos ++ ;
if ( callbacks . url !== undefined ) {
return callbacks . url ( input , fnStart , pos , contentStart , end ) ;
}
return pos ;
}
// Don't handle bad urls
return consumeTheRemnantsOfABadUrl ( input , pos ) ;
}
// U+0022 QUOTATION MARK (")
// U+0027 APOSTROPHE (')
// U+0028 LEFT PARENTHESIS (()
// non-printable code point
// This is a parse error. Consume the remnants of a bad url, create a <bad-url-token>, and return it.
else if (
cc === CC _QUOTATION _MARK ||
cc === CC _APOSTROPHE ||
cc === CC _LEFT _PARENTHESIS ||
_isNonPrintableCodePoint ( cc )
) {
// Don't handle bad urls
return consumeTheRemnantsOfABadUrl ( input , pos ) ;
}
// // U+005C REVERSE SOLIDUS (\)
// // If the stream starts with a valid escape, consume an escaped code point and append the returned code point to the <url-token>’ s value.
// // Otherwise, this is a parse error. Consume the remnants of a bad url, create a <bad-url-token>, and return it.
else if ( cc === CC _REVERSE _SOLIDUS ) {
if ( _ifTwoCodePointsAreValidEscape ( input , pos ) ) {
pos = _consumeAnEscapedCodePoint ( input , pos ) ;
} else {
// Don't handle bad urls
return consumeTheRemnantsOfABadUrl ( input , pos ) ;
}
}
// anything else
// Append the current input code point to the <url-token>’ s value.
else {
// Nothing
2024-09-18 08:27:46 -07:00
}
}
2024-11-04 11:06:36 -08:00
} ;
/** @type {CharHandler} */
const consumeAnIdentLikeToken = ( input , pos , callbacks ) => {
const start = pos ;
// This section describes how to consume an ident-like token from a stream of code points.
// It returns an <ident-token>, <function-token>, <url-token>, or <bad-url-token>.
pos = _consumeAnIdentSequence ( input , pos , callbacks ) ;
// If string’ s value is an ASCII case-insensitive match for "url", and the next input code point is U+0028 LEFT PARENTHESIS ((), consume it.
// While the next two input code points are whitespace, consume the next input code point.
// If the next one or two input code points are U+0022 QUOTATION MARK ("), U+0027 APOSTROPHE ('), or whitespace followed by U+0022 QUOTATION MARK (") or U+0027 APOSTROPHE ('), then create a <function-token> with its value set to string and return it.
// Otherwise, consume a url token, and return it.
if (
input . slice ( start , pos ) . toLowerCase ( ) === "url" &&
input . charCodeAt ( pos ) === CC _LEFT _PARENTHESIS
) {
2024-09-18 08:27:46 -07:00
pos ++ ;
2024-11-04 11:06:36 -08:00
const end = pos ;
while (
_isWhiteSpace ( input . charCodeAt ( pos ) ) &&
_isWhiteSpace ( input . charCodeAt ( pos + 1 ) )
) {
pos ++ ;
}
if (
input . charCodeAt ( pos ) === CC _QUOTATION _MARK ||
input . charCodeAt ( pos ) === CC _APOSTROPHE ||
( _isWhiteSpace ( input . charCodeAt ( pos ) ) &&
( input . charCodeAt ( pos + 1 ) === CC _QUOTATION _MARK ||
input . charCodeAt ( pos + 1 ) === CC _APOSTROPHE ) )
) {
if ( callbacks . function !== undefined ) {
return callbacks . function ( input , start , end ) ;
}
return pos ;
}
return consumeAUrlToken ( input , pos , start , callbacks ) ;
2024-09-18 08:27:46 -07:00
}
2024-11-04 11:06:36 -08:00
// Otherwise, if the next input code point is U+0028 LEFT PARENTHESIS ((), consume it.
// Create a <function-token> with its value set to string and return it.
if ( input . charCodeAt ( pos ) === CC _LEFT _PARENTHESIS ) {
pos ++ ;
if ( callbacks . function !== undefined ) {
return callbacks . function ( input , start , pos ) ;
}
return pos ;
}
// Otherwise, create an <ident-token> with its value set to string and return it.
if ( callbacks . identifier !== undefined ) {
return callbacks . identifier ( input , start , pos ) ;
}
2024-09-18 08:27:46 -07:00
return pos ;
} ;
/** @type {CharHandler} */
const consumeLessThan = ( input , pos , _callbacks ) => {
2024-11-04 11:06:36 -08:00
// If the next 3 input code points are U+0021 EXCLAMATION MARK U+002D HYPHEN-MINUS U+002D HYPHEN-MINUS (!--), consume them and return a <CDO-token>.
if ( input . slice ( pos , pos + 3 ) === "!--" ) {
return pos + 3 ;
}
// Otherwise, return a <delim-token> with its value set to the current input code point.
return pos ;
2024-09-18 08:27:46 -07:00
} ;
/** @type {CharHandler} */
2024-11-04 11:06:36 -08:00
const consumeCommercialAt = ( input , pos , callbacks ) => {
const start = pos - 1 ;
// If the next 3 input code points would start an ident sequence, consume an ident sequence, create an <at-keyword-token> with its value set to the returned value, and return it.
if (
_ifThreeCodePointsWouldStartAnIdentSequence (
input ,
pos ,
input . charCodeAt ( pos ) ,
input . charCodeAt ( pos + 1 ) ,
input . charCodeAt ( pos + 2 )
)
) {
pos = _consumeAnIdentSequence ( input , pos , callbacks ) ;
2024-09-18 08:27:46 -07:00
if ( callbacks . atKeyword !== undefined ) {
pos = callbacks . atKeyword ( input , start , pos ) ;
}
2024-11-04 11:06:36 -08:00
return pos ;
2024-09-18 08:27:46 -07:00
}
2024-11-04 11:06:36 -08:00
// Otherwise, return a <delim-token> with its value set to the current input code point.
2024-09-18 08:27:46 -07:00
return pos ;
} ;
/** @type {CharHandler} */
const consumeReverseSolidus = ( input , pos , callbacks ) => {
// If the input stream starts with a valid escape, reconsume the current input code point, consume an ident-like token, and return it.
2024-11-04 11:06:36 -08:00
if ( _ifTwoCodePointsAreValidEscape ( input , pos ) ) {
pos -- ;
return consumeAnIdentLikeToken ( input , pos , callbacks ) ;
2024-09-18 08:27:46 -07:00
}
2024-11-04 11:06:36 -08:00
2024-09-18 08:27:46 -07:00
// Otherwise, this is a parse error. Return a <delim-token> with its value set to the current input code point.
return pos ;
} ;
2024-11-04 11:06:36 -08:00
/** @type {CharHandler} */
const consumeAToken = ( input , pos , callbacks ) => {
const cc = input . charCodeAt ( pos - 1 ) ;
2024-09-18 08:27:46 -07:00
// https://drafts.csswg.org/css-syntax/#consume-token
switch ( cc ) {
// whitespace
case CC _LINE _FEED :
case CC _CARRIAGE _RETURN :
case CC _FORM _FEED :
case CC _TAB :
case CC _SPACE :
2024-11-04 11:06:36 -08:00
return consumeSpace ( input , pos , callbacks ) ;
2024-09-18 08:27:46 -07:00
// U+0022 QUOTATION MARK (")
case CC _QUOTATION _MARK :
2024-11-04 11:06:36 -08:00
return consumeAStringToken ( input , pos , callbacks ) ;
2024-09-18 08:27:46 -07:00
// U+0023 NUMBER SIGN (#)
case CC _NUMBER _SIGN :
2024-11-04 11:06:36 -08:00
return consumeNumberSign ( input , pos , callbacks ) ;
2024-09-18 08:27:46 -07:00
// U+0027 APOSTROPHE (')
case CC _APOSTROPHE :
2024-11-04 11:06:36 -08:00
return consumeAStringToken ( input , pos , callbacks ) ;
2024-09-18 08:27:46 -07:00
// U+0028 LEFT PARENTHESIS (()
case CC _LEFT _PARENTHESIS :
2024-11-04 11:06:36 -08:00
return consumeLeftParenthesis ( input , pos , callbacks ) ;
2024-09-18 08:27:46 -07:00
// U+0029 RIGHT PARENTHESIS ())
case CC _RIGHT _PARENTHESIS :
2024-11-04 11:06:36 -08:00
return consumeRightParenthesis ( input , pos , callbacks ) ;
2024-09-18 08:27:46 -07:00
// U+002B PLUS SIGN (+)
case CC _PLUS _SIGN :
2024-11-04 11:06:36 -08:00
return consumePlusSign ( input , pos , callbacks ) ;
2024-09-18 08:27:46 -07:00
// U+002C COMMA (,)
case CC _COMMA :
2024-11-04 11:06:36 -08:00
return consumeComma ( input , pos , callbacks ) ;
2024-09-18 08:27:46 -07:00
// U+002D HYPHEN-MINUS (-)
case CC _HYPHEN _MINUS :
2024-11-04 11:06:36 -08:00
return consumeHyphenMinus ( input , pos , callbacks ) ;
2024-09-18 08:27:46 -07:00
// U+002E FULL STOP (.)
case CC _FULL _STOP :
2024-11-04 11:06:36 -08:00
return consumeFullStop ( input , pos , callbacks ) ;
2024-09-18 08:27:46 -07:00
// U+003A COLON (:)
case CC _COLON :
2024-11-04 11:06:36 -08:00
return consumeColon ( input , pos , callbacks ) ;
2024-09-18 08:27:46 -07:00
// U+003B SEMICOLON (;)
case CC _SEMICOLON :
2024-11-04 11:06:36 -08:00
return consumeSemicolon ( input , pos , callbacks ) ;
2024-09-18 08:27:46 -07:00
// U+003C LESS-THAN SIGN (<)
case CC _LESS _THAN _SIGN :
2024-11-04 11:06:36 -08:00
return consumeLessThan ( input , pos , callbacks ) ;
2024-09-18 08:27:46 -07:00
// U+0040 COMMERCIAL AT (@)
case CC _AT _SIGN :
2024-11-04 11:06:36 -08:00
return consumeCommercialAt ( input , pos , callbacks ) ;
2024-09-18 08:27:46 -07:00
// U+005B LEFT SQUARE BRACKET ([)
case CC _LEFT _SQUARE :
2024-11-04 11:06:36 -08:00
return consumeLeftSquareBracket ( input , pos , callbacks ) ;
2024-09-18 08:27:46 -07:00
// U+005C REVERSE SOLIDUS (\)
case CC _REVERSE _SOLIDUS :
2024-11-04 11:06:36 -08:00
return consumeReverseSolidus ( input , pos , callbacks ) ;
2024-09-18 08:27:46 -07:00
// U+005D RIGHT SQUARE BRACKET (])
case CC _RIGHT _SQUARE :
2024-11-04 11:06:36 -08:00
return consumeRightSquareBracket ( input , pos , callbacks ) ;
2024-09-18 08:27:46 -07:00
// U+007B LEFT CURLY BRACKET ({)
case CC _LEFT _CURLY :
2024-11-04 11:06:36 -08:00
return consumeLeftCurlyBracket ( input , pos , callbacks ) ;
2024-09-18 08:27:46 -07:00
// U+007D RIGHT CURLY BRACKET (})
case CC _RIGHT _CURLY :
2024-11-04 11:06:36 -08:00
return consumeRightCurlyBracket ( input , pos , callbacks ) ;
2024-09-18 08:27:46 -07:00
default :
// digit
2024-11-04 11:06:36 -08:00
// Reconsume the current input code point, consume a numeric token, and return it.
if ( _isDigit ( cc ) ) {
pos -- ;
return consumeANumericToken ( input , pos , callbacks ) ;
} else if ( cc === CC _LOWER _U || cc === CC _UPPER _U ) {
// If unicode ranges allowed is true and the input stream would start a unicode-range,
// reconsume the current input code point, consume a unicode-range token, and return it.
// Skip now
// if (_ifThreeCodePointsWouldStartAUnicodeRange(input, pos)) {
// pos--;
// return consumeAUnicodeRangeToken(input, pos, callbacks);
// }
// Otherwise, reconsume the current input code point, consume an ident-like token, and return it.
pos -- ;
return consumeAnIdentLikeToken ( input , pos , callbacks ) ;
}
2024-09-18 08:27:46 -07:00
// ident-start code point
2024-11-04 11:06:36 -08:00
// Reconsume the current input code point, consume an ident-like token, and return it.
else if ( isIdentStartCodePoint ( cc ) ) {
pos -- ;
return consumeAnIdentLikeToken ( input , pos , callbacks ) ;
2024-09-18 08:27:46 -07:00
}
2024-11-04 11:06:36 -08:00
2024-09-18 08:27:46 -07:00
// EOF, but we don't have it
2024-11-04 11:06:36 -08:00
2024-09-18 08:27:46 -07:00
// anything else
2024-11-04 11:06:36 -08:00
// Return a <delim-token> with its value set to the current input code point.
return consumeDelimToken ( input , pos , callbacks ) ;
2024-09-18 08:27:46 -07:00
}
2024-11-04 11:06:36 -08:00
} ;
2024-09-18 08:27:46 -07:00
/ * *
* @ param { string } input input css
* @ param { CssTokenCallbacks } callbacks callbacks
* @ returns { void }
* /
module . exports = ( input , callbacks ) => {
// This section describes how to consume a token from a stream of code points. It will return a single token of any type.
let pos = 0 ;
while ( pos < input . length ) {
// Consume comments.
pos = consumeComments ( input , pos , callbacks ) ;
// Consume the next input code point.
2024-11-04 11:06:36 -08:00
pos ++ ;
pos = consumeAToken ( input , pos , callbacks ) ;
2024-09-18 08:27:46 -07:00
}
} ;
module . exports . isIdentStartCodePoint = isIdentStartCodePoint ;
/ * *
* @ param { string } input input
* @ param { number } pos position
* @ returns { number } position after comments
* /
module . exports . eatComments = ( input , pos ) => {
for ( ; ; ) {
const originalPos = pos ;
pos = consumeComments ( input , pos , { } ) ;
if ( originalPos === pos ) {
break ;
}
}
return pos ;
} ;
/ * *
* @ param { string } input input
* @ param { number } pos position
* @ returns { number } position after whitespace
* /
module . exports . eatWhitespace = ( input , pos ) => {
while ( _isWhiteSpace ( input . charCodeAt ( pos ) ) ) {
pos ++ ;
}
return pos ;
} ;
/ * *
* @ param { string } input input
* @ param { number } pos position
* @ returns { number } position after whitespace and comments
* /
module . exports . eatWhitespaceAndComments = ( input , pos ) => {
for ( ; ; ) {
const originalPos = pos ;
pos = consumeComments ( input , pos , { } ) ;
while ( _isWhiteSpace ( input . charCodeAt ( pos ) ) ) {
pos ++ ;
}
if ( originalPos === pos ) {
break ;
}
}
return pos ;
} ;
2024-11-04 11:06:36 -08:00
/ * *
* @ param { string } input input
* @ param { number } pos position
* @ returns { number } position after whitespace and comments
* /
module . exports . eatComments = ( input , pos ) => {
for ( ; ; ) {
const originalPos = pos ;
pos = consumeComments ( input , pos , { } ) ;
if ( originalPos === pos ) {
break ;
}
}
return pos ;
} ;
2024-09-18 08:27:46 -07:00
/ * *
* @ param { string } input input
* @ param { number } pos position
* @ returns { number } position after whitespace
* /
module . exports . eatWhiteLine = ( input , pos ) => {
for ( ; ; ) {
const cc = input . charCodeAt ( pos ) ;
if ( _isSpace ( cc ) ) {
pos ++ ;
continue ;
}
if ( _isNewLine ( cc ) ) pos ++ ;
// For `\r\n`
if ( cc === CC _CARRIAGE _RETURN && input . charCodeAt ( pos + 1 ) === CC _LINE _FEED )
pos ++ ;
break ;
}
return pos ;
} ;
2024-11-04 11:06:36 -08:00
/ * *
* @ param { string } input input
* @ param { number } pos position
* @ returns { [ number , number ] | undefined } positions of ident sequence
* /
module . exports . skipCommentsAndEatIdentSequence = ( input , pos ) => {
pos = module . exports . eatComments ( input , pos ) ;
const start = pos ;
if (
_ifThreeCodePointsWouldStartAnIdentSequence (
input ,
pos ,
input . charCodeAt ( pos ) ,
input . charCodeAt ( pos + 1 ) ,
input . charCodeAt ( pos + 2 )
)
) {
return [ start , _consumeAnIdentSequence ( input , pos , { } ) ] ;
}
return undefined ;
} ;
/ * *
* @ param { string } input input
* @ param { number } pos position
* @ returns { [ number , number ] | undefined } positions of ident sequence
* /
module . exports . eatString = ( input , pos ) => {
pos = module . exports . eatWhitespaceAndComments ( input , pos ) ;
const start = pos ;
if (
input . charCodeAt ( pos ) === CC _QUOTATION _MARK ||
input . charCodeAt ( pos ) === CC _APOSTROPHE
) {
return [ start , consumeAStringToken ( input , pos + 1 , { } ) ] ;
}
return undefined ;
} ;
/ * *
* @ param { string } input input
* @ param { number } pos position
* @ param { CssTokenCallbacks } cbs callbacks
* @ returns { [ number , number ] [ ] } positions of ident sequence
* /
module . exports . eatImageSetStrings = ( input , pos , cbs ) => {
/** @type {[number, number][]} */
const result = [ ] ;
let isFirst = true ;
let needStop = false ;
// We already in `func(` token
let balanced = 1 ;
/** @type {CssTokenCallbacks} */
const callbacks = {
... cbs ,
string : ( _input , start , end ) => {
if ( isFirst && balanced === 1 ) {
result . push ( [ start , end ] ) ;
isFirst = false ;
}
return end ;
} ,
comma : ( _input , _start , end ) => {
if ( balanced === 1 ) {
isFirst = true ;
}
return end ;
} ,
leftParenthesis : ( input , start , end ) => {
balanced ++ ;
return end ;
} ,
function : ( _input , start , end ) => {
balanced ++ ;
return end ;
} ,
rightParenthesis : ( _input , _start , end ) => {
balanced -- ;
if ( balanced === 0 ) {
needStop = true ;
}
return end ;
}
} ;
while ( pos < input . length ) {
// Consume comments.
pos = consumeComments ( input , pos , callbacks ) ;
// Consume the next input code point.
pos ++ ;
pos = consumeAToken ( input , pos , callbacks ) ;
if ( needStop ) {
break ;
}
}
return result ;
} ;
/ * *
* @ param { string } input input
* @ param { number } pos position
* @ param { CssTokenCallbacks } cbs callbacks
* @ returns { [ [ number , number , number , number ] | undefined , [ number , number ] | undefined , [ number , number ] | undefined , [ number , number ] | undefined ] } positions of top level tokens
* /
module . exports . eatImportTokens = ( input , pos , cbs ) => {
const result =
/** @type {[[number, number, number, number] | undefined, [number, number] | undefined, [number, number] | undefined, [number, number] | undefined]} */
( new Array ( 4 ) ) ;
/** @type {0 | 1 | 2 | undefined} */
let scope ;
let needStop = false ;
let balanced = 0 ;
/** @type {CssTokenCallbacks} */
const callbacks = {
... cbs ,
url : ( _input , start , end , contentStart , contentEnd ) => {
if (
result [ 0 ] === undefined &&
balanced === 0 &&
result [ 1 ] === undefined &&
result [ 2 ] === undefined &&
result [ 3 ] === undefined
) {
result [ 0 ] = [ start , end , contentStart , contentEnd ] ;
scope = undefined ;
}
return end ;
} ,
string : ( _input , start , end ) => {
if (
balanced === 0 &&
result [ 0 ] === undefined &&
result [ 1 ] === undefined &&
result [ 2 ] === undefined &&
result [ 3 ] === undefined
) {
result [ 0 ] = [ start , end , start + 1 , end - 1 ] ;
scope = undefined ;
} else if ( result [ 0 ] !== undefined && scope === 0 ) {
result [ 0 ] [ 2 ] = start + 1 ;
result [ 0 ] [ 3 ] = end - 1 ;
}
return end ;
} ,
leftParenthesis : ( _input , _start , end ) => {
balanced ++ ;
return end ;
} ,
rightParenthesis : ( _input , _start , end ) => {
balanced -- ;
if ( balanced === 0 && scope !== undefined ) {
/** @type {[number, number]} */
( result [ scope ] ) [ 1 ] = end ;
scope = undefined ;
}
return end ;
} ,
function : ( input , start , end ) => {
if ( balanced === 0 ) {
const name = input
. slice ( start , end - 1 )
. replace ( /\\/g , "" )
. toLowerCase ( ) ;
if (
name === "url" &&
result [ 0 ] === undefined &&
result [ 1 ] === undefined &&
result [ 2 ] === undefined &&
result [ 3 ] === undefined
) {
scope = 0 ;
result [ scope ] = [ start , end + 1 , end + 1 , end + 1 ] ;
} else if (
name === "layer" &&
result [ 1 ] === undefined &&
result [ 2 ] === undefined
) {
scope = 1 ;
result [ scope ] = [ start , end ] ;
} else if ( name === "supports" && result [ 2 ] === undefined ) {
scope = 2 ;
result [ scope ] = [ start , end ] ;
} else {
scope = undefined ;
}
}
balanced ++ ;
return end ;
} ,
identifier : ( input , start , end ) => {
if (
balanced === 0 &&
result [ 1 ] === undefined &&
result [ 2 ] === undefined
) {
const name = input . slice ( start , end ) . replace ( /\\/g , "" ) . toLowerCase ( ) ;
if ( name === "layer" ) {
result [ 1 ] = [ start , end ] ;
scope = undefined ;
}
}
return end ;
} ,
semicolon : ( _input , start , end ) => {
if ( balanced === 0 ) {
needStop = true ;
result [ 3 ] = [ start , end ] ;
}
return end ;
}
} ;
while ( pos < input . length ) {
// Consume comments.
pos = consumeComments ( input , pos , callbacks ) ;
// Consume the next input code point.
pos ++ ;
pos = consumeAToken ( input , pos , callbacks ) ;
if ( needStop ) {
break ;
}
}
return result ;
} ;
/ * *
* @ param { string } input input
* @ param { number } pos position
* @ returns { [ number , number ] | undefined } positions of ident sequence
* /
module . exports . eatIdentSequence = ( input , pos ) => {
pos = module . exports . eatWhitespaceAndComments ( input , pos ) ;
const start = pos ;
if (
_ifThreeCodePointsWouldStartAnIdentSequence (
input ,
pos ,
input . charCodeAt ( pos ) ,
input . charCodeAt ( pos + 1 ) ,
input . charCodeAt ( pos + 2 )
)
) {
return [ start , _consumeAnIdentSequence ( input , pos , { } ) ] ;
}
return undefined ;
} ;
/ * *
* @ param { string } input input
* @ param { number } pos position
* @ returns { [ number , number , boolean ] | undefined } positions of ident sequence or string
* /
module . exports . eatIdentSequenceOrString = ( input , pos ) => {
pos = module . exports . eatWhitespaceAndComments ( input , pos ) ;
const start = pos ;
if (
input . charCodeAt ( pos ) === CC _QUOTATION _MARK ||
input . charCodeAt ( pos ) === CC _APOSTROPHE
) {
return [ start , consumeAStringToken ( input , pos + 1 , { } ) , false ] ;
} else if (
_ifThreeCodePointsWouldStartAnIdentSequence (
input ,
pos ,
input . charCodeAt ( pos ) ,
input . charCodeAt ( pos + 1 ) ,
input . charCodeAt ( pos + 2 )
)
) {
return [ start , _consumeAnIdentSequence ( input , pos , { } ) , true ] ;
}
return undefined ;
} ;
/ * *
* @ param { string } chars characters
* @ returns { ( input : string , pos : number ) => number } function to eat characters
* /
module . exports . eatUntil = chars => {
const charCodes = Array . from ( { length : chars . length } , ( _ , i ) =>
chars . charCodeAt ( i )
) ;
const arr = Array . from (
{ length : charCodes . reduce ( ( a , b ) => Math . max ( a , b ) , 0 ) + 1 } ,
( ) => false
) ;
for ( const cc of charCodes ) {
arr [ cc ] = true ;
}
return ( input , pos ) => {
for ( ; ; ) {
const cc = input . charCodeAt ( pos ) ;
if ( cc < arr . length && arr [ cc ] ) {
return pos ;
}
pos ++ ;
if ( pos === input . length ) return pos ;
}
} ;
} ;