UNPKG

@surface/custom-element

Version:

Provides support of directives and data binding on custom elements.

122 lines (121 loc) 4.35 kB
import { assert } from "@surface/core"; import Expression, { SyntaxError } from "@surface/expression"; import { getOffsetSyntaxError, parseExpression } from "./expression-parsers.js"; const stringTokens = ["\"", "'", "`"]; export default class InterpolatedExpression { constructor(source) { this.quasis = []; this.expressions = []; this.expressionEnd = 0; this.expressionStart = 0; this.index = 0; this.source = source; } get current() { return this.source[this.index]; } get eof() { return this.index == this.source.length; } static parse(source) { return new InterpolatedExpression(source).scan(); } advance() { this.index++; } collectTextFragment(start, end) { const textFragment = this.source.substring(start, end) .replace(/(?<!\\)\\{/g, "{") .replace(/\\\\{/g, "\\") .replace(/{$/, ""); this.quasis.push(Expression.templateElement(textFragment, textFragment, end >= this.source.length)); } parse(start) { try { let scaped = false; while (!this.eof && this.current != "{" || scaped) { scaped = this.current == "\\" && !scaped; if (scaped && this.source.substring(this.index, this.index + 3) == "\\\\{") { scaped = false; this.advance(); } this.advance(); } if (start == 0 || start < this.index) { this.collectTextFragment(start, this.index + 1); } if (!this.eof) { this.expressionStart = this.index + 1; if (this.scanBalance()) { this.expressionEnd = this.index - 1; const expression = parseExpression(this.source.substring(this.expressionStart, this.expressionEnd)); this.expressions.push(expression); if (!this.eof) { this.parse(this.index); } else if (this.expressions.length > 0) { this.collectTextFragment(this.index, this.index); } } else { throw new SyntaxError("Unexpected end of expression", (this.source.match(/\n/g)?.length ?? 0) + 1, this.source.length - 1, this.source.length); } } } catch (error) { assert(error instanceof SyntaxError); throw getOffsetSyntaxError(this.source, this.source.substring(this.expressionStart, this.expressionEnd), error); } } scan() { this.parse(0); return Expression.template(this.quasis, this.expressions); } scanBalance() { let stack = 0; do { if (stringTokens.includes(this.current)) { if (!this.scanString()) { return false; } } if (this.current == "{") { stack++; } if (this.current == "}") { stack--; } this.advance(); } while (!this.eof && stack > 0); return stack == 0; } scanString() { const token = this.current; this.advance(); if (token == this.current) { return true; } let scaped = false; if (token == "`") { do { scaped = this.current == "\\" && !scaped; if (!scaped && this.source.substring(this.index, this.index + 2) == "${") { this.advance(); if (!this.scanBalance()) { return false; } } else { this.advance(); } } while (!this.eof && this.current != token || scaped); } else { do { scaped = this.current == "\\" && !scaped; this.advance(); } while (!this.eof && this.current != token || scaped); } return this.current == token; } }