From 8e7c64376aec2197514b73aab0211a214c43bb4b Mon Sep 17 00:00:00 2001 From: Dylan Piercey Date: Wed, 16 Mar 2022 15:30:07 -0700 Subject: [PATCH] perf: optimize expression state to use charcodes --- src/states/ATTRIBUTE.ts | 53 +++++++++++++++++++++++++---------- src/states/EXPRESSION.ts | 38 ++++++++++++++----------- src/states/INLINE_SCRIPT.ts | 2 +- src/states/OPEN_TAG.ts | 24 +++++++++++++--- src/states/PLACEHOLDER.ts | 2 +- src/states/TAG_NAME.ts | 2 +- src/states/TEMPLATE_STRING.ts | 2 +- src/util/util.ts | 8 +++--- 8 files changed, 87 insertions(+), 44 deletions(-) diff --git a/src/states/ATTRIBUTE.ts b/src/states/ATTRIBUTE.ts index 1d0365ad..a50ec3d1 100644 --- a/src/states/ATTRIBUTE.ts +++ b/src/states/ATTRIBUTE.ts @@ -24,6 +24,36 @@ export interface AttrMeta extends Range { bound: boolean; } +const HTML_VALUE_TERMINATORS = [ + CODE.CLOSE_ANGLE_BRACKET, + CODE.COMMA, + [CODE.FORWARD_SLASH, CODE.CLOSE_ANGLE_BRACKET], +]; + +const CONCISE_VALUE_TERMINATORS = [ + CODE.CLOSE_SQUARE_BRACKET, + CODE.SEMICOLON, + CODE.COMMA, +]; + +const HTML_NAME_TERMINATORS = [ + CODE.CLOSE_ANGLE_BRACKET, + CODE.COMMA, + CODE.OPEN_PAREN, + CODE.EQUAL, + [CODE.COLON, CODE.EQUAL], + [CODE.FORWARD_SLASH, CODE.CLOSE_ANGLE_BRACKET], +]; + +const CONCISE_NAME_TERMINATORS = [ + CODE.CLOSE_SQUARE_BRACKET, + CODE.SEMICOLON, + CODE.EQUAL, + CODE.COMMA, + CODE.OPEN_PAREN, + [CODE.COLON, CODE.EQUAL], +]; + // We enter STATE.ATTRIBUTE when we see a non-whitespace // character after reading the tag name export const ATTRIBUTE: StateDefinition = { @@ -73,18 +103,16 @@ export const ATTRIBUTE: StateDefinition = { attr.state = ATTR_STATE.VALUE; const expr = this.enterState(STATE.EXPRESSION); expr.terminatedByWhitespace = true; - expr.terminator = [ - this.isConcise ? "]" : "/>", - this.isConcise ? ";" : ">", - ",", - ]; + expr.terminator = this.isConcise + ? CONCISE_VALUE_TERMINATORS + : HTML_VALUE_TERMINATORS; this.rewind(1); } else if (code === CODE.OPEN_PAREN) { ensureAttrName(this, attr); attr.state = ATTR_STATE.ARGUMENT; this.skip(1); // skip ( - this.enterState(STATE.EXPRESSION).terminator = ")"; + this.enterState(STATE.EXPRESSION).terminator = CODE.CLOSE_PAREN; this.rewind(1); } else if (code === CODE.OPEN_CURLY_BRACE && attr.args) { ensureAttrName(this, attr); @@ -92,21 +120,16 @@ export const ATTRIBUTE: StateDefinition = { this.skip(1); // skip { const expr = this.enterState(STATE.EXPRESSION); expr.terminatedByWhitespace = false; - expr.terminator = "}"; + expr.terminator = CODE.CLOSE_CURLY_BRACE; this.rewind(1); } else if (attr.state === undefined) { attr.state = ATTR_STATE.NAME; const expr = this.enterState(STATE.EXPRESSION); expr.terminatedByWhitespace = true; expr.skipOperators = true; - expr.terminator = [ - this.isConcise ? "]" : "/>", - this.isConcise ? ";" : ">", - ":=", - "=", - ",", - "(", - ]; + expr.terminator = this.isConcise + ? CONCISE_NAME_TERMINATORS + : HTML_NAME_TERMINATORS; this.rewind(1); } else { this.exitState(); diff --git a/src/states/EXPRESSION.ts b/src/states/EXPRESSION.ts index e8811356..9feba7ca 100644 --- a/src/states/EXPRESSION.ts +++ b/src/states/EXPRESSION.ts @@ -9,7 +9,7 @@ import { export interface ExpressionMeta extends Range { groupStack: number[]; - terminator?: string | string[]; + terminator?: number | (number | number[])[]; skipOperators: boolean; terminatedByEOL: boolean; terminatedByWhitespace: boolean; @@ -66,9 +66,13 @@ export const EXPRESSION: StateDefinition = { return; } + const { terminator } = expression; + if ( - expression.terminator && - checkForTerminator(this, expression.terminator) + terminator && + (typeof terminator === "number" + ? terminator === code + : checkForTerminators(this, code, terminator)) ) { this.exitState(); return; @@ -279,21 +283,21 @@ function buildOperatorPattern(isConcise: boolean) { return new RegExp(`${lookAheadPattern}|${lookBehindPattern}`, "y"); } -function checkForTerminator(parser: Parser, terminator: string | string[]) { - if (typeof terminator === "string") { - if (parser.data[parser.pos] === terminator) { - return true; - } else if (terminator.length > 1) { - for (let i = 0; i < terminator.length; i++) { - if (parser.data[parser.pos + i] !== terminator[i]) { - return false; +function checkForTerminators( + parser: Parser, + code: number, + terminators: (number | number[])[] +) { + outer: for (const terminator of terminators) { + if (typeof terminator === "number") { + if (code === terminator) return true; + } else { + if (terminator[0] === code) { + for (let i = terminator.length; i-- > 1; ) { + if (parser.data.charCodeAt(parser.pos + i) !== terminator[i]) + continue outer; } - } - return true; - } - } else { - for (let i = 0; i < terminator.length; i++) { - if (checkForTerminator(parser, terminator[i])) { + return true; } } diff --git a/src/states/INLINE_SCRIPT.ts b/src/states/INLINE_SCRIPT.ts index caf66eb8..417f6184 100644 --- a/src/states/INLINE_SCRIPT.ts +++ b/src/states/INLINE_SCRIPT.ts @@ -40,7 +40,7 @@ export const INLINE_SCRIPT: StateDefinition = { inlineScript.block = true; this.skip(1); // skip { const expr = this.enterState(STATE.EXPRESSION); - expr.terminator = "}"; + expr.terminator = CODE.CLOSE_CURLY_BRACE; expr.skipOperators = true; this.rewind(1); } else { diff --git a/src/states/OPEN_TAG.ts b/src/states/OPEN_TAG.ts index 75fc14a6..cdba640a 100644 --- a/src/states/OPEN_TAG.ts +++ b/src/states/OPEN_TAG.ts @@ -31,6 +31,22 @@ export interface OpenTagMeta extends Range { parentTag: OpenTagMeta | undefined; } const PARSED_TEXT_TAGS = ["script", "style", "textarea", "html-comment"]; +const CONCISE_TAG_VAR_TERMINATORS = [ + CODE.SEMICOLON, + CODE.OPEN_PAREN, + CODE.PIPE, + CODE.EQUAL, + [CODE.COLON, CODE.EQUAL], +]; + +const HTML_TAG_VAR_TERMINATORS = [ + CODE.CLOSE_ANGLE_BRACKET, + CODE.OPEN_PAREN, + CODE.PIPE, + CODE.EQUAL, + [CODE.COLON, CODE.EQUAL], + [CODE.FORWARD_SLASH, CODE.CLOSE_ANGLE_BRACKET], +]; export const OPEN_TAG: StateDefinition = { name: "OPEN_TAG", @@ -275,8 +291,8 @@ export const OPEN_TAG: StateDefinition = { expr.skipOperators = true; expr.terminatedByWhitespace = true; expr.terminator = this.isConcise - ? [";", "(", "|", "=", ":="] - : [">", "/>", "(", "|", "=", ":="]; + ? CONCISE_TAG_VAR_TERMINATORS + : HTML_TAG_VAR_TERMINATORS; this.rewind(1); } else if (code === CODE.OPEN_PAREN && !tag.hasAttrs) { if (tag.hasArgs) { @@ -291,14 +307,14 @@ export const OPEN_TAG: StateDefinition = { this.skip(1); // skip ( const expr = this.enterState(STATE.EXPRESSION); expr.skipOperators = true; - expr.terminator = ")"; + expr.terminator = CODE.CLOSE_PAREN; this.rewind(1); } else if (code === CODE.PIPE && !tag.hasAttrs) { tag.state = TAG_STATE.PARAMS; this.skip(1); // skip | const expr = this.enterState(STATE.EXPRESSION); expr.skipOperators = true; - expr.terminator = "|"; + expr.terminator = CODE.PIPE; this.rewind(1); } else { if (tag.tagName) { diff --git a/src/states/PLACEHOLDER.ts b/src/states/PLACEHOLDER.ts index b9731c07..d45c1551 100644 --- a/src/states/PLACEHOLDER.ts +++ b/src/states/PLACEHOLDER.ts @@ -84,7 +84,7 @@ export function checkForPlaceholder(parser: Parser, code: number) { parser.endText(); parser.enterState(PLACEHOLDER).escape = escape; parser.skip(escape ? 2 : 3); // skip ${ or $!{ - parser.enterState(STATE.EXPRESSION).terminator = "}"; + parser.enterState(STATE.EXPRESSION).terminator = CODE.CLOSE_CURLY_BRACE; parser.rewind(1); return true; } diff --git a/src/states/TAG_NAME.ts b/src/states/TAG_NAME.ts index 3fcb3f54..e06a1ccc 100644 --- a/src/states/TAG_NAME.ts +++ b/src/states/TAG_NAME.ts @@ -127,7 +127,7 @@ export const TAG_NAME: StateDefinition = { this.lookAtCharCodeAhead(1) === CODE.OPEN_CURLY_BRACE ) { this.skip(2); // skip ${ - this.enterState(STATE.EXPRESSION).terminator = "}"; + this.enterState(STATE.EXPRESSION).terminator = CODE.CLOSE_CURLY_BRACE; this.rewind(1); } else if ( isWhitespaceCode(code) || diff --git a/src/states/TEMPLATE_STRING.ts b/src/states/TEMPLATE_STRING.ts index 1ec32f2f..9ba9aff4 100644 --- a/src/states/TEMPLATE_STRING.ts +++ b/src/states/TEMPLATE_STRING.ts @@ -20,7 +20,7 @@ export const TEMPLATE_STRING: StateDefinition = { this.skip(1); // skip { const expr = this.enterState(STATE.EXPRESSION); expr.skipOperators = true; - expr.terminator = "}"; + expr.terminator = CODE.CLOSE_CURLY_BRACE; } else { if (code === CODE.BACK_SLASH) { this.skip(1); // skip \ diff --git a/src/util/util.ts b/src/util/util.ts index b2a3dc11..f701a19c 100644 --- a/src/util/util.ts +++ b/src/util/util.ts @@ -21,7 +21,7 @@ export function getLoc(lines: number[], range: Range): Location { export function getPos( lines: number[], startLine: number, - pos: number + index: number ): Position { let max = lines.length - 1; let line = startLine; @@ -29,7 +29,7 @@ export function getPos( while (line < max) { const mid = (1 + line + max) >>> 1; - if (lines[mid] <= pos) { + if (lines[mid] <= index) { line = mid; } else { max = mid - 1; @@ -38,12 +38,12 @@ export function getPos( return { line: line + 1, - column: line === 0 ? pos + 1 : pos - lines[line], + column: index - lines[line], }; } export function getLines(src: string) { - const lines = [0]; + const lines = [-1]; for (let i = 0; i < src.length; i++) { if (src.charCodeAt(i) === CODE.NEWLINE) { lines.push(i);