|
|
|
|
@ -1,6 +1,10 @@
|
|
|
|
|
import type { AST, Pattern, Definition } from './ast';
|
|
|
|
|
import { _rt, store } from './runtime-js';
|
|
|
|
|
|
|
|
|
|
const definitions: Map<string, AST> = new Map();
|
|
|
|
|
const dependencies: Map<string, Set<string>> = new Map();
|
|
|
|
|
const dependents: Map<string, Set<string>> = new Map();
|
|
|
|
|
|
|
|
|
|
export function compile(ast: AST): string {
|
|
|
|
|
switch (ast.kind) {
|
|
|
|
|
case 'literal':
|
|
|
|
|
@ -15,7 +19,7 @@ export function compile(ast: AST): string {
|
|
|
|
|
|
|
|
|
|
case 'lambda':
|
|
|
|
|
const params = ast.params.map(sanitize).join(') => (');
|
|
|
|
|
return `((${params}) => ${compile(ast.body)})`;
|
|
|
|
|
return `Object.assign((${params}) => ${compile(ast.body)}, { _ast: (${JSON.stringify(ast)}) })`;
|
|
|
|
|
|
|
|
|
|
case 'apply':
|
|
|
|
|
// Constructor
|
|
|
|
|
@ -99,7 +103,7 @@ function sanitize(name: string): string {
|
|
|
|
|
|
|
|
|
|
if (ops[name]) return ops[name];
|
|
|
|
|
|
|
|
|
|
const natives = ['measure', 'measureText', 'storeSearch', 'debug', 'len', 'slice', 'str'];
|
|
|
|
|
const natives = ['measure', 'measureText', 'storeSearch', 'debug', 'len', 'slice', 'str', 'redefine'];
|
|
|
|
|
if (natives.includes(name)) return `_rt.${name}`;
|
|
|
|
|
|
|
|
|
|
const reserved = [
|
|
|
|
|
@ -201,6 +205,27 @@ function compilePattern(pattern: Pattern, expr: string): { condition: string, bi
|
|
|
|
|
export function compileAndRun(defs: Definition[]) {
|
|
|
|
|
const compiledDefs: string[] = [];
|
|
|
|
|
|
|
|
|
|
const topLevel = new Set(defs.map(d => d.name));
|
|
|
|
|
|
|
|
|
|
for (const def of defs) {
|
|
|
|
|
definitions.set(def.name, def.body);
|
|
|
|
|
const free = freeVars(def.body);
|
|
|
|
|
const deps = new Set([...free].filter(v => topLevel.has(v)));
|
|
|
|
|
dependencies.set(def.name, deps);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dependents.clear();
|
|
|
|
|
for (const name of topLevel) {
|
|
|
|
|
dependents.set(name, new Set());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const [name, deps] of dependencies) {
|
|
|
|
|
for (const dep of deps) {
|
|
|
|
|
dependents.get(dep)?.add(name);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (const def of defs) {
|
|
|
|
|
const compiled = `const ${sanitize(def.name)} = ${compile(def.body)};`;
|
|
|
|
|
compiledDefs.push(compiled);
|
|
|
|
|
@ -232,3 +257,125 @@ return { ${defNames}, __result: ${sanitize(lastName)} };`;
|
|
|
|
|
|
|
|
|
|
return allDefs.__result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function freeVars(ast: AST, bound: Set<string> = new Set()): Set<string> {
|
|
|
|
|
switch (ast.kind) {
|
|
|
|
|
case 'literal':
|
|
|
|
|
case 'constructor':
|
|
|
|
|
return new Set();
|
|
|
|
|
|
|
|
|
|
case 'variable':
|
|
|
|
|
return bound.has(ast.name) ? new Set() : new Set([ast.name]);
|
|
|
|
|
|
|
|
|
|
case 'lambda': {
|
|
|
|
|
const newBound = new Set([...bound, ...ast.params]);
|
|
|
|
|
return freeVars(ast.body, newBound);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case 'let': {
|
|
|
|
|
const valueVars = freeVars(ast.value, bound);
|
|
|
|
|
const bodyVars = freeVars(ast.body, new Set([...bound, ast.name]));
|
|
|
|
|
return new Set([...valueVars, ...bodyVars]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case 'apply': {
|
|
|
|
|
const funcVars = freeVars(ast.func, bound);
|
|
|
|
|
const argVars = ast.args.flatMap(a => [...freeVars(a, bound)])
|
|
|
|
|
return new Set([...funcVars, ...argVars]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case 'record': {
|
|
|
|
|
const allVars = Object.values(ast.fields).flatMap(v => [...freeVars(v, bound)]);
|
|
|
|
|
return new Set(allVars);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case 'list': {
|
|
|
|
|
const allVars = ast.elements.flatMap(e =>
|
|
|
|
|
'spread' in e ? [...freeVars(e.spread, bound)]
|
|
|
|
|
: [...freeVars(e, bound)]);
|
|
|
|
|
return new Set(allVars);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case 'record-access':
|
|
|
|
|
return freeVars(ast.record, bound);
|
|
|
|
|
|
|
|
|
|
case 'record-update': {
|
|
|
|
|
const recordVars = freeVars(ast.record, bound);
|
|
|
|
|
const updateVars = Object.values(ast.updates).flatMap(v => [...freeVars(v, bound)]);
|
|
|
|
|
return new Set([...recordVars, ...updateVars]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case 'match': {
|
|
|
|
|
const exprVars = freeVars(ast.expr, bound);
|
|
|
|
|
const caseVars = ast.cases.flatMap(c => {
|
|
|
|
|
const patternBindings = patternVars(c.pattern);
|
|
|
|
|
const newBound = new Set([...bound, ...patternBindings]);
|
|
|
|
|
return [...freeVars(c.result, newBound)];
|
|
|
|
|
})
|
|
|
|
|
return new Set([...exprVars, ...caseVars]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case 'rebind':
|
|
|
|
|
return freeVars(ast.value, bound);
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
return new Set();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function patternVars(pattern: Pattern): string[] {
|
|
|
|
|
switch (pattern.kind) {
|
|
|
|
|
case 'var':
|
|
|
|
|
return [pattern.name];
|
|
|
|
|
case 'constructor':
|
|
|
|
|
return pattern.args.flatMap(patternVars);
|
|
|
|
|
case 'list':
|
|
|
|
|
return pattern.elements.flatMap(patternVars);
|
|
|
|
|
case 'list-spread':
|
|
|
|
|
return [...pattern.head.flatMap(patternVars), pattern.spread];
|
|
|
|
|
case 'record':
|
|
|
|
|
return Object.values(pattern.fields).flatMap(patternVars);
|
|
|
|
|
default:
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function recompile(name: string, newAst: AST) {
|
|
|
|
|
definitions.set(name, newAst);
|
|
|
|
|
|
|
|
|
|
const topLevel = new Set(definitions.keys());
|
|
|
|
|
const free = freeVars(newAst);
|
|
|
|
|
const newDeps = new Set([...free].filter(v => topLevel.has(v)));
|
|
|
|
|
|
|
|
|
|
const oldDeps = dependencies.get(name) || new Set();
|
|
|
|
|
for (const dep of oldDeps) {
|
|
|
|
|
dependents.get(dep)?.delete(name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const dep of newDeps) {
|
|
|
|
|
dependents.get(dep)?.add(name);
|
|
|
|
|
}
|
|
|
|
|
dependencies.set(name, newDeps);
|
|
|
|
|
|
|
|
|
|
const toRecompile: string[] = [];
|
|
|
|
|
const visited = new Set<string>();
|
|
|
|
|
|
|
|
|
|
function collectDependents(n: string) {
|
|
|
|
|
if (visited.has(n)) return;
|
|
|
|
|
visited.add(n);
|
|
|
|
|
toRecompile.push(n);
|
|
|
|
|
for (const dep of dependents.get(n) || []) {
|
|
|
|
|
collectDependents(dep);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
collectDependents(name);
|
|
|
|
|
|
|
|
|
|
for (const defName of toRecompile) {
|
|
|
|
|
const ast = definitions.get(defName)!;
|
|
|
|
|
const compiled = compile(ast);
|
|
|
|
|
|
|
|
|
|
const fn = new Function('_rt', 'store', `return ${compiled}`);
|
|
|
|
|
store[defName] = fn(_rt, store);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|