UNPKG

mlld

Version:

mlld: llm scripting language

589 lines (587 loc) 21.6 kB
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