mlld
Version:
mlld: a modular prompt scripting language
1,529 lines (1,521 loc) • 2.29 MB
JavaScript
'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
var locationFormatter_exports = {};
__export(locationFormatter_exports, {
formatLocation: () => formatLocation,
formatLocationForError: () => formatLocationForError
});
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, 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",
exe: "exe",
for: "for",
path: "path",
import: "import",
output: "output",
when: "when"
};
helpers = {
debug(msg, ...args) {
if (process.env.DEBUG_MLLD_GRAMMAR) console.log("[DEBUG GRAMMAR]", msg, ...args);
},
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 slash directive context
* A slash directive context requires:
* 1. / symbol at logical line start
* 2. Followed by a valid directive keyword
*/
isSlashDirectiveContext(input, pos) {
if (input[pos] !== "/") return false;
const isAtLineStart = this.isLogicalLineStart(input, pos);
if (!isAtLineStart) return false;
const directiveKeywords = Object.keys(DirectiveKind);
const afterSlashPos = pos + 1;
for (const keyword of directiveKeywords) {
if (afterSlashPos + keyword.length > input.length) continue;
const potentialKeyword = input.substring(afterSlashPos, afterSlashPos + keyword.length);
if (potentialKeyword === keyword) {
if (afterSlashPos + keyword.length === input.length) return true;
const nextChar = input[afterSlashPos + keyword.length];
if (" \r\n".includes(nextChar)) return true;
}
}
return false;
},
/**
* 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.isSlashDirectiveContext(input, pos)) return false;
return true;
},
/**
* Determines if the current position is within a right-hand side (RHS) expression
* RHS contexts are after assignment operators (=, :) in directive bodies
*/
isRHSContext(input, pos) {
if (pos === 0) return false;
let i = pos - 1;
let inString = false;
let stringChar = null;
let foundEquals = false;
while (i >= 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 === "=" || char === ":") {
foundEquals = true;
break;
}
if (char === ";" || char === "\n") {
return false;
}
}
i--;
}
if (foundEquals) {
let j = i - 1;
while (j >= 0 && " \r".includes(input[j])) {
j--;
}
let name = "";
while (j >= 0 && /[a-zA-Z0-9_]/.test(input[j])) {
name = input[j] + name;
j--;
}
if (j >= 0 && input[j] === "/") {
const validAssignmentDirectives = [
"exec",
"text",
"data",
"run"
];
if (validAssignmentDirectives.includes(name)) {
if (this.isLogicalLineStart(input, j)) {
return true;
}
}
}
return false;
}
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.isSlashDirectiveContext(input, pos) && !this.isAtVariableContext(input, pos) && !this.isRHSContext(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}}}`;
}
}
}
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 (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)),
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 = {
"seconds": 1,
"minutes": 60,
"hours": 3600,
"days": 86400,
"weeks": 604800
};
return value * (multipliers[unit] || 1);
},
createSecurityMeta(options) {
if (!options) return {};
const meta = {};
if (options.ttl) {
meta.ttl = options.ttl;
}
if (options.trust) {
meta.trust = options.trust;
}
return meta;
},
detectFormatFromPath(path36) {
const ext = path36.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
});
},
/**
* 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;
},
// 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) {
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);
},
// 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 RHS when expressions
*/
createWhenExpression(conditions, withClause, location) {
return this.createNode(NodeType.WhenExpression, {
conditions,
withClause: withClause || null,
meta: {
conditionCount: conditions.length,
isValueReturning: true,
evaluationType: "expression",
hasTailModifiers: !!withClause
},
location
});
},
/**
* Creates a ForExpression node for for...in expressions in /var assignments
*/
createForExpression(variable, source, expression, location) {
return {
type: "ForExpression",
nodeId: (0, import_crypto.randomUUID)(),
variable,
source,
expression: Array.isArray(expression) ? expression : [
expression
],
location,
meta: {
isForExpression: true
}
};
},
/**
* Creates an action node for /for directive actions
*/
createForActionNode(directive, content, location) {
const kind = directive;
return [
this.createNode(NodeType.Directive, {
kind,
subtype: kind,
values: {
content: Array.isArray(content) ? content : [
content
]
},
raw: {
content: this.reconstructRawString(content)
},
meta: {
implicit: false
},
location
})
];
}
};
}
});
// grammar/generated/parser/deps/node-type.js
var node_type_default;
var init_node_type = __esm({
"grammar/generated/parser/deps/node-type.js"() {
init_grammar_core();
node_type_default = NodeType;
}
});
// grammar/generated/parser/deps/directive-kind.js
var directive_kind_default;
var init_directive_kind = __esm({
"grammar/generated/parser/deps/directive-kind.js"() {
init_grammar_core();
directive_kind_default = DirectiveKind;
}
});
// grammar/generated/parser/deps/helpers.js
var helpers_default;
var init_helpers = __esm({
"grammar/generated/parser/deps/helpers.js"() {
init_grammar_core();
helpers_default = helpers;
}
});
// grammar/generated/parser/parser.js
function peg$subclass(child, parent) {
function C() {
this.constructor = child;
}
__name(C, "C");
C.prototype = parent.prototype;
child.prototype = new C();
}
function peg$SyntaxError(message, expected, found, location) {
var self = Error.call(this, message);
if (Object.setPrototypeOf) {
Object.setPrototypeOf(self, peg$SyntaxError.prototype);
}
self.expected = expected;
self.found = found;
self.location = location;
self.name = "SyntaxError";
return self;
}
function peg$padEnd(str, targetLength, padString) {
padString = padString || " ";
if (str.length > targetLength) {
return str;
}
targetLength -= str.length;
padString += padString.repeat(targetLength);
return str + padString.slice(0, targetLength);
}
function peg$parse(input, options) {
options = options !== void 0 ? options : {};
var peg$FAILED = {};
var peg$source = options.grammarSource;
var peg$startRuleFunctions = {
Start: peg$parseStart
};
var peg$startRuleFunction = peg$parseStart;
var peg$c0 = ">>";
var peg$c1 = "<<";
var peg$c2 = "\n";
var peg$c3 = "{{";
var peg$c4 = "}}";
var peg$c5 = "::";
var peg$c6 = "```";
var peg$c7 = "mlld-run";
var peg$c8 = "---";
var peg$c9 = "'";
var peg$c11 = ".";
var peg$c12 = "true";
var peg$c13 = "false";
var peg$c14 = "null";
var peg$c15 = "*";
var peg$c16 = "[[";
var peg$c17 = "]]";
var peg$c18 = "\\";
var peg$c19 = "<";
var peg$c20 = '"';
var peg$c21 = "`";
var peg$c22 = "/";
var peg$c23 = "#";
var peg$c24 = "/var";
var peg$c25 = "/show";
var peg$c26 = "/run";
var peg$c27 = "/exe";
var peg$c28 = "/path";
var peg$c29 = "/import";
var peg$c30 = "/when";
var peg$c31 = "/output";
var peg$c32 = "\r\n";
var peg$c33 = ">";
var peg$c34 = "https";
var peg$c35 = "http";
var peg$c36 = "://";
var peg$c37 = "@";
var peg$c38 = " as ";
var peg$c39 = "as";
var peg$c40 = "<>";
var peg$c41 = "[";
var peg$c42 = "]";
var peg$c43 = ",";
var peg$c44 = "|";
var peg$c45 = "&&";
var peg$c46 = "||";
var peg$c47 = ";";
var peg$c48 = "run";
var peg$c49 = "npx";
var peg$c50 = "npm";
var peg$c51 = "yarn";
var peg$c52 = "pnpm";
var peg$c53 = "bun";
var peg$c54 = ":";
var peg$c55 = "-m";
var peg$c56 = "python";
var peg$c57 = "python3";
var peg$c58 = "python2";
var peg$c59 = "node";
var peg$c60 = "sh";
var peg$c61 = "bash";
var peg$c62 = "zsh";
var peg$c63 = "perl";
var peg$c64 = "ruby";
var peg$c65 = "-e";
var peg$c66 = "-c";
var peg$c67 = "make";
var peg$c68 = "cargo";
var peg$c69 = "go";
var peg$c70 = "gradle";
var peg$c71 = "maven";
var peg$c72 = "mvn";
var peg$c73 = "rake";
var peg$c74 = "(";
var peg$c75 = ")";
var peg$c76 = ":::";
var peg$c77 = "{";
var peg$c78 = "}";
var peg$c79 = "?";
var peg$c80 = "==";
var peg$c81 = "!=";
var peg$c82 = "<=";
var peg$c83 = ">=";
var peg$c84 = "=";
var peg$c85 = "!";
var peg$c86 = "foreach";
var peg$c87 = "with";
var peg$c88 = "separator";
var peg$c89 = "template";
var peg$c90 = "in";
var peg$c91 = "output";
var peg$c92 = "to";
var peg$c93 = "show";
var peg$c94 = "var";
var peg$c95 = "trust";
var peg$c96 = "needs";
var peg$c97 = "@run";
var peg$c98 = "stdout";
var peg$c99 = "stderr";
var peg$c100 = "env";
var peg$c101 = "s";
var peg$c102 = "file";
var peg$c103 = "//";
var peg$c104 = "\\\\";
var peg$c105 = "\\@";
var peg$c106 = "live";
var peg$c107 = "static";
var peg$c108 = "always";
var peg$c109 = "verify";
var peg$c110 = "never";
var peg$c111 = "&>";
var peg$c112 = "&";
var peg$c113 = "pipeline";
var peg$c114 = "[(";
var peg$c115 = ")]";
var peg$c116 = "/*";
var peg$c117 = "*/";
var peg$c118 = "js";
var peg$c119 = "javascript";
var peg$c120 = "when";
var peg$c121 = "=>";
var peg$c122 = "for";
var peg$c123 = "each";
var peg$c124 = "now";
var peg$c125 = "base";
var peg$c126 = "input";
var peg$c127 = "debug";
var peg$c128 = "frontmatter";
var peg$c129 = "fm";
var peg$c130 = "format";
var peg$c131 = "asSection";
var peg$c132 = "from";
var peg$c133 = "nodejs";
var peg$c134 = "py";
var peg$c135 = "risk.high";
var peg$c136 = "risk.med";
var peg$c137 = "risk.low";
var peg$c138 = "risk";
var peg$c139 = "about";
var peg$c140 = "meta";
var peg$c141 = "/for";
var peg$c142 = "@item";
var peg$c143 = "@input";
var peg$c144 = "@now";
var peg$c145 = "@time";
var peg$c146 = "@stdin";
var peg$c147 = "env:";
var peg$c148 = "PROJECTPATH";
var peg$c149 = "under";
var peg$c150 = "any";
var peg$c151 = "all";
var peg$c152 = "first";
var peg$r0 = /^[ \t\r]/;
var peg$r1 = /^[^\n]/;
var peg$r2 = /^[ \t]/;
var peg$r3 = /^[^`\r\n]/;
var peg$r4 = /^[0-9]/;
var peg$r5 = /^["$'@[-\]`{}]/;
var peg$r6 = /^["'.0\\nrt]/;
var peg$r7 = /^[ \t\r\n\/\]@${{}"'`]/;
var peg$r8 = /^[\/[\]@${}]/;
var peg$r9 = /^[\]\/[\]@${}]/;
var peg$r10 = /^[\]]/;
var peg$r11 = /^[a-zA-Z_]/;
var peg$r12 = /^[a-zA-Z0-9_]/;
var peg$r13 = /^[.~]/;
var peg$r14 = /^[ \t\r\n\u200B\u200C\u200D]/;
var peg$r15 = /^[ \t\r\n]/;
var peg$r16 = /^[ \t\r\u200B\u200C\u200D]/;
var peg$r17 = /^[\r\u2028-\u2029]/;
var peg$r18 = /^[^\r\n]/;
var peg$r19 = /^[<"]/;
var peg$r20 = /^[`<@]/;
var peg$r21 = /^[a-zA-Z0-9.\-]/;
var peg$r22 = /^[^> ]/;
var peg$r23 = /^[^@|&;[\]\n \t]/;
var peg$r24 = /^[a-zA-Z0-9_\-]/;
var peg$r25 = /^[a-zA-Z0-9_:\-]/;
var peg$r26 = /^[a-zA-Z0-9_.\/\-]/;
var peg$r27 = /^["'),`]/;
var peg$r28 = /^["'),\\`]/;
var peg$r29 = /^["@<\n\r]/;
var peg$r30 = /^[`@<]/;
var peg$r31 = /^[<@]/;
var peg$r32 = /^[ \t\r\n\/\]{}]/;
var peg$r33 = /^[^[\n]/;
var peg$r34 = /^[^\]]/;
var peg$r35 = /^[(.-\/]/;
var peg$r36 = /^[a-zA-Z0-9_@\-]/;
var peg$r37 = /^[a-zA-Z0-9_\/@\-]/;
var peg$r38 = /^[^ \t\n\r]/;
var peg$r39 = /^[^ \t\n\r"'[\]]/;
var peg$r40 = /^[^\/s]/;
var peg$r41 = /^[^"]/;
var peg$r42 = /^[^']/;
var peg$r43 = /^[^@\\s\n]/;
var peg$r44 = /^[\]"'\r\n]/;
var peg$r45 = /^[dhmsw]/;
var peg$r46 = /^[<>]/;
var peg$r47 = /^[&>]/;
var peg$r48 = /^[^"@]/;
var peg$r49 = /^[^@|&;<> \t\n\r"']/;
var peg$r50 = /^[^}]/;
var peg$r51 = /^[ \t\n\r]/;
var peg$r52 = /^[^#\]]/;
var peg$r53 = /^[^\\s\\n]/;
var peg$r54 = /^[^)]/;
var peg$r55 = /^[a-f0-9]/;
var peg$r56 = /^[^,)]/;
var peg$e0 = peg$classExpectation([
" ",
" ",
"\r"
], false, false);
var peg$e1 = peg$literalExpectation(">>", false);
var peg$e2 = peg$literalExpectation("<<", false);
var peg$e3 = peg$classExpectation([
"\n"
], true, false);
var peg$e4 = peg$literalExpectation("\n", false);
var peg$e5 = peg$literalExpectation("{{", false);
var peg$e6 = peg$literalExpectation("}}", false);
var peg$e7 = peg$literalExpectation("::", false);
var peg$e8 = peg$anyExpectation();
var peg$e9 = peg$classExpectation([
" ",
" "
], false, false);
var peg$e10 = peg$literalExpectation("```", false);
var peg$e11 = peg$literalExpectation("mlld-run", false);
var peg$e12 = peg$classExpectation([
"`",
"\r",
"\n"
], true, false);
var peg$e13 = peg$otherExpectation("Top-level directive context");
var peg$e14 = peg$otherExpectation("Variable reference context");
var peg$e15 = peg$otherExpectation("Right-hand side context");
var peg$e16 = peg$otherExpectation("Plain text context");
var peg$e17 = peg$otherExpectation("Run-style code block context");
var peg$e18 = peg$otherExpectation("Exec /run right-hand side context");
var peg$e19 = peg$otherExpectation("Path starting with @variable context");
var peg$e20 = peg$otherExpectation("Directive boundary");
var peg$e21 = peg$otherExpectation("YAML frontmatter");
var peg$e22 = peg$literalExpectation("---", false);
var peg$e23 = peg$otherExpectation("String Literal");
var peg$e24 = peg$literalExpectation("'", false);
var peg$e25 = peg$otherExpectation("Number Literal");
var peg$e26 = peg$literalExpectation("-", false);
var peg$e27 = peg$classExpectation([
[
"0",
"9"
]
], false, false);
var peg$e28 = peg$literalExpectation(".", false);
var peg$e29 = peg$otherExpectation("Boolean Literal");
var peg$e30 = peg$literalExpectation("true", f