mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-14 18:12:09 +08:00
698 lines
17 KiB
TypeScript
698 lines
17 KiB
TypeScript
export interface Stylesheet {
|
|
rules: Rule[];
|
|
}
|
|
export type Rule = QualifiedRule | AtRule;
|
|
|
|
export interface AtRule {
|
|
type: 'at-rule';
|
|
name: string;
|
|
prelude: InputToken[];
|
|
block: SimpleBlock;
|
|
}
|
|
export interface QualifiedRule {
|
|
type: 'qualified-rule';
|
|
prelude: InputToken[];
|
|
block: SimpleBlock;
|
|
}
|
|
|
|
// const nonQuoteURLRegEx = /(:?[^\)\s\t\n\r\f\'\"\(]|\\(?:\$|\n|[0-9a-fA-F]{1,6}\s?))*/gym; // TODO: non-printable code points omitted
|
|
|
|
export type InputToken = '(' | ')' | '{' | '}' | '[' | ']' | ':' | ';' | ',' | ' ' | '^=' | '|=' | '$=' | '*=' | '~=' | '<!--' | '-->' | undefined | /* <EOF-token> */ InputTokenObject | FunctionInputToken | FunctionToken | SimpleBlock | AtKeywordToken;
|
|
|
|
export const enum TokenObjectType {
|
|
/**
|
|
* <string-token>
|
|
*/
|
|
string = 1,
|
|
/**
|
|
* <delim-token>
|
|
*/
|
|
delim = 2,
|
|
/**
|
|
* <number-token>
|
|
*/
|
|
number = 3,
|
|
/**
|
|
* <percentage-token>
|
|
*/
|
|
percentage = 4,
|
|
/**
|
|
* <dimension-token>
|
|
*/
|
|
dimension = 5,
|
|
/**
|
|
* <ident-token>
|
|
*/
|
|
ident = 6,
|
|
/**
|
|
* <url-token>
|
|
*/
|
|
url = 7,
|
|
/**
|
|
* <function-token>
|
|
* This is a token indicating a function's leading: <ident-token>(
|
|
*/
|
|
functionToken = 8,
|
|
/**
|
|
* <simple-block>
|
|
*/
|
|
simpleBlock = 9,
|
|
/**
|
|
* <comment-token>
|
|
*/
|
|
comment = 10,
|
|
/**
|
|
* <at-keyword-token>
|
|
*/
|
|
atKeyword = 11,
|
|
/**
|
|
* <hash-token>
|
|
*/
|
|
hash = 12,
|
|
/**
|
|
* <function>
|
|
* This is a complete consumed function: <function-token>([<component-value> [, <component-value>]*])")"
|
|
*/
|
|
function = 14,
|
|
}
|
|
|
|
export interface InputTokenObject {
|
|
type: TokenObjectType;
|
|
text: string;
|
|
}
|
|
|
|
/**
|
|
* This is a "<ident>(" token.
|
|
*/
|
|
export interface FunctionInputToken extends InputTokenObject {
|
|
name: string;
|
|
}
|
|
|
|
/**
|
|
* This is a completely parsed function like "<ident>([component [, component]*])".
|
|
*/
|
|
export interface FunctionToken extends FunctionInputToken {
|
|
components: any[];
|
|
}
|
|
|
|
export interface SimpleBlock extends InputTokenObject {
|
|
associatedToken: InputToken;
|
|
values: InputToken[];
|
|
}
|
|
|
|
export type AtKeywordToken = InputTokenObject;
|
|
|
|
const commentRegEx = /(\/\*(?:[^\*]|\*[^\/])*\*\/)/gmy;
|
|
// eslint-disable-next-line no-control-regex
|
|
const nameRegEx = /-?(?:(?:[a-zA-Z_]|[^\x00-\x7F]|\\(?:\$|\n|[0-9a-fA-F]{1,6}\s?))(?:[a-zA-Z_0-9\-]*|\\(?:\$|\n|[0-9a-fA-F]{1,6}\s?))*)/gmy;
|
|
const numberRegEx = /[\+\-]?(?:\d+\.\d+|\d+|\.\d+)(?:[eE][\+\-]?\d+)?/gmy;
|
|
const doubleQuoteStringRegEx = /"((?:[^\n\r\f\"]|\\(?:\$|\n|[0-9a-fA-F]{1,6}\s?))*)(:?"|$)/gmy; // Besides $n, parse escape
|
|
|
|
const whitespaceRegEx = /[\s\t\n\r\f]*/gmy;
|
|
|
|
const singleQuoteStringRegEx = /'((?:[^\n\r\f\']|\\(?:\$|\n|[0-9a-fA-F]{1,6}\s?))*)(:?'|$)/gmy; // Besides $n, parse escape
|
|
|
|
/**
|
|
* CSS parser following relatively close:
|
|
* CSS Syntax Module Level 3
|
|
* https://www.w3.org/TR/css-syntax-3/
|
|
*/
|
|
|
|
export class CSS3Parser {
|
|
private nextInputCodePointIndex = 0;
|
|
private reconsumedInputToken: InputToken;
|
|
private topLevelFlag: boolean;
|
|
|
|
constructor(private text: string) {}
|
|
|
|
/**
|
|
* For testing purposes.
|
|
* This method allows us to run and assert the proper working of the tokenizer.
|
|
*/
|
|
tokenize(): InputToken[] {
|
|
const tokens: InputToken[] = [];
|
|
let inputToken: InputToken;
|
|
do {
|
|
inputToken = this.consumeAToken();
|
|
tokens.push(inputToken);
|
|
} while (inputToken);
|
|
|
|
return tokens;
|
|
}
|
|
|
|
/**
|
|
* 4.3.1. Consume a token
|
|
* https://www.w3.org/TR/css-syntax-3/#consume-a-token
|
|
*/
|
|
private consumeAToken(): InputToken {
|
|
if (this.reconsumedInputToken) {
|
|
const result = this.reconsumedInputToken;
|
|
this.reconsumedInputToken = null;
|
|
|
|
return result;
|
|
}
|
|
const char = this.text[this.nextInputCodePointIndex];
|
|
switch (char) {
|
|
case '"':
|
|
return this.consumeAStringToken();
|
|
case "'":
|
|
return this.consumeAStringToken();
|
|
case '(':
|
|
case ')':
|
|
case ',':
|
|
case ':':
|
|
case ';':
|
|
case '[':
|
|
case ']':
|
|
case '{':
|
|
case '}':
|
|
this.nextInputCodePointIndex++;
|
|
|
|
return <any>char;
|
|
case '#':
|
|
return this.consumeAHashToken() || this.consumeADelimToken();
|
|
case ' ':
|
|
case '\t':
|
|
case '\n':
|
|
case '\r':
|
|
case '\f':
|
|
return this.consumeAWhitespace();
|
|
case '@':
|
|
return this.consumeAtKeyword() || this.consumeADelimToken();
|
|
// TODO: Only if this is valid escape, otherwise it is a parse error
|
|
case '\\':
|
|
return this.consumeAnIdentLikeToken() || this.consumeADelimToken();
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
return this.consumeANumericToken();
|
|
case 'u':
|
|
case 'U':
|
|
if (this.text[this.nextInputCodePointIndex + 1] === '+') {
|
|
const thirdChar = this.text[this.nextInputCodePointIndex + 2];
|
|
if ((thirdChar >= '0' && thirdChar <= '9') || thirdChar === '?') {
|
|
// TODO: Handle unicode stuff such as U+002B
|
|
throw new Error('Unicode tokens not supported!');
|
|
}
|
|
}
|
|
|
|
return this.consumeAnIdentLikeToken() || this.consumeADelimToken();
|
|
case '$':
|
|
case '*':
|
|
case '^':
|
|
case '|':
|
|
case '~':
|
|
return this.consumeAMatchToken() || this.consumeADelimToken();
|
|
case '-':
|
|
return this.consumeANumericToken() || this.consumeAnIdentLikeToken() || this.consumeCDC() || this.consumeADelimToken();
|
|
case '+':
|
|
case '.':
|
|
return this.consumeANumericToken() || this.consumeADelimToken();
|
|
case '/':
|
|
return this.consumeAComment() || this.consumeADelimToken();
|
|
case '<':
|
|
return this.consumeCDO() || this.consumeADelimToken();
|
|
case undefined:
|
|
return undefined;
|
|
default:
|
|
return this.consumeAnIdentLikeToken() || this.consumeADelimToken();
|
|
}
|
|
}
|
|
|
|
private consumeADelimToken(): InputToken {
|
|
return {
|
|
type: TokenObjectType.delim,
|
|
text: this.text[this.nextInputCodePointIndex++],
|
|
};
|
|
}
|
|
|
|
private consumeAWhitespace(): InputToken {
|
|
whitespaceRegEx.lastIndex = this.nextInputCodePointIndex;
|
|
whitespaceRegEx.exec(this.text);
|
|
this.nextInputCodePointIndex = whitespaceRegEx.lastIndex;
|
|
|
|
return ' ';
|
|
}
|
|
|
|
private consumeAHashToken(): InputTokenObject {
|
|
this.nextInputCodePointIndex++;
|
|
const hashName = this.consumeAName();
|
|
if (hashName) {
|
|
return { type: TokenObjectType.hash, text: '#' + hashName.text };
|
|
}
|
|
this.nextInputCodePointIndex--;
|
|
|
|
return null;
|
|
}
|
|
|
|
private consumeCDO(): '<!--' | null {
|
|
if (this.text.substr(this.nextInputCodePointIndex, 4) === '<!--') {
|
|
this.nextInputCodePointIndex += 4;
|
|
|
|
return '<!--';
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private consumeCDC(): '-->' | null {
|
|
if (this.text.substr(this.nextInputCodePointIndex, 3) === '-->') {
|
|
this.nextInputCodePointIndex += 3;
|
|
|
|
return '-->';
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private consumeAMatchToken(): '*=' | '$=' | '|=' | '~=' | '^=' | null {
|
|
if (this.text[this.nextInputCodePointIndex + 1] === '=') {
|
|
const token = this.text.substr(this.nextInputCodePointIndex, 2);
|
|
this.nextInputCodePointIndex += 2;
|
|
|
|
return <'*=' | '$=' | '|=' | '~=' | '^='>token;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 4.3.2. Consume a numeric token
|
|
* https://www.w3.org/TR/css-syntax-3/#consume-a-numeric-token
|
|
*/
|
|
private consumeANumericToken(): InputToken {
|
|
numberRegEx.lastIndex = this.nextInputCodePointIndex;
|
|
const result = numberRegEx.exec(this.text);
|
|
if (!result) {
|
|
return null;
|
|
}
|
|
this.nextInputCodePointIndex = numberRegEx.lastIndex;
|
|
if (this.text[this.nextInputCodePointIndex] === '%') {
|
|
return { type: TokenObjectType.percentage, text: result[0] }; // TODO: Push the actual number and unit here...
|
|
}
|
|
|
|
const name = this.consumeAName();
|
|
if (name) {
|
|
return {
|
|
type: TokenObjectType.dimension,
|
|
text: result[0] + name.text,
|
|
};
|
|
}
|
|
|
|
return { type: TokenObjectType.number, text: result[0] };
|
|
}
|
|
|
|
/**
|
|
* 4.3.3. Consume an ident-like token
|
|
* https://www.w3.org/TR/css-syntax-3/#consume-an-ident-like-token
|
|
*/
|
|
private consumeAnIdentLikeToken(): InputToken {
|
|
const name = this.consumeAName();
|
|
if (!name) {
|
|
return null;
|
|
}
|
|
if (this.text[this.nextInputCodePointIndex] === '(') {
|
|
this.nextInputCodePointIndex++;
|
|
if (name.text.toLowerCase() === 'url') {
|
|
return this.consumeAURLToken();
|
|
}
|
|
|
|
return <FunctionInputToken>{
|
|
type: TokenObjectType.functionToken,
|
|
name: name.text,
|
|
text: name.text + '(',
|
|
};
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
* 4.3.4. Consume a string token
|
|
* https://www.w3.org/TR/css-syntax-3/#consume-a-string-token
|
|
*/
|
|
private consumeAStringToken(): InputTokenObject {
|
|
const char = this.text[this.nextInputCodePointIndex];
|
|
let result: RegExpExecArray;
|
|
if (char === "'") {
|
|
singleQuoteStringRegEx.lastIndex = this.nextInputCodePointIndex;
|
|
result = singleQuoteStringRegEx.exec(this.text);
|
|
if (!result) {
|
|
return null;
|
|
}
|
|
this.nextInputCodePointIndex = singleQuoteStringRegEx.lastIndex;
|
|
} else if (char === '"') {
|
|
doubleQuoteStringRegEx.lastIndex = this.nextInputCodePointIndex;
|
|
result = doubleQuoteStringRegEx.exec(this.text);
|
|
if (!result) {
|
|
return null;
|
|
}
|
|
this.nextInputCodePointIndex = doubleQuoteStringRegEx.lastIndex;
|
|
}
|
|
|
|
// TODO: Handle bad-string.
|
|
// TODO: Perform string escaping.
|
|
return { type: TokenObjectType.string, text: result[0] };
|
|
}
|
|
|
|
/**
|
|
* 4.3.5. Consume a url token
|
|
* https://www.w3.org/TR/css-syntax-3/#consume-a-url-token
|
|
*/
|
|
private consumeAURLToken(): InputToken {
|
|
const start = this.nextInputCodePointIndex - 3 /* url */ - 1; /* ( */
|
|
const urlToken: InputToken = {
|
|
type: TokenObjectType.url,
|
|
text: undefined,
|
|
};
|
|
this.consumeAWhitespace();
|
|
if (this.nextInputCodePointIndex >= this.text.length) {
|
|
return urlToken;
|
|
}
|
|
const nextInputCodePoint = this.text[this.nextInputCodePointIndex];
|
|
if (nextInputCodePoint === '"' || nextInputCodePoint === "'") {
|
|
const stringToken = this.consumeAStringToken();
|
|
// TODO: Handle bad-string.
|
|
// TODO: Set value instead.
|
|
urlToken.text = stringToken.text;
|
|
this.consumeAWhitespace();
|
|
if (this.text[this.nextInputCodePointIndex] === ')' || this.nextInputCodePointIndex >= this.text.length) {
|
|
this.nextInputCodePointIndex++;
|
|
const end = this.nextInputCodePointIndex;
|
|
urlToken.text = this.text.substring(start, end);
|
|
|
|
return urlToken;
|
|
} else {
|
|
// TODO: Handle bad-url.
|
|
return null;
|
|
}
|
|
}
|
|
|
|
while (this.nextInputCodePointIndex < this.text.length) {
|
|
const char = this.text[this.nextInputCodePointIndex++];
|
|
switch (char) {
|
|
case ')':
|
|
return urlToken;
|
|
case ' ':
|
|
case '\t':
|
|
case '\n':
|
|
case '\r':
|
|
case '\f':
|
|
this.consumeAWhitespace();
|
|
if (this.text[this.nextInputCodePointIndex] === ')') {
|
|
this.nextInputCodePointIndex++;
|
|
|
|
return urlToken;
|
|
} else {
|
|
// TODO: Bar url! Consume remnants.
|
|
return null;
|
|
}
|
|
case '"':
|
|
case "'":
|
|
// TODO: Parse error! Bar url! Consume remnants.
|
|
return null;
|
|
case '\\':
|
|
// TODO: Escape!
|
|
throw new Error('Escaping not yet supported!');
|
|
default:
|
|
// TODO: Non-printable chars - error.
|
|
urlToken.text += char;
|
|
}
|
|
}
|
|
|
|
return urlToken;
|
|
}
|
|
|
|
/**
|
|
* 4.3.11. Consume a name
|
|
* https://www.w3.org/TR/css-syntax-3/#consume-a-name
|
|
*/
|
|
private consumeAName(): InputTokenObject {
|
|
nameRegEx.lastIndex = this.nextInputCodePointIndex;
|
|
const result = nameRegEx.exec(this.text);
|
|
if (!result) {
|
|
return null;
|
|
}
|
|
this.nextInputCodePointIndex = nameRegEx.lastIndex;
|
|
|
|
// TODO: Perform string escaping.
|
|
return { type: TokenObjectType.ident, text: result[0] };
|
|
}
|
|
|
|
private consumeAtKeyword(): InputTokenObject {
|
|
this.nextInputCodePointIndex++;
|
|
const name = this.consumeAName();
|
|
if (name) {
|
|
return { type: TokenObjectType.atKeyword, text: name.text };
|
|
}
|
|
this.nextInputCodePointIndex--;
|
|
|
|
return null;
|
|
}
|
|
|
|
private consumeAComment(): InputToken {
|
|
if (this.text[this.nextInputCodePointIndex + 1] === '*') {
|
|
commentRegEx.lastIndex = this.nextInputCodePointIndex;
|
|
const result = commentRegEx.exec(this.text);
|
|
if (!result) {
|
|
return null; // TODO: Handle <bad-comment>
|
|
}
|
|
this.nextInputCodePointIndex = commentRegEx.lastIndex;
|
|
|
|
// The CSS spec tokenizer does not emmit comment tokens
|
|
return this.consumeAToken();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private reconsumeTheCurrentInputToken(currentInputToken: InputToken) {
|
|
this.reconsumedInputToken = currentInputToken;
|
|
}
|
|
|
|
/**
|
|
* 5.3.1. Parse a stylesheet
|
|
* https://www.w3.org/TR/css-syntax-3/#parse-a-stylesheet
|
|
*/
|
|
public parseAStylesheet(): Stylesheet {
|
|
this.topLevelFlag = true;
|
|
return {
|
|
rules: this.consumeAListOfRules(),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 5.4.1. Consume a list of rules
|
|
* https://www.w3.org/TR/css-syntax-3/#consume-a-list-of-rules
|
|
*/
|
|
public consumeAListOfRules(): Rule[] {
|
|
const rules: Rule[] = [];
|
|
let inputToken: InputToken;
|
|
while ((inputToken = this.consumeAToken())) {
|
|
switch (inputToken) {
|
|
case ' ':
|
|
continue;
|
|
case '<!--':
|
|
case '-->': {
|
|
if (this.topLevelFlag) {
|
|
continue;
|
|
}
|
|
this.reconsumeTheCurrentInputToken(inputToken);
|
|
const atRule = this.consumeAnAtRule();
|
|
if (atRule) {
|
|
rules.push(atRule);
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
if ((<InputTokenObject>inputToken).type === TokenObjectType.atKeyword) {
|
|
this.reconsumeTheCurrentInputToken(inputToken);
|
|
const atRule = this.consumeAnAtRule();
|
|
if (atRule) {
|
|
rules.push(atRule);
|
|
}
|
|
continue;
|
|
}
|
|
this.reconsumeTheCurrentInputToken(inputToken);
|
|
const qualifiedRule = this.consumeAQualifiedRule();
|
|
if (qualifiedRule) {
|
|
rules.push(qualifiedRule);
|
|
}
|
|
}
|
|
|
|
return rules;
|
|
}
|
|
|
|
/**
|
|
* 5.4.2. Consume an at-rule
|
|
* https://www.w3.org/TR/css-syntax-3/#consume-an-at-rule
|
|
*/
|
|
public consumeAnAtRule(): AtRule {
|
|
let inputToken = this.consumeAToken();
|
|
const atRule: AtRule = {
|
|
type: 'at-rule',
|
|
name: (<AtKeywordToken>inputToken).text,
|
|
prelude: [],
|
|
block: undefined,
|
|
};
|
|
while ((inputToken = this.consumeAToken())) {
|
|
if (inputToken === ';') {
|
|
return atRule;
|
|
} else if (inputToken === '{') {
|
|
atRule.block = this.consumeASimpleBlock(inputToken);
|
|
|
|
return atRule;
|
|
} else if ((<InputTokenObject>inputToken).type === TokenObjectType.simpleBlock && (<SimpleBlock>inputToken).associatedToken === '{') {
|
|
atRule.block = <SimpleBlock>inputToken;
|
|
|
|
return atRule;
|
|
}
|
|
this.reconsumeTheCurrentInputToken(inputToken);
|
|
const component = this.consumeAComponentValue();
|
|
if (component) {
|
|
atRule.prelude.push(component);
|
|
}
|
|
}
|
|
|
|
return atRule;
|
|
}
|
|
|
|
/**
|
|
* 5.4.3. Consume a qualified rule
|
|
* https://www.w3.org/TR/css-syntax-3/#consume-a-qualified-rule
|
|
*/
|
|
public consumeAQualifiedRule(): QualifiedRule {
|
|
const qualifiedRule: QualifiedRule = {
|
|
type: 'qualified-rule',
|
|
prelude: [],
|
|
block: undefined,
|
|
};
|
|
let inputToken: InputToken;
|
|
while ((inputToken = this.consumeAToken())) {
|
|
if (inputToken === '{') {
|
|
qualifiedRule.block = this.consumeASimpleBlock(inputToken);
|
|
|
|
return qualifiedRule;
|
|
} else if ((<InputTokenObject>inputToken).type === TokenObjectType.simpleBlock) {
|
|
const simpleBlock: SimpleBlock = <SimpleBlock>inputToken;
|
|
if (simpleBlock.associatedToken === '{') {
|
|
qualifiedRule.block = simpleBlock;
|
|
|
|
return qualifiedRule;
|
|
}
|
|
}
|
|
this.reconsumeTheCurrentInputToken(inputToken);
|
|
const componentValue = this.consumeAComponentValue();
|
|
if (componentValue) {
|
|
qualifiedRule.prelude.push(componentValue);
|
|
}
|
|
}
|
|
|
|
// TODO: This is a parse error, log parse errors!
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 5.4.6. Consume a component value
|
|
* https://www.w3.org/TR/css-syntax-3/#consume-a-component-value
|
|
*/
|
|
private consumeAComponentValue(): InputToken {
|
|
// const inputToken = this.consumeAToken();
|
|
const inputToken = this.consumeAToken();
|
|
switch (inputToken) {
|
|
case '{':
|
|
case '[':
|
|
case '(':
|
|
this.nextInputCodePointIndex++;
|
|
|
|
return this.consumeASimpleBlock(inputToken);
|
|
}
|
|
if (typeof inputToken === 'object' && inputToken.type === TokenObjectType.functionToken) {
|
|
return this.consumeAFunction((<FunctionInputToken>inputToken).name);
|
|
}
|
|
|
|
return inputToken;
|
|
}
|
|
|
|
/**
|
|
* 5.4.7. Consume a simple block
|
|
* https://www.w3.org/TR/css-syntax-3/#consume-a-simple-block
|
|
*/
|
|
private consumeASimpleBlock(associatedToken: InputToken): SimpleBlock {
|
|
const endianToken: ']' | '}' | ')' = {
|
|
'[': ']',
|
|
'{': '}',
|
|
'(': ')',
|
|
}[<any>associatedToken];
|
|
const start = this.nextInputCodePointIndex - 1;
|
|
const block: SimpleBlock = {
|
|
type: TokenObjectType.simpleBlock,
|
|
text: undefined,
|
|
associatedToken,
|
|
values: [],
|
|
};
|
|
let nextInputToken;
|
|
while ((nextInputToken = this.text[this.nextInputCodePointIndex])) {
|
|
if (nextInputToken === endianToken) {
|
|
this.nextInputCodePointIndex++;
|
|
const end = this.nextInputCodePointIndex;
|
|
block.text = this.text.substring(start, end);
|
|
|
|
return block;
|
|
}
|
|
const value = this.consumeAComponentValue();
|
|
if (value) {
|
|
block.values.push(value);
|
|
}
|
|
}
|
|
block.text = this.text.substring(start);
|
|
|
|
return block;
|
|
}
|
|
|
|
/**
|
|
* 5.4.8. Consume a function
|
|
* https://www.w3.org/TR/css-syntax-3/#consume-a-function
|
|
*/
|
|
private consumeAFunction(name: string): InputToken {
|
|
const start = this.nextInputCodePointIndex;
|
|
const funcToken: FunctionToken = {
|
|
type: TokenObjectType.function,
|
|
name,
|
|
text: undefined,
|
|
components: [],
|
|
};
|
|
do {
|
|
if (this.nextInputCodePointIndex >= this.text.length) {
|
|
funcToken.text = name + '(' + this.text.substring(start);
|
|
|
|
return funcToken;
|
|
}
|
|
const nextInputToken = this.text[this.nextInputCodePointIndex];
|
|
switch (nextInputToken) {
|
|
case ')': {
|
|
this.nextInputCodePointIndex++;
|
|
const end = this.nextInputCodePointIndex;
|
|
funcToken.text = name + '(' + this.text.substring(start, end);
|
|
|
|
return funcToken;
|
|
}
|
|
default: {
|
|
const component = this.consumeAComponentValue();
|
|
if (component) {
|
|
funcToken.components.push(component);
|
|
}
|
|
}
|
|
// TODO: Else we won't advance
|
|
}
|
|
} while (true);
|
|
}
|
|
}
|