diff --git a/packages/parser/src/index.ts b/packages/parser/src/index.ts index 9f18e9e..cf2a416 100644 --- a/packages/parser/src/index.ts +++ b/packages/parser/src/index.ts @@ -253,37 +253,11 @@ export function scanInterpolations(text: string): InterpolationData[] { const fullStart = i; let j = i + 2; - let depth = 1; - - while (j < text.length && depth > 0) { - if (text[j - 1] !== "\\" && text[j] === "<" && text[j + 1] === "<") { - depth++; - j += 2; - continue; - } + const candidates: number[] = []; + while (j < text.length) { if (text[j - 1] !== "\\" && text[j] === ">" && text[j + 1] === ">") { - depth--; - if (depth === 0) { - const rawExpression = text.slice(i + 2, j); - const leadingWhitespace = rawExpression.match(/^\s*/)?.[0].length ?? 0; - const trailingWhitespace = rawExpression.match(/\s*$/)?.[0].length ?? 0; - const trimmedRawExpression = rawExpression.slice( - leadingWhitespace, - rawExpression.length - trailingWhitespace, - ); - - interpolations.push({ - expression: transformOutputTemplate(trimmedRawExpression), - rawExpression: trimmedRawExpression, - sourceStart: i + 2 + leadingWhitespace, - sourceEnd: j - trailingWhitespace, - fullStart, - fullEnd: j + 2, - }); - i = j + 2; - break; - } + candidates.push(j); j += 2; continue; } @@ -291,14 +265,54 @@ export function scanInterpolations(text: string): InterpolationData[] { j++; } - if (depth > 0) { + const expressionEnd = findBestInterpolationEnd(text, candidates); + + if (expressionEnd === undefined) { i++; + continue; } + + const rawExpression = text.slice(i + 2, expressionEnd); + const leadingWhitespace = rawExpression.match(/^\s*/)?.[0].length ?? 0; + const trailingWhitespace = rawExpression.match(/\s*$/)?.[0].length ?? 0; + const trimmedRawExpression = rawExpression.slice( + leadingWhitespace, + rawExpression.length - trailingWhitespace, + ); + + interpolations.push({ + expression: unescapeEscapedDelimiters(trimmedRawExpression), + rawExpression: trimmedRawExpression, + sourceStart: i + 2 + leadingWhitespace, + sourceEnd: expressionEnd - trailingWhitespace, + fullStart, + fullEnd: expressionEnd + 2, + }); + i = expressionEnd + 2; } return interpolations; } +function findBestInterpolationEnd(text: string, candidates: number[]): number | undefined { + for (let i = 0; i < candidates.length; i++) { + const candidate = candidates[i]; + const hasLaterCandidate = i < candidates.length - 1; + + if (hasLaterCandidate && isSpacedShiftOperator(text, candidate)) { + continue; + } + + return candidate; + } + + return undefined; +} + +function isSpacedShiftOperator(text: string, offset: number): boolean { + return /\s/.test(text[offset - 1] ?? "") && /\s/.test(text[offset + 2] ?? ""); +} + export function transformOutputTemplate(output: string): string { const interpolations = scanInterpolations(output); let transformed = ""; @@ -336,6 +350,24 @@ function escapeOutputText(text: string): string { return transformed; } +function unescapeEscapedDelimiters(text: string): string { + let transformed = ""; + + for (let i = 0; i < text.length; i++) { + if (text[i] === "\\" && text[i + 1] === ">") { + transformed += ">"; + i++; + } else if (text[i] === "\\" && text[i + 1] === "<") { + transformed += "<"; + i++; + } else { + transformed += text[i]; + } + } + + return transformed; +} + function tokenToSource(token: RootToken): string { if (token.type === "TagOpenStart") { return `<${token.attributes.name}`; diff --git a/packages/parser/tests/output-transformer.spec.ts b/packages/parser/tests/output-transformer.spec.ts index 6b2c2f3..211776b 100644 --- a/packages/parser/tests/output-transformer.spec.ts +++ b/packages/parser/tests/output-transformer.spec.ts @@ -6,3 +6,9 @@ test("transformOutputTemplate", () => { const cleaned = transformOutputTemplate(source); expect(cleaned).toEqual('const someStr = \\`
${val ? ">>" : "<<"}
\\`;'); }); + +test("transformOutputTemplate allows shift operators in interpolations", () => { + const source = "<< 1 << 2 >> 3 >>"; + const cleaned = transformOutputTemplate(source); + expect(cleaned).toEqual("${1 << 2 >> 3}"); +}); diff --git a/packages/vscode/syntaxes/tempblot.tmLanguage.json b/packages/vscode/syntaxes/tempblot.tmLanguage.json index 5f4c776..56d24b4 100644 --- a/packages/vscode/syntaxes/tempblot.tmLanguage.json +++ b/packages/vscode/syntaxes/tempblot.tmLanguage.json @@ -837,7 +837,7 @@ "name": "punctuation.definition.interpolation.begin.tempblot" } }, - "end": "\\s*(>>)", + "end": "\\s*(>>(?![^\\n]*>>))", "endCaptures": { "1": { "name": "punctuation.definition.interpolation.end.tempblot" @@ -845,6 +845,10 @@ }, "name": "meta.interpolation.tempblot", "patterns": [ + { + "match": "(?<=\\s)(<<|>>)(?=\\s)", + "name": "keyword.operator.bitwise.shift.ts" + }, { "include": "source.ts" }