142 lines
2.3 KiB
JavaScript
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;
|
|
}
|