#!/usr/bin/env node /** * wrap-modules.js — Add define-library wrappers and import declarations * to browser .sx SOURCE files for lazy loading support. * * Targets the real source locations (spec/, web/, lib/), NOT dist/. * Run bundle.sh after to copy to dist/, then compile-modules.js. * * - 8 unwrapped files get define-library + export + begin wrappers * - 4 already-wrapped files get dependency import declarations * - boot.sx gets imports (stays unwrapped — entry point) */ const fs = require('fs'); const path = require('path'); const ROOT = path.resolve(__dirname, '..', '..', '..'); // Source file → library name (null = entry point) const MODULES = { // Spec modules 'spec/render.sx': { lib: '(sx render)', deps: [] }, 'spec/signals.sx': { lib: '(sx signals)', deps: [] }, 'web/web-signals.sx': { lib: '(sx signals-web)', deps: ['(sx dom)', '(sx browser)'] }, 'web/deps.sx': { lib: '(web deps)', deps: [] }, 'web/router.sx': { lib: '(web router)', deps: [] }, 'web/page-helpers.sx': { lib: '(web page-helpers)', deps: [] }, // Lib modules 'lib/freeze.sx': { lib: '(sx freeze)', deps: [] }, 'lib/highlight.sx': { lib: '(sx highlight)', deps: [] }, 'lib/bytecode.sx': { lib: '(sx bytecode)', deps: [] }, 'lib/compiler.sx': { lib: '(sx compiler)', deps: [] }, 'lib/vm.sx': { lib: '(sx vm)', deps: [] }, // Web FFI 'web/lib/dom.sx': { lib: '(sx dom)', deps: [] }, 'web/lib/browser.sx': { lib: '(sx browser)', deps: [] }, // Web adapters 'web/adapter-html.sx': { lib: '(web adapter-html)', deps: ['(sx render)'] }, 'web/adapter-sx.sx': { lib: '(web adapter-sx)', deps: ['(web boot-helpers)'] }, 'web/adapter-dom.sx': { lib: '(web adapter-dom)', deps: ['(sx dom)', '(sx render)'] }, // Web framework 'web/lib/boot-helpers.sx': { lib: '(web boot-helpers)', deps: ['(sx dom)', '(sx browser)', '(web adapter-dom)'] }, 'web/lib/hypersx.sx': { lib: '(sx hypersx)', deps: [] }, 'web/engine.sx': { lib: '(web engine)', deps: ['(web boot-helpers)', '(sx dom)', '(sx browser)'] }, 'web/orchestration.sx': { lib: '(web orchestration)', deps: ['(web boot-helpers)', '(sx dom)', '(sx browser)', '(web adapter-dom)', '(web engine)'] }, 'web/boot.sx': { lib: null, deps: ['(sx dom)', '(sx browser)', '(web boot-helpers)', '(web adapter-dom)', '(sx signals)', '(sx signals-web)', '(web router)', '(web page-helpers)', '(web orchestration)', '(sx render)', '(sx bytecode)', '(sx compiler)', '(sx vm)'] }, // Test harness 'spec/harness.sx': { lib: '(sx harness)', deps: [] }, 'web/harness-reactive.sx': { lib: '(sx harness-reactive)', deps: [] }, 'web/harness-web.sx': { lib: '(sx harness-web)', deps: [] }, }; // Extract top-level define names from source. // Handles both `(define name ...)` and `(define\n name ...)` formats. function extractDefineNames(source) { const names = []; const lines = source.split('\n'); let depth = 0; let expectName = false; for (const line of lines) { if (depth === 0) { const m = line.match(/^\(define\s+\(?(\S+)/); if (m) { names.push(m[1]); expectName = false; } else if (line.match(/^\(define\s*$/)) { expectName = true; } } else if (depth === 1 && expectName) { const m = line.match(/^\s+(\S+)/); if (m) { names.push(m[1]); expectName = false; } } for (const ch of line) { if (ch === '(') depth++; else if (ch === ')') depth--; } } return names; } function processFile(relPath, info) { const filePath = path.join(ROOT, relPath); if (!fs.existsSync(filePath)) { console.log(' SKIP', relPath, '(not found)'); return; } let source = fs.readFileSync(filePath, 'utf8'); const { lib, deps } = info; const hasWrapper = source.includes('(define-library'); const hasDepImports = deps.length > 0 && source.match(/^\(import\s+\(/m) && !source.match(/^\(import\s+\(\w+ \w+\)\)\s*$/m); // more than just self-import // Skip files with no deps and already wrapped (or no wrapper needed) if (deps.length === 0 && (hasWrapper || !lib)) { console.log(' ok', relPath, '(no changes needed)'); return; } // Build import lines for deps const importLines = deps.map(d => `(import ${d})`).join('\n'); // CASE 1: Entry point (boot.sx) — just add imports at top if (!lib) { if (deps.length > 0 && !source.startsWith('(import')) { source = importLines + '\n\n' + source; fs.writeFileSync(filePath, source); console.log(' +imports', relPath, `(${deps.length} deps, entry point)`); } else { console.log(' ok', relPath, '(entry point, already has imports)'); } return; } // CASE 2: Already wrapped — add imports before define-library if (hasWrapper) { if (deps.length > 0) { // Check if imports already present const firstImportCheck = deps[0].replace(/[()]/g, '\\$&'); if (source.match(new RegExp('\\(import ' + firstImportCheck))) { console.log(' ok', relPath, '(already has dep imports)'); return; } const dlIdx = source.indexOf('(define-library'); source = source.slice(0, dlIdx) + importLines + '\n\n' + source.slice(dlIdx); fs.writeFileSync(filePath, source); console.log(' +imports', relPath, `(${deps.length} deps)`); } return; } // CASE 3: Needs full wrapping if (deps.length === 0 && !hasWrapper) { // Wrap with no deps const names = extractDefineNames(source); if (names.length === 0) { console.log(' WARN', relPath, '— no defines found, skipping'); return; } const wrapped = buildWrapped(lib, names, source, ''); fs.writeFileSync(filePath, wrapped); console.log(' wrapped', relPath, `as ${lib} (${names.length} exports)`); return; } // Wrap with deps const names = extractDefineNames(source); if (names.length === 0) { console.log(' WARN', relPath, '— no defines found, skipping'); return; } const wrapped = buildWrapped(lib, names, source, importLines); fs.writeFileSync(filePath, wrapped); console.log(' wrapped', relPath, `as ${lib} (${names.length} exports, ${deps.length} deps)`); } function buildWrapped(libName, exportNames, bodySource, importSection) { const parts = []; // Dependency imports (top-level, before define-library) if (importSection) { parts.push(importSection); parts.push(''); } // define-library header parts.push(`(define-library ${libName}`); parts.push(` (export ${exportNames.join(' ')})`); parts.push(' (begin'); parts.push(''); // Body (original source, indented) parts.push(bodySource); parts.push(''); // Close begin + define-library parts.push('))'); parts.push(''); // Self-import for backward compat parts.push(`;; Re-export to global env`); parts.push(`(import ${libName})`); parts.push(''); return parts.join('\n'); } console.log('Processing source .sx files...\n'); for (const [relPath, info] of Object.entries(MODULES)) { processFile(relPath, info); } console.log('\nDone! Now run:'); console.log(' bash hosts/ocaml/browser/bundle.sh'); console.log(' node hosts/ocaml/browser/compile-modules.js');