compiling. interpreting was too slow
This commit is contained in:
parent
6edf592637
commit
2cd5a609bb
8 changed files with 650 additions and 21 deletions
245
src/compiler.ts
Normal file
245
src/compiler.ts
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
import type { AST, Pattern, Definition } from './ast';
|
||||
import { _rt, store } from './runtime-js';
|
||||
|
||||
export function compile(ast: AST): string {
|
||||
switch (ast.kind) {
|
||||
case 'literal':
|
||||
if (ast.value.kind === 'string')
|
||||
return JSON.stringify(ast.value.value);
|
||||
if (ast.value.kind === 'int' || ast.value.kind === 'float')
|
||||
return JSON.stringify(ast.value.value);
|
||||
throw new Error(`Cannot compile literal of kind ${ast.value.kind}`);
|
||||
|
||||
case 'variable':
|
||||
return sanitize(ast.name);
|
||||
|
||||
case 'lambda':
|
||||
const params = ast.params.map(sanitize).join(') => (');
|
||||
return `((${params}) => ${compile(ast.body)})`;
|
||||
|
||||
case 'apply':
|
||||
// Constructor
|
||||
if (ast.func.kind === 'constructor') {
|
||||
const ctorName = ast.func.name;
|
||||
const arg = compile(ast.args[0]);
|
||||
return `((_a) => _a && typeof _a === 'object' && !Array.isArray(_a) && !_a._tag
|
||||
? { _tag: "${ctorName}", ..._a }
|
||||
: { _tag: "${ctorName}", _0: _a })(${arg})`;
|
||||
}
|
||||
|
||||
const args = ast.args.map(compile).join(')(');
|
||||
return `${compile(ast.func)}(${args})`;
|
||||
|
||||
case 'record': {
|
||||
const fields = Object.entries(ast.fields)
|
||||
.map(([k, v]) => `${sanitize(k)}: ${compile(v)}`);
|
||||
return `({${fields.join(', ')}})`;
|
||||
}
|
||||
|
||||
case 'list': {
|
||||
const elements = ast.elements.map(e =>
|
||||
'spread' in e ? `...${compile(e.spread)}` : compile(e)
|
||||
);
|
||||
return `[${elements.join(', ')}]`;
|
||||
}
|
||||
|
||||
case 'record-access':
|
||||
return `${compile(ast.record)}.${sanitize(ast.field)}`;
|
||||
|
||||
case 'record-update':
|
||||
const updates = Object.entries(ast.updates)
|
||||
.map(([k, v]) => `${sanitize(k)}: ${compile(v)}`);
|
||||
return `({...${compile(ast.record)}, ${updates.join(', ')}})`;
|
||||
|
||||
case 'let':
|
||||
return `((${sanitize(ast.name)}) =>
|
||||
${compile(ast.body)})(${compile(ast.value)})`;
|
||||
|
||||
case 'match':
|
||||
return compileMatch(ast);
|
||||
|
||||
case 'constructor':
|
||||
return `({ _tag: "${ast.name}" })`;
|
||||
/*
|
||||
return `((arg) => arg && typeof arg === 'object' && !arg._tag
|
||||
? { _tag: "${ast.name}", ...arg }
|
||||
: { _tag: "${ast.name}", _0: arg })`;
|
||||
*/
|
||||
|
||||
case 'rebind': {
|
||||
if (ast.target.kind === 'variable') {
|
||||
return `({ _tag: "Rebind", _0: "${ast.target.name}", _1: ${compile(ast.value)} })`;
|
||||
} else if (ast.target.kind === 'record-access') {
|
||||
let current: AST = ast.target;
|
||||
const path: string[] = [];
|
||||
while (current.kind === 'record-access') {
|
||||
path.unshift(current.field);
|
||||
current = current.record;
|
||||
}
|
||||
if (current.kind === 'variable') {
|
||||
return `({ _tag: "Rebind", _0: "${current.name}", _1: ${JSON.stringify(path)}, _2: ${compile(ast.value)} })`;
|
||||
}
|
||||
}
|
||||
throw new Error('Invalid rebind target');
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(`Cannot compile ${ast.kind}`);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function sanitize(name: string): string {
|
||||
const ops: Record<string, string> = {
|
||||
'add': '_rt.add', 'sub': '_rt.sub', 'mul': '_rt.mul',
|
||||
'div': '_rt.div', 'mod': '_rt.mod', 'eq': '_rt.eq',
|
||||
'cat': '_rt.cat', 'gt': '_rt.gt', 'lt': '_rt.lt',
|
||||
'gte': '_rt.gte', 'lte': '_rt.lte', 'max': '_rt.max', 'min': '_rt.min',
|
||||
};
|
||||
|
||||
if (ops[name]) return ops[name];
|
||||
|
||||
const natives = ['measureText', 'storeSearch', 'debug', 'len', 'slice', 'str'];
|
||||
if (natives.includes(name)) return `_rt.${name}`;
|
||||
|
||||
const reserved = [
|
||||
'default','class','function','return','const','let','var',
|
||||
'if','else','switch','case','for','while','do','break',
|
||||
'continue','new','delete','typeof','in','this','super',
|
||||
'import','export','extends','static','yield','await','async',
|
||||
'try','catch','finally','throw','null','true','false'
|
||||
];
|
||||
if (reserved.includes(name)) return `_${name}`;
|
||||
|
||||
return name.replace(/-/g, '_');
|
||||
}
|
||||
|
||||
function compileMatch(ast: AST & { kind: 'match'}): string {
|
||||
const expr = compile(ast.expr);
|
||||
const tmpVar = `_m${Math.floor(Math.random() * 10000)}`;
|
||||
|
||||
let code = `((${tmpVar}) => { `;
|
||||
|
||||
for (const c of ast.cases) {
|
||||
const { condition, bindings } = compilePattern(c.pattern, tmpVar);
|
||||
code += `if (${condition}) { `;
|
||||
if (bindings.length > 0) {
|
||||
code += `const ${bindings.join(', ')}; `;
|
||||
}
|
||||
code += `return ${compile(c.result)}; }`;
|
||||
}
|
||||
|
||||
code += `console.error("No match for:", ${tmpVar}); throw new Error("No match"); })(${expr})`;
|
||||
return code;
|
||||
}
|
||||
|
||||
function compilePattern(pattern: Pattern, expr: string): { condition: string, bindings: string[] } {
|
||||
switch (pattern.kind) {
|
||||
case 'wildcard':
|
||||
return { condition: 'true', bindings: [] };
|
||||
|
||||
case 'var':
|
||||
return { condition: 'true', bindings: [`${sanitize(pattern.name)} = ${expr}`] };
|
||||
|
||||
case 'literal':
|
||||
return { condition: `${expr} === ${JSON.stringify(pattern.value)}`, bindings: [] };
|
||||
|
||||
case 'constructor': {
|
||||
let condition = `${expr}?._tag === "${pattern.name}"`;
|
||||
const bindings: string[] = [];
|
||||
pattern.args.forEach((argPattern, i) => {
|
||||
const sub = compilePattern(argPattern, `${expr}._${i}`);
|
||||
condition += ` && ${sub.condition}`;
|
||||
bindings.push(...sub.bindings);
|
||||
});
|
||||
|
||||
return { condition, bindings };
|
||||
}
|
||||
|
||||
case 'list': {
|
||||
let condition = `Array.isArray(${expr}) && ${expr}.length === ${pattern.elements.length}`;
|
||||
const bindings: string[] = [];
|
||||
pattern.elements.forEach((elemPattern, i) => {
|
||||
const sub = compilePattern(elemPattern, `${expr}[${i}]`);
|
||||
if (sub.condition !== 'true') condition += ` && ${sub.condition}`;
|
||||
bindings.push(...sub.bindings);
|
||||
});
|
||||
|
||||
return { condition, bindings };
|
||||
}
|
||||
|
||||
case 'list-spread': {
|
||||
let condition = `Array.isArray(${expr}) && ${expr}.length >= ${pattern.head.length}`;
|
||||
const bindings: string[] = [];
|
||||
pattern.head.forEach((elemPattern, i) => {
|
||||
const sub = compilePattern(elemPattern, `${expr}[${i}]`);
|
||||
if (sub.condition !== 'true') condition += ` && ${sub.condition}`;
|
||||
bindings.push(...sub.bindings);
|
||||
});
|
||||
bindings.push(`${sanitize(pattern.spread)} = ${expr}.slice(${pattern.head.length})`);
|
||||
|
||||
return { condition, bindings };
|
||||
}
|
||||
|
||||
case 'record': {
|
||||
let condition = 'true';
|
||||
const bindings: string[] = [];
|
||||
for (const [field, fieldPattern] of Object.entries(pattern.fields)) {
|
||||
const sub = compilePattern(fieldPattern, `${expr}.${sanitize(field)}`);
|
||||
if (sub.condition !== 'true') condition += ` && ${sub.condition}`;
|
||||
bindings.push(...sub.bindings);
|
||||
}
|
||||
|
||||
return { condition, bindings };
|
||||
}
|
||||
|
||||
default:
|
||||
return { condition: 'true', bindings: [] };
|
||||
}
|
||||
}
|
||||
|
||||
export function compileAndRun(defs: Definition[]) {
|
||||
const compiledDefs: string[] = [];
|
||||
|
||||
for (const def of defs) {
|
||||
const compiled = `const ${sanitize(def.name)} = ${compile(def.body)};`;
|
||||
compiledDefs.push(compiled);
|
||||
|
||||
try {
|
||||
new Function('_rt', compiled);
|
||||
} catch (e) {
|
||||
console.error(`=== BROKEN: ${def.name} ===`);
|
||||
console.error(compiled);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
const compiledDefs = defs.map(def =>
|
||||
`const ${sanitize(def.name)} = ${compile(def.body)};`
|
||||
).join('\n');
|
||||
*/
|
||||
|
||||
const lastName = defs[defs.length - 1].name;
|
||||
const defNames = defs.map(d => sanitize(d.name)).join(', ');
|
||||
|
||||
const code = `${compiledDefs.join('\n')}
|
||||
return { ${defNames}, __result: ${sanitize(lastName)} };`;
|
||||
|
||||
// console.log('--- Compiled Code ---');
|
||||
// console.log(code);
|
||||
// console.log('=====================');
|
||||
|
||||
const fn = new Function('_rt', code);
|
||||
const allDefs = fn(_rt);
|
||||
|
||||
// Populate store
|
||||
for (const [name, value] of Object.entries(allDefs)) {
|
||||
if (name !== '__result') {
|
||||
store[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return allDefs.__result;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue