replace regex based directive scanner
This commit is contained in:
107
render.ts
107
render.ts
@@ -13,7 +13,6 @@ import { TextDecoder } from "node:util"
|
||||
|
||||
const IF_PATH_RE = /^<@if\((.+?)\)>(.*)$/
|
||||
const VAR_RE = /<@var\(context\.(.+?)(?::(\w+))?\)>/g
|
||||
const DIRECTIVE_RE = /<@(if|elseif|else|endif)(?:\((.+?)\))?>/g
|
||||
const STRING_COMPARE_RE = /^(eq|neq)\(context\.(.+?),\s*"(.*)"\)$/
|
||||
const PATH_RE = /^context\.(.+)$/
|
||||
|
||||
@@ -66,8 +65,55 @@ type RenderState = {
|
||||
manifest?: ReverseMapManifest
|
||||
}
|
||||
|
||||
function splitArgs(args: string): string[] {
|
||||
const result: string[] = []
|
||||
let current = ""
|
||||
let depth = 0
|
||||
let inString = false
|
||||
let escaped = false
|
||||
|
||||
for (const char of args) {
|
||||
if (escaped) {
|
||||
current += char
|
||||
escaped = false
|
||||
continue
|
||||
}
|
||||
if (char === "\\") {
|
||||
current += char
|
||||
escaped = true
|
||||
continue
|
||||
}
|
||||
if (char === "\"") {
|
||||
current += char
|
||||
inString = !inString
|
||||
continue
|
||||
}
|
||||
if (!inString && char === "(") depth += 1
|
||||
if (!inString && char === ")") depth -= 1
|
||||
if (!inString && depth === 0 && char === ",") {
|
||||
result.push(current.trim())
|
||||
current = ""
|
||||
continue
|
||||
}
|
||||
current += char
|
||||
}
|
||||
|
||||
if (current.trim() !== "") result.push(current.trim())
|
||||
return result
|
||||
}
|
||||
|
||||
function evalCondition(expr: string | undefined, context: Record<string, unknown>): boolean {
|
||||
if (!expr) throw new Error("Missing condition expression")
|
||||
for (const operator of ["and", "or"] as const) {
|
||||
const prefix = `${operator}(`
|
||||
if (expr.startsWith(prefix) && expr.endsWith(")")) {
|
||||
const args = splitArgs(expr.slice(prefix.length, -1))
|
||||
if (args.length === 0) throw new Error(`Invalid condition expression: ${expr}`)
|
||||
return operator === "and"
|
||||
? args.every(arg => evalCondition(arg, context))
|
||||
: args.some(arg => evalCondition(arg, context))
|
||||
}
|
||||
}
|
||||
const stringCompareMatch = expr.match(STRING_COMPARE_RE)
|
||||
if (stringCompareMatch) {
|
||||
const result = resolveContext(context, stringCompareMatch[2]!) === stringCompareMatch[3]
|
||||
@@ -209,6 +255,41 @@ type DirectiveToken = {
|
||||
end: number
|
||||
}
|
||||
|
||||
function readDirectiveCondition(text: string, start: number): { condition: string; end: number } | null {
|
||||
let depth = 0
|
||||
let inString = false
|
||||
let escaped = false
|
||||
|
||||
for (let i = start; i < text.length; i++) {
|
||||
const char = text[i]!
|
||||
if (escaped) {
|
||||
escaped = false
|
||||
continue
|
||||
}
|
||||
if (char === "\\") {
|
||||
escaped = true
|
||||
continue
|
||||
}
|
||||
if (char === "\"") {
|
||||
inString = !inString
|
||||
continue
|
||||
}
|
||||
if (inString) continue
|
||||
if (char === "(") {
|
||||
depth += 1
|
||||
continue
|
||||
}
|
||||
if (char === ")") {
|
||||
depth -= 1
|
||||
if (depth === 0 && text[i + 1] === ">") {
|
||||
return { condition: text.slice(start + 1, i), end: i + 2 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
type TextNode = {
|
||||
type: "text"
|
||||
text: string
|
||||
@@ -246,12 +327,24 @@ type ConditionalRender = {
|
||||
}
|
||||
|
||||
function getDirectiveTokens(content: string): DirectiveToken[] {
|
||||
return Array.from(content.matchAll(DIRECTIVE_RE), match => ({
|
||||
type: match[1] as DirectiveToken["type"],
|
||||
condition: match[2],
|
||||
index: match.index!,
|
||||
end: match.index! + match[0].length,
|
||||
}))
|
||||
const tokens: DirectiveToken[] = []
|
||||
for (let i = 0; i < content.length; i++) {
|
||||
if (content[i] !== "<" || content[i + 1] !== "@") continue
|
||||
const rest = content.slice(i + 2)
|
||||
const type = ["elseif", "endif", "else", "if"].find(name => rest.startsWith(name)) as DirectiveToken["type"] | undefined
|
||||
if (!type) continue
|
||||
const afterName = i + 2 + type.length
|
||||
if ((type === "if" || type === "elseif") && content[afterName] === "(") {
|
||||
const parsed = readDirectiveCondition(content, afterName)
|
||||
if (!parsed) continue
|
||||
tokens.push({ type, condition: parsed.condition, index: i, end: parsed.end })
|
||||
i = parsed.end - 1
|
||||
} else if ((type === "else" || type === "endif") && content[afterName] === ">") {
|
||||
tokens.push({ type, index: i, end: afterName + 1 })
|
||||
i = afterName
|
||||
}
|
||||
}
|
||||
return tokens
|
||||
}
|
||||
|
||||
function parseNodes(
|
||||
|
||||
Reference in New Issue
Block a user