tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

module-stage.ts (4481B)


      1 // Decode the pinned native compiler's header API.
      2 import type { Graph, NodeId } from './graph.js';
      3 import { readList, readText, text } from './codecs.js';
      4 import { compilerDiagnostic, SOURCE_LIMIT } from './compiler.js';
      5 import { fetchBytes, decodeBundle, type Bundle } from './bundle.js';
      6 import { packageIndex } from './packages.js';
      7 import { localModuleInputs, validateLocalModule, type LocalModuleInput } from './local-modules.js';
      8 import { fetchModule } from './module-resolver.js';
      9 export interface ModuleStage {
     10   body: string;
     11   imports: [string, string][]; // immutable reference, qualifier (or *)
     12   exports: [string, string][]; // local name/*, or from + immutable reference
     13   native: boolean;
     14 }
     15 export function stageModule(graph: Graph, library: Map<string, NodeId>, source: string): ModuleStage {
     16   const call = (name: string, argument: NodeId): NodeId => {
     17     const f = library.get('source_module.' + name);
     18     if (f === undefined) throw new Error('Missing native module compiler helper');
     19     return graph.apply(f, [argument]);
     20   };
     21   const value = (result: NodeId, diagnostic = false): NodeId => {
     22     const [tag, payload] = graph.pair(result), flag = graph.observe(tag).node;
     23     if (flag.kind === 'leaf') throw new Error(diagnostic ? compilerDiagnostic(graph, payload) : readText(graph, graph.pair(payload)[0], 512));
     24     if (flag.kind !== 'stem' || graph.observe(flag.child).node.kind !== 'leaf') throw new Error('Invalid native module result');
     25     return graph.pair(payload)[0];
     26   };
     27   const [edges, rest] = graph.pair(value(call('sourceModuleHeaderStage', text(graph, source, SOURCE_LIMIT)), true));
     28   if (readList(graph, edges, SOURCE_LIMIT).length) throw new Error('Browser modules require manifest hashes; workspace and build-only sources are unavailable');
     29   const [uses, tail] = graph.pair(rest), [exports, body] = graph.pair(tail);
     30   const shapes = (rows: NodeId, helper: string): [string, string][] => readList(graph, rows, SOURCE_LIMIT).map(row => {
     31     const [a, b] = graph.pair(value(call(helper, row)));
     32     return [readText(graph, a, 128), readText(graph, b, 128)];
     33   });
     34   const imports = shapes(uses, 'sourceModuleUseShape'), selected = shapes(exports, 'sourceModuleExportShape');
     35   return { body: readText(graph, body, SOURCE_LIMIT), imports, exports: selected, native: !!(imports.length || selected.length) };
     36 }
     37 export async function moduleDependencies(base: URL, stage: ModuleStage, local: LocalModuleInput[] = []): Promise<Map<string, Bundle>> {
     38   const refs = [...new Set([...stage.imports.map(i => i[0]), ...stage.exports.filter(e => e[0] === 'from').map(e => e[1])])];
     39   const result = new Map<string, Bundle>();
     40   if (!refs.length) return result;
     41   const available = new Map(localModuleInputs(local).map(entry => [entry.manifest, entry]));
     42   let index: ReturnType<typeof packageIndex> | undefined;
     43   for (const ref of refs) {
     44     const entry = available.get(ref);
     45     if (entry) {
     46       const checked = await validateLocalModule(entry.bytes);
     47       if (checked.manifest !== ref) throw new Error('Local module manifest mismatch');
     48       result.set(ref, decodeBundle(checked.bytes));
     49     } else {
     50       index ??= packageIndex(JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(await fetchBytes(new URL('packages.json', base), 2_097_152))) as unknown);
     51       result.set(ref, await fetchModule(base, ref, index));
     52     }
     53   }
     54   return result;
     55 }
     56 // Projection of already native-parsed export requests onto backend-owned trees.
     57 // Keep executable trees as backend-owned handles during interface selection.
     58 export function selectModuleExports<T>(stage: ModuleStage, locals: Map<string, T>, modules: Map<string, Map<string, T>>): Map<string, T> {
     59   const selected = new Map<string, T>();
     60   const add = (name: string, tree: T): void => {
     61     if (selected.has(name)) throw new Error('duplicate export');
     62     if (selected.size >= 4096) throw new Error('Module export limit exceeded');
     63     selected.set(name, tree);
     64   };
     65   for (const [name, ref] of stage.exports) {
     66     if (name === 'from') {
     67       const values = modules.get(ref); if (!values) throw new Error('Missing re-export dependency');
     68       for (const [key, value] of values) add(key, value);
     69     } else if (name === '*') { for (const [key, value] of locals) add(key, value); }
     70     else { if (!locals.has(name)) throw new Error('undeclared local export'); add(name, locals.get(name)!); }
     71   }
     72   if (!selected.size) throw new Error('module requires an export');
     73   return selected;
     74 }