mlld
Version:
mlld: llm scripting language
589 lines (587 loc) • 21.6 kB
JavaScript
import { isStructuredValue } from './chunk-CQPPOI5P.mjs';
import { MlldImportError } from './chunk-YVSXROKS.mjs';
import { astLocationToSourceLocation } from './chunk-SON743RN.mjs';
import { createStructuredValueVariable, createTemplateVariable, createArrayVariable, createObjectVariable, createImportedVariable, createExecutableVariable } from './chunk-CHF7KR7X.mjs';
import { __name } from './chunk-NJQWMXLH.mjs';
// interpreter/eval/import/VariableImporter.ts
var _VariableImporter = class _VariableImporter {
constructor(objectResolver) {
this.objectResolver = objectResolver;
}
/**
* Serialize shadow environments for export (Maps to objects)
* WHY: Maps don't serialize to JSON, so we convert them to plain objects
* GOTCHA: Function references are preserved directly
*/
serializeShadowEnvs(envs) {
const result = {};
for (const [lang, shadowMap] of Object.entries(envs)) {
if (shadowMap instanceof Map && shadowMap.size > 0) {
const obj = {};
for (const [name, func] of shadowMap) {
obj[name] = func;
}
result[lang] = obj;
}
}
return result;
}
/**
* Deserialize shadow environments after import (objects to Maps)
* WHY: Shadow environments are expected as Maps internally
*/
deserializeShadowEnvs(envs) {
const result = {};
for (const [lang, shadowObj] of Object.entries(envs)) {
if (shadowObj && typeof shadowObj === "object") {
const map = /* @__PURE__ */ new Map();
for (const [name, func] of Object.entries(shadowObj)) {
map.set(name, func);
}
result[lang] = map;
}
}
return result;
}
/**
* Checks whether the requested alias has already been claimed during the
* current import pass and throws a detailed error when a collision exists.
*/
ensureImportBindingAvailable(targetEnv, name, importSource, location) {
if (!name || name.trim().length === 0) return;
const existingBinding = targetEnv.getImportBinding(name);
if (!existingBinding) {
return;
}
throw new MlldImportError(
`Import collision - '${name}' already imported from ${existingBinding.source}. Alias one of the imports.`,
{
code: "IMPORT_NAME_CONFLICT",
context: {
name,
existingSource: existingBinding.source,
attemptedSource: importSource,
existingLocation: existingBinding.location,
newLocation: location,
suggestion: "Use 'as' to alias one of the imports"
},
details: {
filePath: location?.filePath || existingBinding.location?.filePath,
variableName: name
}
}
);
}
/**
* Writes the variable and persists the associated binding only after the
* assignment succeeds, preventing partially-applied imports from polluting
* the collision tracking map.
*/
setVariableWithImportBinding(targetEnv, alias, variable, binding) {
let shouldPersistBinding = false;
try {
targetEnv.setVariable(alias, variable);
shouldPersistBinding = true;
} finally {
if (shouldPersistBinding) {
targetEnv.setImportBinding(alias, binding);
}
}
}
/**
* Serialize module environment for export (Map to object)
* WHY: Maps don't serialize to JSON, so we need to convert to exportable format
* IMPORTANT: Use the exact same serialization as processModuleExports to ensure compatibility
*/
serializeModuleEnv(moduleEnv) {
const tempResult = this.processModuleExports(moduleEnv, {}, true);
return tempResult.moduleObject;
}
/**
* Deserialize module environment after import (object to Map)
* IMPORTANT: Reuse createVariableFromValue to ensure proper Variable reconstruction
*/
deserializeModuleEnv(moduleEnv) {
const result = /* @__PURE__ */ new Map();
if (moduleEnv && typeof moduleEnv === "object") {
for (const [name, varData] of Object.entries(moduleEnv)) {
const variable = this.createVariableFromValue(
name,
varData,
"module-env",
// Use a special import path to indicate this is from module env
name
);
result.set(name, variable);
}
}
return result;
}
/**
* Import variables from a processing result into the target environment
*/
async importVariables(processingResult, directive, targetEnv) {
const { moduleObject } = processingResult;
await this.handleImportType(directive, moduleObject, targetEnv, processingResult.childEnvironment);
}
/**
* Process module exports - either use explicit @data module or auto-generate
*/
processModuleExports(childVars, parseResult, skipModuleEnvSerialization, manifest) {
const frontmatter = parseResult.frontmatter || null;
const moduleObject = {};
const explicitNames = manifest?.hasEntries() ? manifest.getNames() : null;
const explicitExports = explicitNames ? new Set(explicitNames) : null;
if (explicitNames && explicitNames.length > 0) {
for (const name of explicitNames) {
if (!childVars.has(name)) {
const location = manifest?.getLocation(name);
throw new MlldImportError(
`Exported name '${name}' is not defined in this module`,
{
code: "EXPORTED_NAME_NOT_FOUND",
context: {
exportName: name,
location
},
details: {
filePath: location?.filePath,
variableName: name
}
}
);
}
}
}
const shouldSerializeModuleEnv = !skipModuleEnvSerialization;
let moduleEnvSnapshot = null;
const getModuleEnvSnapshot = /* @__PURE__ */ __name(() => {
if (!moduleEnvSnapshot) {
moduleEnvSnapshot = new Map(childVars);
}
return moduleEnvSnapshot;
}, "getModuleEnvSnapshot");
if (process.env.MLLD_DEBUG === "true") {
console.log(`[processModuleExports] childVars size: ${childVars.size}`);
console.log(`[processModuleExports] childVars keys: ${Array.from(childVars.keys()).join(", ")}`);
}
for (const [name, variable] of childVars) {
if (explicitExports && !explicitExports.has(name)) {
continue;
}
if (!this.isLegitimateVariableForExport(variable)) {
if (process.env.MLLD_DEBUG === "true") {
console.log(`[processModuleExports] Skipping non-legitimate variable '${name}' with type: ${variable.type}`);
}
continue;
}
if (variable.type === "executable") {
const execVar = variable;
let serializedMetadata = { ...execVar.metadata };
if (serializedMetadata.capturedShadowEnvs) {
serializedMetadata = {
...serializedMetadata,
capturedShadowEnvs: this.serializeShadowEnvs(serializedMetadata.capturedShadowEnvs)
};
}
if (shouldSerializeModuleEnv) {
const capturedEnv = serializedMetadata.capturedModuleEnv instanceof Map ? serializedMetadata.capturedModuleEnv : getModuleEnvSnapshot();
serializedMetadata = {
...serializedMetadata,
capturedModuleEnv: this.serializeModuleEnv(capturedEnv)
};
} else {
delete serializedMetadata.capturedModuleEnv;
}
moduleObject[name] = {
__executable: true,
value: execVar.value,
// paramNames removed - they're already in executableDef and shouldn't be exposed as imports
executableDef: execVar.metadata?.executableDef,
metadata: serializedMetadata
};
} else if (variable.type === "template") {
const templateVar = variable;
moduleObject[name] = {
__template: true,
content: templateVar.value,
templateSyntax: templateVar.templateSyntax,
parameters: templateVar.parameters,
templateAst: templateVar.metadata?.templateAst || (Array.isArray(templateVar.value) ? templateVar.value : void 0)
};
} else if (variable.type === "object" && typeof variable.value === "object" && variable.value !== null) {
const resolvedObject = this.objectResolver.resolveObjectReferences(variable.value, childVars);
moduleObject[name] = resolvedObject;
} else {
moduleObject[name] = variable.value;
}
}
return {
moduleObject,
frontmatter
};
}
/**
* Create a variable from an imported value, inferring the type
*/
createVariableFromValue(name, value, importPath, originalName) {
const source = {
directive: "var",
syntax: Array.isArray(value) ? "array" : value && typeof value === "object" ? "object" : "quoted",
hasInterpolation: false,
isMultiLine: false
};
const metadata = {
isImported: true,
importPath,
originalName: originalName !== name ? originalName : void 0,
definedAt: { line: 0, column: 0, filePath: importPath }
};
if (isStructuredValue(value)) {
return createStructuredValueVariable(
name,
value,
source,
{
...metadata,
isStructuredValue: true,
structuredValueType: value.type
}
);
}
if (value && typeof value === "object" && "__executable" in value && value.__executable) {
return this.createExecutableFromImport(name, value, source, metadata);
}
if (value && typeof value === "object" && value.__template) {
const templateSource = {
directive: "var",
syntax: "template",
hasInterpolation: true,
isMultiLine: true
};
const tmplMetadata = { ...metadata, templateAst: value.templateAst };
return createTemplateVariable(
name,
value.content,
value.parameters,
value.templateSyntax === "tripleColon" ? "tripleColon" : "doubleColon",
templateSource,
tmplMetadata
);
}
const originalType = this.inferVariableType(value);
let processedValue = value;
if (typeof value === "number" || typeof value === "boolean") {
processedValue = String(value);
}
if (originalType === "array" && Array.isArray(processedValue)) {
const isComplexArray = this.hasComplexContent(processedValue);
return createArrayVariable(
name,
processedValue,
isComplexArray,
source,
{
...metadata,
isImported: true,
importPath,
originalName: originalName !== name ? originalName : void 0
}
);
}
if (originalType === "object") {
const normalizedObject = this.unwrapArraySnapshots(processedValue, importPath);
const isComplex = this.hasComplexContent(normalizedObject);
return createObjectVariable(
name,
normalizedObject,
isComplex,
// Mark as complex if it contains AST nodes
source,
{
...metadata,
isImported: true,
importPath,
originalName: originalName !== name ? originalName : void 0
}
);
}
return createImportedVariable(
name,
processedValue,
originalType,
importPath,
false,
originalName || name,
source,
metadata
);
}
unwrapArraySnapshots(value, importPath) {
if (Array.isArray(value)) {
return value.map((item) => this.unwrapArraySnapshots(item, importPath));
}
if (value && typeof value === "object") {
if (value.__arraySnapshot) {
const snapshot = value;
const source = {
directive: "var",
syntax: "array",
hasInterpolation: false,
isMultiLine: false
};
const arrayMetadata = {
...snapshot.metadata || {},
isImported: true,
importPath,
originalName: snapshot.name
};
const normalizedElements = Array.isArray(snapshot.value) ? snapshot.value.map((item) => this.unwrapArraySnapshots(item, importPath)) : [];
const arrayName = snapshot.name || "imported_array";
return createArrayVariable(arrayName, normalizedElements, snapshot.isComplex === true, source, arrayMetadata);
}
const result = {};
for (const [key, entry] of Object.entries(value)) {
result[key] = this.unwrapArraySnapshots(entry, importPath);
}
return result;
}
return value;
}
/**
* Create a namespace variable for imports with aliased wildcards (e.g., * as @config)
*/
createNamespaceVariable(alias, moduleObject, importPath) {
const source = {
directive: "var",
syntax: "object",
hasInterpolation: false,
isMultiLine: false
};
const isComplex = this.hasComplexContent(moduleObject);
return createObjectVariable(
alias,
moduleObject,
isComplex,
// Mark as complex if it contains AST nodes or executables
source,
{
isImported: true,
importPath,
isNamespace: true,
definedAt: { line: 0, column: 0, filePath: importPath }
}
);
}
/**
* Merge variables into the target environment based on import type
*/
async handleImportType(directive, moduleObject, targetEnv, childEnv) {
if (directive.subtype === "importAll") {
throw new MlldImportError(
`Wildcard imports '/import { * }' are no longer supported. Use namespace imports instead: '/import "file"' or '/import "file" as @name'`,
directive.location,
{
suggestion: `Change '/import { * } from "file"' to '/import "file"'`
}
);
} else if (directive.subtype === "importNamespace") {
await this.handleNamespaceImport(directive, moduleObject, targetEnv, childEnv);
} else if (directive.subtype === "importSelected") {
await this.handleSelectedImport(directive, moduleObject, targetEnv, childEnv);
} else {
throw new Error(`Unknown import subtype: ${directive.subtype}`);
}
}
/**
* Handle namespace imports
*/
async handleNamespaceImport(directive, moduleObject, targetEnv, childEnv) {
const namespaceNodes = directive.values?.namespace;
const alias = namespaceNodes && Array.isArray(namespaceNodes) && namespaceNodes[0]?.content ? namespaceNodes[0].content : directive.values?.imports?.[0]?.alias;
if (!alias) {
throw new Error("Namespace import missing alias");
}
const importerFilePath = targetEnv.getCurrentFilePath();
const aliasLocationNode = namespaceNodes && Array.isArray(namespaceNodes) ? namespaceNodes[0] : void 0;
const aliasLocation = aliasLocationNode?.location ? astLocationToSourceLocation(aliasLocationNode.location, importerFilePath) : astLocationToSourceLocation(directive.location, importerFilePath);
let namespaceObject = moduleObject;
const importRef = directive.values?.from?.[0]?.content || "";
const moduleName = importRef.split("/").pop()?.replace(/\.mld$/, "") || "";
const exportKeys = Object.keys(moduleObject);
const commonNames = [moduleName, "main", "default", "exports"];
if (exportKeys.length === 1) {
namespaceObject = moduleObject[exportKeys[0]];
} else {
for (const name of commonNames) {
if (name && moduleObject[name] && typeof moduleObject[name] === "object") {
const mainExport = moduleObject[name];
const otherExports = exportKeys.filter((k) => k !== name && !k.startsWith("_"));
if (Object.keys(mainExport).length > otherExports.length) {
namespaceObject = mainExport;
break;
}
}
}
}
const importPath = childEnv.getCurrentFilePath() || "unknown";
const importDisplay = this.getImportDisplayPath(directive, importPath);
const bindingInfo = { source: importDisplay, location: aliasLocation };
this.ensureImportBindingAvailable(targetEnv, alias, importDisplay, aliasLocation);
if (namespaceObject && typeof namespaceObject === "object" && namespaceObject.__template) {
const templateVar = this.createVariableFromValue(alias, namespaceObject, importPath);
this.setVariableWithImportBinding(targetEnv, alias, templateVar, bindingInfo);
return;
}
const namespaceVar = this.createNamespaceVariable(
alias,
namespaceObject,
importPath
);
this.setVariableWithImportBinding(targetEnv, alias, namespaceVar, bindingInfo);
}
/**
* Handle selected imports
*/
async handleSelectedImport(directive, moduleObject, targetEnv, childEnv) {
const imports = directive.values?.imports || [];
const importPath = childEnv.getCurrentFilePath() || "unknown";
const importDisplay = this.getImportDisplayPath(directive, importPath);
const importerFilePath = targetEnv.getCurrentFilePath();
for (const importItem of imports) {
const importName = importItem.identifier;
const alias = importItem.alias || importName;
if (!(importName in moduleObject)) {
throw new Error(`Import '${importName}' not found in module`);
}
const bindingLocation = importItem?.location ? astLocationToSourceLocation(importItem.location, importerFilePath) : astLocationToSourceLocation(directive.location, importerFilePath);
const bindingInfo = { source: importDisplay, location: bindingLocation };
this.ensureImportBindingAvailable(targetEnv, alias, importDisplay, bindingLocation);
const importedValue = moduleObject[importName];
const variable = this.createVariableFromValue(alias, importedValue, importPath, importName);
this.setVariableWithImportBinding(targetEnv, alias, variable, bindingInfo);
}
}
/**
* Produces a human-readable source string for error messages, stripping any
* quotes that appeared in the original directive.
*/
getImportDisplayPath(directive, fallback) {
const raw = directive?.raw;
if (raw && typeof raw.path === "string" && raw.path.trim().length > 0) {
const trimmed = raw.path.trim();
return trimmed.replace(/^['"]|['"]$/g, "");
}
return fallback;
}
/**
* Create an executable variable from import metadata
*/
createExecutableFromImport(name, value, source, metadata) {
value.value;
const executableDef = value.executableDef;
const paramNames = executableDef?.paramNames || [];
let originalMetadata = value.metadata || {};
if (originalMetadata.capturedShadowEnvs) {
originalMetadata = {
...originalMetadata,
capturedShadowEnvs: this.deserializeShadowEnvs(originalMetadata.capturedShadowEnvs)
};
}
if (originalMetadata.capturedModuleEnv) {
const deserializedEnv = this.deserializeModuleEnv(originalMetadata.capturedModuleEnv);
for (const [_, variable] of deserializedEnv) {
if (variable.type === "executable" && variable.metadata) {
variable.metadata.capturedModuleEnv = deserializedEnv;
}
}
originalMetadata = {
...originalMetadata,
capturedModuleEnv: deserializedEnv
};
}
const enhancedMetadata = {
...metadata,
...originalMetadata,
// Preserve ALL original metadata including capturedShadowEnvs
isImported: true,
importPath: metadata.importPath,
executableDef
// This is what actually matters for execution
};
const execVariable = createExecutableVariable(
name,
"command",
// Default type - the real type is in executableDef
"",
// Empty template - the real template is in executableDef
paramNames,
void 0,
// No language here - it's in executableDef
source,
enhancedMetadata
);
return execVariable;
}
/**
* Check if a value contains complex AST nodes that need evaluation
*/
hasComplexContent(value) {
if (value === null || typeof value !== "object") {
return false;
}
if (this.isVariableLike(value)) {
return false;
}
if (value.type) {
return true;
}
if (value.__executable) {
return true;
}
if (Array.isArray(value)) {
return value.some((item) => this.hasComplexContent(item));
}
for (const prop of Object.values(value)) {
if (this.hasComplexContent(prop)) {
return true;
}
}
return false;
}
isVariableLike(value) {
return value && typeof value === "object" && typeof value.type === "string" && "name" in value && "value" in value && "source" in value && "createdAt" in value && "modifiedAt" in value;
}
/**
* Infer variable type from value
*/
inferVariableType(value) {
if (isStructuredValue(value)) {
return "structured";
} else if (Array.isArray(value)) {
return "array";
} else if (value && typeof value === "object") {
return "object";
} else if (typeof value === "string") {
return "simple-text";
} else {
return "simple-text";
}
}
/**
* Check if a variable is a legitimate mlld variable that can be exported/imported.
* System variables (marked with metadata.isSystem) are excluded from exports to prevent
* namespace collisions when importing multiple modules with system variables like @fm.
*/
isLegitimateVariableForExport(variable) {
if (variable.metadata?.isSystem) {
return false;
}
return true;
}
};
__name(_VariableImporter, "VariableImporter");
var VariableImporter = _VariableImporter;
export { VariableImporter };
//# sourceMappingURL=chunk-B5K53GVD.mjs.map
//# sourceMappingURL=chunk-B5K53GVD.mjs.map