catgirl-calculus/src/compile.mjs

142 lines
2.3 KiB
JavaScript

import Line from "./Line.mjs";
import Expr from "./Expr.mjs";
export function compile (program) {
let env = new Map();
for (const line of program) {
const result = compile_line(env, line);
if (!result.valid) {
return {
valid: false,
env: env,
};
}
env = result.env;
}
return {
valid: true,
env: env,
};
}
export function compile_line (env, line) {
function invalid (current_env = env) {
return {
valid: false,
env: current_env,
};
}
if (line.type === Line.Invalid) return invalid();
if (line.type === Line.Empty || line.type === Line.Comment) {
return {
valid: true,
env: env,
};
}
if (line.type === Line.Special) {
// as there are currently no reserved special lines,
// and implementation-specific ones are handled by the REPL code,
// every special line that reaches this point is invalid.
return invalid();
}
if (line.type === Line.Binding) {
let value;
let valid = true;
try {
value = evaluate(env, line.body);
} catch (e) {
valid = false;
}
if (!valid) return invalid();
const new_env = new Map(env);
new_env.set(line.name, value);
return {
valid: true,
env: new_env,
fn: value,
};
}
if (line.type === Line.Expression) {
let value;
let valid = true;
try {
value = evaluate(env, line.value);
} catch (e) {
valid = false;
}
if (!valid) return invalid();
return {
valid: true,
env: env,
value: value,
};
}
return invalid();
}
function evaluate (env, expr, locals = []) {
if (expr.type === Expr.Token) {
const token = expr.value;
if (has_local(locals, token)) {
return get_local(locals, token);
}
if (env.has(token)) {
return env.get(token);
}
throw new Error();
}
if (expr.type === Expr.Application) {
const fn = evaluate(env, expr.fn, locals);
const arg = evaluate(env, expr.arg, locals);
return([fn, arg]);
}
if (expr.type === Expr.Fn) {
const args = [ ...expr.args ].reverse();
const body = evaluate(env, expr.body, args);
return compile_function(args, body);
}
throw new Error();
}
function compile_function (args, body) {
if (args.length > 1) {
return [0, compile_function(args.slice(1), body)];
} else {
return [0, body];
}
}
function has_local (locals, name) {
return locals.includes(name);
}
function get_local (locals, name) {
return locals.indexOf(name) + 1;
}