UNPKG

mlld

Version:

mlld: llm scripting language

1,453 lines (1,448 loc) 4 MB
'use strict'; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __esm = (fn, res) => function __init() { return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; }; var __commonJS = (cb, mod) => function __require() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. __defProp(target, "default", { value: mod, enumerable: true }) , mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); // core/utils/locationFormatter.ts function formatLocation(location) { if (!location) { return { display: "unknown location" }; } if ("filePath" in location && location.filePath) { const parts = []; if (location.filePath) { parts.push(location.filePath); } if (location.line !== void 0) { if (location.column !== void 0) { parts.push(`${location.line}:${location.column}`); } else { parts.push(`line ${location.line}`); } } return { display: parts.join(":"), file: location.filePath, line: location.line, column: location.column }; } if ("line" in location && location.line !== void 0) { const parts = []; if (location.column !== void 0) { parts.push(`line ${location.line}, column ${location.column}`); } else { parts.push(`line ${location.line}`); } return { display: parts.join(""), line: location.line, column: location.column }; } return { display: "unknown location" }; } function formatLocationForError(location) { return formatLocation(location).display; } var init_locationFormatter = __esm({ "core/utils/locationFormatter.ts"() { __name(formatLocation, "formatLocation"); __name(formatLocationForError, "formatLocationForError"); } }); // core/errors/MlldError.ts var ErrorSeverity, _MlldError, MlldError; var init_MlldError = __esm({ "core/errors/MlldError.ts"() { init_locationFormatter(); ErrorSeverity = /* @__PURE__ */ function(ErrorSeverity2) { ErrorSeverity2["Recoverable"] = "recoverable"; ErrorSeverity2["Fatal"] = "fatal"; ErrorSeverity2["Info"] = "info"; ErrorSeverity2["Warning"] = "warning"; return ErrorSeverity2; }({}); _MlldError = class _MlldError extends Error { constructor(message, options) { super(message, { cause: options.cause }); /** A unique code identifying the type of error */ __publicField(this, "code"); /** The severity level of the error */ __publicField(this, "severity"); /** Additional context-specific details about the error */ __publicField(this, "details"); /** Optional source location where the error occurred */ __publicField(this, "sourceLocation"); /** Optional environment for source access */ __publicField(this, "env"); this.name = this.constructor.name; this.code = options.code; this.severity = options.severity; this.details = options.details; this.sourceLocation = options.sourceLocation; this.env = options.env; if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); } } /** * Determines if the error represents a condition that could potentially be * treated as a warning rather than a fatal error, based on its severity. * Recoverable errors and explicit warnings can potentially be warnings. * * @returns {boolean} True if the error severity allows it to be a warning, false otherwise. */ canBeWarning() { return this.severity === "recoverable" || this.severity === "warning"; } /** * Get source context for error display */ getSourceContext() { if (!this.sourceLocation || !this.env) { return void 0; } const filePath = this.sourceLocation.filePath; if (!filePath) { return void 0; } const source = this.env.getSource(filePath); if (!source || !this.sourceLocation.line) { return void 0; } return this.formatSourceContext(source); } /** * Format source context with visual indicators */ formatSourceContext(source) { const lines = source.split("\n"); const lineNum = this.sourceLocation.line - 1; if (lineNum < 0 || lineNum >= lines.length) { return ""; } lines[lineNum]; const column = this.sourceLocation.column || 1; const pointer = " ".repeat(column - 1) + "^"; const contextStart = Math.max(0, lineNum - 2); const contextEnd = Math.min(lines.length - 1, lineNum + 2); let result = ""; for (let i = contextStart; i <= contextEnd; i++) { const lineNumber = String(i + 1).padStart(4, " "); const marker = i === lineNum ? ">" : " "; result += `${marker} ${lineNumber} | ${lines[i]} `; if (i === lineNum && this.sourceLocation.column) { result += ` | ${pointer} `; } } return result; } /** * Provides a string representation including code and severity. */ toString() { let result = `[${this.code}] ${this.message}`; if (this.sourceLocation) { result += ` at ${formatLocationForError(this.sourceLocation)}`; } result += ` (Severity: ${this.severity})`; const sourceContext = this.getSourceContext(); if (sourceContext) { result += "\n\n" + sourceContext; } return result; } /** * Serializes the error to JSON with formatted location string. */ toJSON() { const result = { name: this.name, message: this.message, code: this.code, severity: this.severity }; if (this.details) { result.details = this.details; } if (this.sourceLocation) { result.sourceLocation = formatLocationForError(this.sourceLocation); } return result; } }; __name(_MlldError, "MlldError"); MlldError = _MlldError; } }); // grammar/generated/parser/grammar-core.js var import_crypto, acorn, NodeType, DirectiveKind, warningCollector, helpers; var init_grammar_core = __esm({ "grammar/generated/parser/grammar-core.js"() { import_crypto = require("crypto"); acorn = __toESM(require("acorn")); NodeType = { Text: "Text", Comment: "Comment", CodeFence: "CodeFence", MlldRunBlock: "MlldRunBlock", VariableReference: "VariableReference", Directive: "Directive", PathSeparator: "PathSeparator", DotSeparator: "DotSeparator", Literal: "Literal", SectionMarker: "SectionMarker", Error: "Error", Newline: "Newline", StringLiteral: "StringLiteral", Frontmatter: "Frontmatter", CommandBase: "CommandBase", Parameter: "Parameter", ExecInvocation: "ExecInvocation", CommandReference: "CommandReference", FileReference: "FileReference", BinaryExpression: "BinaryExpression", TernaryExpression: "TernaryExpression", UnaryExpression: "UnaryExpression", WhenExpression: "WhenExpression" }; DirectiveKind = { run: "run", var: "var", show: "show", stream: "stream", exe: "exe", for: "for", path: "path", import: "import", export: "export", output: "output", append: "append", when: "when", guard: "guard", // NO deprecated entries - clean break! needs: "needs", wants: "wants", policy: "policy", while: "while" }; warningCollector = null; helpers = { debug(msg, ...args) { if (process.env.DEBUG_MLLD_GRAMMAR) console.log("[DEBUG GRAMMAR]", msg, ...args); }, warn(message, suggestion, loc, code) { const warning = { message, ...suggestion ? { suggestion } : {}, ...loc ? { location: loc } : {}, ...code ? { code } : {} }; if (warningCollector) { try { warningCollector(warning); return warning; } catch { } } try { console.warn(`[mlld grammar warning] ${warning.message}`); } catch { } return warning; }, setWarningCollector(collector) { if (!collector) { warningCollector = null; return; } if (Array.isArray(collector)) { warningCollector = /* @__PURE__ */ __name((warning) => { collector.push(warning); }, "warningCollector"); return; } warningCollector = collector; }, clearWarningCollector() { warningCollector = null; }, isExecutableReference(ref) { if (!ref) return false; if (ref.type === "ExecInvocation") return true; if (ref.type === "FieldAccessExec") return true; if (ref.arguments !== void 0 && ref.arguments !== null) return true; if (ref.hasParentheses === true) return true; return false; }, isLogicalLineStart(input, pos) { if (pos === 0) return true; let i = pos - 1; while (i >= 0 && " \r".includes(input[i])) i--; return i < 0 || input[i] === "\n"; }, // Context Detection System - Core Helper Methods // --------------------------------------------- /** * Determines if the current position represents a directive context. * A directive context requires: * 1. Logical line start * 2. Optional leading slash * 3. Followed by a directive keyword */ isDirectiveContext(input, pos) { if (!this.isLogicalLineStart(input, pos)) return false; let cursor = pos; if (input[cursor] === "/") cursor++; const directiveKeywords = [ ...Object.keys(DirectiveKind), "log" ]; for (const keyword of directiveKeywords) { const end = cursor + keyword.length; if (end > input.length) continue; const potentialKeyword = input.substring(cursor, end); if (potentialKeyword !== keyword) continue; if (end === input.length) return true; const nextChar = input[end]; if (" \r\n".includes(nextChar)) return true; } return false; }, /** * Legacy helper retained for compatibility. * Delegates to isDirectiveContext but requires the slash prefix. */ isSlashDirectiveContext(input, pos) { return input[pos] === "/" && this.isDirectiveContext(input, pos); }, /** * Determines if the current position represents a variable reference context * A variable context requires: * 1. @ symbol NOT at logical line start, or * 2. @ at line start but NOT followed by directive keyword */ isAtVariableContext(input, pos) { if (input[pos] !== "@") return false; if (this.isDirectiveContext(input, pos)) return false; return true; }, /** * DEPRECATED: RHS slashes are no longer supported * Keeping for reference but this should not be used * @deprecated */ isRHSContext(input, pos) { return false; }, /** * Determines if the current position represents plain text context * Plain text is any context that isn't a directive, variable, or RHS */ isPlainTextContext(input, pos) { return !this.isDirectiveContext(input, pos) && !this.isAtVariableContext(input, pos); }, /** * Determines if the current position is within a run code block context * This is used to identify language + code block patterns */ isInRunCodeBlockContext(input, pos) { return false; }, createNode(type, props) { if (!props.location && process.env.DEBUG_MLLD_GRAMMAR) { console.warn(`WARNING: Creating ${type} node without location data`); if (process.env.DEBUG_MLLD_GRAMMAR_TRACE) { console.trace(); } } return Object.freeze({ type, nodeId: (0, import_crypto.randomUUID)(), location: props.location, ...props }); }, createDirective(kind, data) { return this.createNode(NodeType.Directive, { directive: { kind, ...data } }); }, // New method for creating directives with the updated structure createStructuredDirective(kind, subtype, values, raw, meta, locationData, source = null) { return this.createNode(NodeType.Directive, { kind, subtype, source, values, raw, meta, location: locationData }); }, createVariableReferenceNode(valueType, data, location) { if (!location) { throw new Error(`Location is required for createVariableReferenceNode (valueType: ${valueType}, identifier: ${data.identifier || "unknown"})`); } return this.createNode(NodeType.VariableReference, { valueType, ...data, location }); }, normalizePathVar(id) { return id; }, validateRunContent: /* @__PURE__ */ __name(() => true, "validateRunContent"), validateDefineContent: /* @__PURE__ */ __name(() => true, "validateDefineContent"), validatePath(pathParts, directiveKind) { const raw = this.reconstructRawString(pathParts).trim(); let hasVariables = false; if (pathParts && pathParts.length > 0) { for (const node of pathParts) { if (node.type === NodeType.VariableReference) { hasVariables = true; } } } const finalFlags = { hasVariables }; const result = { raw, values: pathParts, ...finalFlags }; this.debug("PATH", "validatePath final result:", JSON.stringify(result, null, 2)); return result; }, getImportSubtype(list) { if (!list) return "importAll"; if (list.length === 0) return "importAll"; if (list.length === 1 && list[0].name === "*") return "importAll"; return "importSelected"; }, trace(pos, reason) { }, reconstructRawString(nodes) { if (!Array.isArray(nodes)) { if (nodes && typeof nodes === "object") { if (nodes.type === NodeType.Text) return nodes.content || ""; if (nodes.type === NodeType.VariableReference) { const varId = nodes.identifier; const valueType = nodes.valueType; const fields = nodes.fields || []; let fieldPath = ""; for (const field of fields) { if (field.type === "field" || field.type === "dot") { fieldPath += `.${field.name || field.value}`; } else if (field.type === "array") { fieldPath += `[${field.index}]`; } } if (valueType === "varInterpolation") { return `{{${varId}${fieldPath}}}`; } else if (valueType === "varIdentifier") { return `@${varId}${fieldPath}`; } else { return `{{${varId}${fieldPath}}}`; } } if (nodes.type === "ConditionalStringFragment") { const conditionRaw = this.reconstructRawString(nodes.condition); const contentRaw = this.reconstructRawString(nodes.content || []); return `${conditionRaw}?"${contentRaw}"`; } if (nodes.type === "ConditionalTemplateSnippet") { const conditionRaw = this.reconstructRawString(nodes.condition); const contentRaw = this.reconstructRawString(nodes.content || []); return `${conditionRaw}?\`${contentRaw}\``; } } return String(nodes || ""); } let raw = ""; for (const node of nodes) { if (!node) continue; if (node.type === NodeType.Text) { raw += node.content || ""; } else if (node.type === NodeType.VariableReference) { const varId = node.identifier; const valueType = node.valueType; const fields = node.fields || []; let fieldPath = ""; for (const field of fields) { if (field.type === "field" || field.type === "dot") { fieldPath += `.${field.name || field.value}`; } else if (field.type === "array") { fieldPath += `[${field.index}]`; } } if (valueType === "varInterpolation") { raw += `{{${varId}${fieldPath}}}`; } else if (valueType === "varIdentifier") { raw += `@${varId}${fieldPath}`; } else { raw += `{{${varId}${fieldPath}}}`; } } else if (node.type === NodeType.PathSeparator) { raw += node.value || ""; } else if (node.type === NodeType.SectionMarker) { raw += node.value || ""; } else if (node.type === NodeType.StringLiteral) { raw += node.value || ""; } else if (node.type === "ConditionalStringFragment") { const conditionRaw = this.reconstructRawString(node.condition); const contentRaw = this.reconstructRawString(node.content || []); raw += `${conditionRaw}?"${contentRaw}"`; } else if (node.type === "ConditionalTemplateSnippet") { const conditionRaw = this.reconstructRawString(node.condition); const contentRaw = this.reconstructRawString(node.content || []); raw += `${conditionRaw}?\`${contentRaw}\``; } else if (typeof node === "string") { raw += node; } else { raw += node.content || node.value || node.raw || ""; } } return raw; }, createPathMetadata(rawPath, parts) { return { hasVariables: parts.some((p) => p && p.type === NodeType.VariableReference), isAbsolute: rawPath.startsWith("/"), hasExtension: /\.[a-zA-Z0-9]+$/.test(rawPath), extension: rawPath.match(/\.([a-zA-Z0-9]+)$/)?.[1] || null }; }, createCommandMetadata(parts) { return { hasVariables: parts.some((p) => p && p.type === NodeType.VariableReference) }; }, createTemplateMetadata(parts, wrapperType) { return { hasVariables: parts.some((p) => p && (p.type === NodeType.VariableReference || p.type === NodeType.ExecInvocation || p.type === "ConditionalTemplateSnippet" || p.type === "ConditionalStringFragment")), isTemplateContent: wrapperType === "doubleBracket" }; }, createUrlMetadata(protocol, parts, hasSection = false) { return { isUrl: true, protocol, hasVariables: parts.some((p) => p && p.type === NodeType.VariableReference), hasSection }; }, ttlToSeconds(value, unit) { const multipliers = { "milliseconds": 1 / 1e3, "seconds": 1, "minutes": 60, "hours": 3600, "days": 86400, "weeks": 604800 }; return value * (multipliers[unit] || 1); }, detectFormatFromPath(path46) { const ext = path46.match(/\.([a-zA-Z0-9]+)$/)?.[1]?.toLowerCase(); if (!ext) return null; const formatMap = { "json": "json", "xml": "xml", "yaml": "yaml", "yml": "yaml", "csv": "csv", "md": "markdown", "markdown": "markdown", "txt": "text", "text": "text" }; return formatMap[ext] || null; }, createSectionMeta(pathParts, sectionParts, hasRename) { return { sourceType: "section", hasVariables: [ ...pathParts, ...sectionParts ].some((part) => part && part.type === "VariableReference"), hasRename }; }, reconstructSectionPath(pathParts, sectionParts) { const pathStr = this.reconstructRawString(pathParts); const sectionStr = this.reconstructRawString(sectionParts); return `${pathStr} # ${sectionStr}`; }, /** * Checks if we're at a bracket that should end command parsing * This uses a specific heuristic: ] at end of input OR ] on its own line */ isCommandEndingBracket(input, pos) { if (input[pos] !== "]") return false; const nextPos = pos + 1; if (nextPos >= input.length) return true; let i = nextPos; while (i < input.length && (input[i] === " " || input[i] === " ")) { i++; } return i >= input.length || input[i] === "\n"; }, /** * Parse command content that may contain variables and text segments * This is used by the CommandBracketContent rule to handle @var interpolation * * @param content - The content to parse * @param baseLocation - The location of the content in the source */ parseCommandContent(content, baseLocation) { const parts = []; let i = 0; let currentText = ""; let textStartOffset = 0; if (!baseLocation) { console.warn("parseCommandContent called without baseLocation"); return this.parseCommandContentLegacy(content); } let currentOffset = baseLocation.start.offset; let currentLine = baseLocation.start.line; let currentColumn = baseLocation.start.column; while (i < content.length) { if (content[i] === "@" && i + 1 < content.length) { if (currentText) { const textEndOffset = currentOffset; const textEndLine = currentLine; const textEndColumn = currentColumn; parts.push(this.createNode(NodeType.Text, { content: currentText, location: { start: { offset: baseLocation.start.offset + textStartOffset, line: baseLocation.start.line, column: baseLocation.start.column + textStartOffset }, end: { offset: textEndOffset, line: textEndLine, column: textEndColumn } } })); currentText = ""; } const varStartOffset = currentOffset; const varStartLine = currentLine; const varStartColumn = currentColumn; i++; currentOffset++; currentColumn++; let varName = ""; while (i < content.length && /[a-zA-Z0-9_]/.test(content[i])) { varName += content[i]; i++; currentOffset++; currentColumn++; } if (varName) { const varEndOffset = currentOffset; const varEndLine = currentLine; const varEndColumn = currentColumn; parts.push(this.createVariableReferenceNode("varIdentifier", { identifier: varName }, { start: { offset: varStartOffset, line: varStartLine, column: varStartColumn }, end: { offset: varEndOffset, line: varEndLine, column: varEndColumn } })); textStartOffset = i; } else { currentText += "@"; } } else { if (currentText === "") { textStartOffset = i; } currentText += content[i]; if (content[i] === "\n") { currentLine++; currentColumn = 1; } else { currentColumn++; } currentOffset++; i++; } } if (currentText) { parts.push(this.createNode(NodeType.Text, { content: currentText, location: { start: { offset: baseLocation.start.offset + textStartOffset, line: baseLocation.start.line, column: baseLocation.start.column + textStartOffset }, end: { offset: currentOffset, line: currentLine, column: currentColumn } } })); } return parts; }, /** * Legacy version of parseCommandContent for backward compatibility * Creates nodes without proper location data */ parseCommandContentLegacy(content) { const parts = []; let i = 0; let currentText = ""; while (i < content.length) { if (content[i] === "@" && i + 1 < content.length) { if (currentText) { parts.push(this.createNode(NodeType.Text, { content: currentText })); currentText = ""; } i++; let varName = ""; while (i < content.length && /[a-zA-Z0-9_]/.test(content[i])) { varName += content[i]; i++; } if (varName) { parts.push(this.createNode(NodeType.Text, { content: "@" + varName })); } else { currentText += "@"; } } else { currentText += content[i]; i++; } } if (currentText) { parts.push(this.createNode(NodeType.Text, { content: currentText })); } return parts; }, /** * Create an ExecInvocation node */ createExecInvocation(commandRef, withClause, location) { return this.createNode("ExecInvocation", { commandRef, withClause: withClause || null, location }); }, attachPostFields(exec2, post) { if (!post || post.length === 0) { return exec2; } let current = exec2; const tail = current.withClause || null; if (tail) { current = { ...current, withClause: null }; } const additionalFields = []; for (const entry of post) { if (entry?.type === "methodCall") { if (additionalFields.length > 0) { const existingFields = current.fields || []; current = { ...current, fields: [ ...existingFields, ...additionalFields ] }; additionalFields.length = 0; } const methodRef = { name: entry.name, identifier: [ this.createNode(NodeType.Text, { content: entry.name, location: entry.location }) ], args: entry.args || [], isCommandReference: true, objectSource: current }; current = this.createExecInvocation(methodRef, null, entry.location); } else { additionalFields.push(entry); } } if (additionalFields.length > 0) { const existingFields = current.fields || []; current = { ...current, fields: [ ...existingFields, ...additionalFields ] }; } if (tail) { current = { ...current, withClause: tail }; } return current; }, applyTail(exec2, tail) { if (!tail) { return exec2; } return { ...exec2, withClause: tail }; }, /** * Get the command name from an ExecInvocation node */ getExecInvocationName(node) { if (!node || node.type !== "ExecInvocation") return null; return node.commandRef?.identifier || node.commandRef?.name; }, /** * Check if a node is an ExecInvocation */ isExecInvocationNode(node) { return node?.type === "ExecInvocation"; }, /** * Parse a JavaScript code block using acorn to find the complete block * This handles nested braces, strings, template literals, etc. properly * * @param input - The full input string * @param startPos - Position after the opening brace * @returns The parsed code content and end position, or null if invalid */ parseJavaScriptBlock(input, startPos) { const potentialCode = input.substring(startPos); let lastValidEnd = -1; let lastValidCode = ""; for (let i = 0; i < potentialCode.length; i++) { if (potentialCode[i] !== "}") continue; const testCode = potentialCode.substring(0, i); try { acorn.parse(`(${testCode})`, { ecmaVersion: "latest", allowReturnOutsideFunction: true }); lastValidEnd = i; lastValidCode = testCode; } catch (e) { try { acorn.parse(testCode, { ecmaVersion: "latest", allowReturnOutsideFunction: true, sourceType: "module" }); lastValidEnd = i; lastValidCode = testCode; } catch (e2) { } } } if (lastValidEnd >= 0) { return { content: lastValidCode.trim(), endPos: startPos + lastValidEnd }; } return null; }, // Array vs Path disambiguation helpers for /var directive createEmptyArray(location) { return { type: "array", items: [], location }; }, createArrayFromContent(content, location) { return { type: "array", items: content, location }; }, createSectionExtraction(content, location) { return { type: "section", path: content.path, section: content.section, location }; }, createPathDereference(content, location) { return { type: "path", segments: content, location }; }, createObjectFromProperties(properties, location) { return { type: "object", properties: properties || {}, location }; }, // Error Recovery Helper Functions // -------------------------------- /** * Checks if an array is unclosed by scanning ahead * Returns true if we hit a newline before finding the closing bracket */ isUnclosedArray(input, pos) { let depth = 1; let i = pos; let hasHash = false; this.debug("isUnclosedArray starting at pos", pos, "first 50 chars:", input.substring(pos, pos + 50)); while (i < input.length && depth > 0) { const char = input[i]; if (char === "[") { depth++; this.debug("Found [ at", i, "depth now", depth); } else if (char === "]") { depth--; this.debug("Found ] at", i, "depth now", depth); } else if (char === "#" && depth === 1) { hasHash = true; this.debug("Found # at", i, "in brackets - this is section syntax"); } else if (char === "\n" && depth > 0) { if (!hasHash) { this.debug("Found newline at", i, "without # - unclosed array"); return true; } this.debug("Found newline at", i, "but has # - continuing scan"); } i++; } const result = depth > 0; this.debug("isUnclosedArray finished: result=", result, "hasHash=", hasHash, "depth=", depth, "scanned to pos", i); return result; }, /** * Checks if an object is unclosed by scanning ahead * Returns true if we hit a newline before finding the closing brace */ isUnclosedObject(input, pos) { let depth = 1; let i = pos; let inString = false; let stringChar = null; while (i < input.length && depth > 0) { const char = input[i]; if ((char === '"' || char === "'") && (i === 0 || input[i - 1] !== "\\")) { if (!inString) { inString = true; stringChar = char; } else if (char === stringChar) { inString = false; stringChar = null; } } if (!inString) { if (char === "{") depth++; else if (char === "}") depth--; else if (char === "\n" && depth > 0) return true; } i++; } return depth > 0; }, /** * Checks if a string quote is unclosed * Returns true if we hit a newline or end of input before finding the closing quote */ detectMissingQuoteClose(input, pos, quoteChar) { let i = pos; while (i < input.length) { if (input[i] === quoteChar && input[i - 1] !== "\\") return false; if (input[i] === "\n") return true; i++; } return true; }, /** * Checks if a template delimiter (::) is unclosed */ isUnclosedTemplate(input, pos) { let i = pos; while (i < input.length - 1) { if (input[i] === ":" && input[i + 1] === ":") return false; i++; } return true; }, /** * Checks if we're at the start of what looks like a multiline array * (array with newline after opening bracket) */ isMultilineArrayStart(input, pos) { let i = pos; while (i < input.length && (input[i] === " " || input[i] === " ")) { i++; } return i < input.length && input[i] === "\n"; }, /** * Scans ahead to check if this looks like a valid language identifier for /run */ isValidLanguageKeyword(input, pos, lang) { const validLanguages = [ "js", "javascript", "node", "python", "py", "bash", "sh" ]; return validLanguages.includes(lang.toLowerCase()); }, /** * Checks if we're missing a 'from' keyword in an import statement */ isMissingFromKeyword(input, pos) { let i = pos; while (i < input.length && (input[i] === " " || input[i] === " ")) { i++; } if (i < input.length) { const char = input[i]; return char === '"' || char === "'" || char === "[" || char === "@"; } return false; }, /** * Create an error with enhanced location tracking * Since we can't access parser internals from here, we'll just throw * a regular error and let the parser enhance it */ mlldError(message, expectedToken, loc) { const error = new Error(message); error.isMlldError = true; error.expectedToken = expectedToken; error.mlldErrorLocation = loc; throw error; }, /** * Capture content inside balanced [ ] brackets starting at startPos (the first character after '[') * Returns null if no matching closing bracket is found */ captureBracketContent(input, startPos) { let depth = 1; let i = startPos; let inString = false; let quote2 = null; while (i < input.length && depth > 0) { const ch = input[i]; if (inString) { if (ch === quote2 && input[i - 1] !== "\\") { inString = false; quote2 = null; } } else { if (ch === '"' || ch === "'" || ch === "`") { inString = true; quote2 = ch; } else if (ch === "[") { depth++; } else if (ch === "]") { depth--; if (depth === 0) { return { content: input.slice(startPos, i), endOffset: i }; } } } i++; } return null; }, /** * Offset a location object by a base location (start of the block content) */ offsetLocation(loc, baseLocation) { if (!loc || !baseLocation?.start) return loc; const baseStart = baseLocation.start; const adjustPosition = /* @__PURE__ */ __name((pos) => { const line = (pos?.line || 1) + (baseStart.line || 1) - 1; const column = pos?.line === 1 ? (pos?.column || 1) + (baseStart.column || 1) - 1 : pos?.column || 1; return { offset: (pos?.offset || 0) + (baseStart.offset || 0), line, column }; }, "adjustPosition"); return { source: baseLocation.source || loc.source, start: adjustPosition(loc.start), end: adjustPosition(loc.end) }; }, /** * Reparse a block substring with a specific start rule to surface inner errors with corrected offsets */ reparseBlock(options) { const parseOptions = { startRule: options.startRule }; if (options.mode) parseOptions.mode = options.mode; if (options.grammarSource) parseOptions.grammarSource = options.grammarSource; try { const normalizedText = options.text.replace(/\s+$/, ""); options.parse(normalizedText, parseOptions); } catch (error) { const err = error; if (err instanceof options.SyntaxErrorClass && err.location) { const adjustedLocation = this.offsetLocation(err.location, options.baseLocation); const enhancedError = new options.SyntaxErrorClass(err.message, err.expected, err.found, adjustedLocation); enhancedError.expected = err.expected; enhancedError.found = err.found; enhancedError.location = adjustedLocation; throw enhancedError; } throw error; } throw this.mlldError("Invalid block content.", void 0, options.baseLocation); }, // Parser State Management for Code Blocks // ---------------------------------------- // These functions help prevent state corruption when parsing multiple // complex functions in mlld-run blocks /** * Parser state tracking object * Used to detect and prevent state corruption issues */ parserState: { codeBlockDepth: 0, braceDepth: 0, inString: false, stringChar: null, lastDirectiveEndPos: -1, functionCount: 0, maxNestingDepth: 20 }, /** * Reset parser state between functions * This prevents state corruption when parsing multiple complex functions */ resetCodeParsingState() { this.parserState.braceDepth = 0; this.parserState.inString = false; this.parserState.stringChar = null; this.parserState.functionCount++; this.debug("Parser state reset", { functionCount: this.parserState.functionCount, lastEndPos: this.parserState.lastDirectiveEndPos }); }, /** * Get current brace depth for debugging and limits */ getBraceDepth() { return this.parserState.braceDepth; }, /** * Increment brace depth with overflow checking */ incrementBraceDepth() { this.parserState.braceDepth++; if (this.parserState.braceDepth > this.parserState.maxNestingDepth) { this.mlldError(`Code block nesting too deep (${this.parserState.braceDepth} levels). Consider simplifying your function or splitting it into smaller functions.`); } }, /** * Decrement brace depth with underflow checking */ decrementBraceDepth() { this.parserState.braceDepth--; if (this.parserState.braceDepth < 0) { this.debug("WARNING: Brace depth underflow detected", { depth: this.parserState.braceDepth, functionCount: this.parserState.functionCount }); this.parserState.braceDepth = 0; } }, /** * Validate parser state consistency * Returns true if state is valid, false if corrupted */ validateParserState() { const isValid = this.parserState.braceDepth >= 0 && this.parserState.braceDepth <= this.parserState.maxNestingDepth; if (!isValid) { this.debug("Parser state validation failed", { braceDepth: this.parserState.braceDepth, inString: this.parserState.inString, functionCount: this.parserState.functionCount }); } return isValid; }, /** * Mark the end of a directive for state tracking */ markDirectiveEnd(pos) { this.parserState.lastDirectiveEndPos = pos; }, // File Reference Helper Functions // -------------------------------- /** * Checks if content inside <...> represents a file reference * File references are detected by presence of: . * @ * Note: We don't include / since we don't support directories * Files without extensions can be used outside interpolation contexts */ isFileReferenceContent(content) { if (content.trim().startsWith("!")) { return false; } return /[.*@]/.test(content); }, /** * Creates a FileReference AST node */ createFileReferenceNode(source, fields, pipes, location) { return { type: "FileReference", nodeId: (0, import_crypto.randomUUID)(), source, fields: fields || [], pipes: pipes || [], location, meta: { isFileReference: true, hasGlob: typeof source === "object" && source.raw && source.raw.includes("*"), isPlaceholder: source && source.type === "placeholder" } }; }, // Binary expression builder with left-to-right associativity createBinaryExpression(first, rest, location) { if (!rest || rest.length === 0) return first; return rest.reduce((left, { op, right }) => this.createNode("BinaryExpression", { operator: op, left, right, location }), first); }, buildWhenBoundPatternExpression(boundIdentifier, pattern) { const anchorLocation = /* @__PURE__ */ __name((loc) => { if (!loc || !loc.start) return loc; return { start: loc.start, end: loc.start }; }, "anchorLocation"); const boundRef = /* @__PURE__ */ __name((loc) => this.createVariableReferenceNode("identifier", { identifier: boundIdentifier }, anchorLocation(loc)), "boundRef"); const build = /* @__PURE__ */ __name((p) => { if (!p) return p; if (p.kind === "logical") { const first = build(p.first); const rest = Array.isArray(p.rest) ? p.rest.map((r) => ({ op: r.op, right: build(r.right) })) : []; return this.createBinaryExpression(first, rest, p.location); } if (p.kind === "wildcard") return p.node; if (p.kind === "compare") { return this.createNode("BinaryExpression", { operator: p.op, left: boundRef(p.location), right: p.right, location: p.location }); } if (p.kind === "equals") { const value = p.value; if (value && typeof value === "object" && "type" in value && value.type === "Literal") { if (value.valueType === "none" || value.valueType === "wildcard") return value; } return this.createNode("BinaryExpression", { operator: "==", left: boundRef(p.location), right: value, location: p.location }); } return p; }, "build"); return build(pattern); }, // Check if nodes contain newlines containsNewline(nodes) { if (!Array.isArray(nodes)) nodes = [ nodes ]; return nodes.some((n) => n.type === "Newline" || n.content && n.content.includes("\n") || n.raw && n.raw.includes("\n")); }, /** * Creates a WhenExpression node for when expressions (used in /var assignments) */ createWhenExpression(conditions, withClause, location, modifier = null, bound = null) { return this.createNode(NodeType.WhenExpression, { conditions, withClause: withClause || null, ...bound ? { boundIdentifier: bound.boundIdentifier, boundValue: bound.boundValue } : {}, meta: { conditionCount: conditions.length, isValueReturning: true, evaluationType: "expression", hasTailModifiers: !!withClause, modifier, hasBoundValue: !!bound, ...bound ? { boundIdentifier: bound.boundIdentifier } : {} }, location }); }, /** * Creates a ForExpression node for for...in expressions in /var assignments */ createForExpression(variable, source, expression, location, opts, batchPipeline) { const meta = { isForExpression: true }; if (opts) { meta.forOptions = opts; } if (batchPipeline) { meta.batchPipeline = batchPipeline; } return { type: "ForExpression", nodeId: (0, import_crypto.randomUUID)(), variable, source, expression: Array.isArray(expression) ? expression : [ expression ], location, meta }; }, /** * Creates an action node for /for directive actions */ createForActionNode(directive, content, location, endingTail, endingComment) { const kind = directive; if (kind === "show" && content) { if (content && typeof content === "object" && "content" in content && "wrapperType" in content) { const values2 = { content: content.content }; if (endingTail && endingTail.pipeline) { values2.pipeline = endingTail.pipeline; } const meta3 = { implicit: false, isTemplateContent: true }; if (endingComment) { meta3.comment = endingComment; } return [ this.createNode(NodeType.Directive, { kind, subtype: "showTemplate", values: values2, raw: { content: this.reconstructRawString(content.content) }, meta: meta3, location }) ]; }