Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save lancejpollard/a3ef64274f73ee1e723af9141701ee38 to your computer and use it in GitHub Desktop.

Select an option

Save lancejpollard/a3ef64274f73ee1e723af9141701ee38 to your computer and use it in GitHub Desktop.
SupVM - distilled version of HVM for SupGen - one shot by GPT 5.5
#!/usr/bin/env node
// SupVM.ts
// ========
// A tiny evaluator for the HVM subset used by InsertGen_hardcoded.hvm.
// Sup-fork captures are the only DUP form needed by that file. SupVM gives
// them the same observable branch correlation through a labelled-choice map,
// and charges captured variables as DUP-like interactions.
declare function require(nam: string): any;
declare var process: any;
var fs = require("node:fs");
// Source terms
// ============
export type Term =
| Term$And
| Term$App
| Term$Ctr
| Term$Eql
| Term$Era
| Term$Lam
| Term$Let
| Term$Mat
| Term$Num
| Term$Ref
| Term$Sup
| Term$Var;
export type Term$And = { $: "And", lft: Term, rgt: Term };
export type Term$App = { $: "App", fun: Term, arg: Term };
export type Term$Ctr = { $: "Ctr", nam: string, fds: Term[] };
export type Term$Eql = { $: "Eql", lft: Term, rgt: Term };
export type Term$Era = { $: "Era" };
export type Term$Lam = { $: "Lam", nam: string, bod: Term };
export type Term$Let = { $: "Let", nam: string, val: Term, bod: Term };
export type Term$Mat = { $: "Mat", css: Case[] };
export type Term$Num = { $: "Num", val: number };
export type Term$Ref = { $: "Ref", nam: string };
export type Term$Sup = { $: "Sup", lab: string, lft: Term, rgt: Term, cap: number };
export type Term$Var = { $: "Var", nam: string };
export type Case = Case$Ctr | Case$Num;
export type Case$Ctr = { $: "Ctr", nam: string, bod: Term };
export type Case$Num = { $: "Num", val: number, bod: Term };
export type Book = Map<string, Term>;
// Runtime values
// ==============
export type Env = Map<string, Susp>;
export type Pick = 0 | 1;
export type Choices = Map<string, Pick>;
export type Susp =
| Susp$And
| Susp$App
| Susp$Eql
| Susp$Many
| Susp$Term
| Susp$Val;
export type Susp$And = { $: "And", lft: Susp, rgt: Susp };
export type Susp$App = { $: "App", fun: Susp, arg: Susp };
export type Susp$Eql = { $: "Eql", lft: Susp, rgt: Susp };
export type Susp$Many = { $: "Many", lft: Susp[], rgt: Susp[], idx: number };
export type Susp$Term = { $: "Term", term: Term, env: Env };
export type Susp$Val = { $: "Val", val: Val };
export type Val =
| Val$App
| Val$Ctr
| Val$Era
| Val$Lam
| Val$Mat
| Val$Num
| Val$Sup
| Val$Var;
export type Val$App = { $: "App", fun: Val, arg: Susp };
export type Val$Ctr = { $: "Ctr", nam: string, fds: Susp[] };
export type Val$Era = { $: "Era" };
export type Val$Lam = { $: "Lam", nam: string, bod: Term, env: Env };
export type Val$Mat = { $: "Mat", css: Case[], env: Env };
export type Val$Num = { $: "Num", val: number };
export type Val$Sup = { $: "Sup", lab: string, lft: Susp, rgt: Susp };
export type Val$Var = { $: "Var", nam: string };
// Normal forms
// ============
export type Norm =
| Norm$App
| Norm$Ctr
| Norm$Lam
| Norm$Mat
| Norm$Num
| Norm$Var;
export type Norm$App = { $: "App", fun: Norm, arg: Norm };
export type Norm$Ctr = { $: "Ctr", nam: string, fds: Norm[] };
export type Norm$Lam = { $: "Lam", nam: string, bod: Norm };
export type Norm$Mat = { $: "Mat", css: NormCase[] };
export type Norm$Num = { $: "Num", val: number };
export type Norm$Var = { $: "Var", nam: string };
export type NormCase = NormCase$Ctr | NormCase$Num;
export type NormCase$Ctr = { $: "Ctr", nam: string, bod: Norm };
export type NormCase$Num = { $: "Num", val: number, bod: Norm };
export type Cnf = Cnf$Era | Cnf$Fork | Cnf$Leaf;
export type Cnf$Era = { $: "Era" };
export type Cnf$Fork = { $: "Fork", lab: string, lft: Job, rgt: Job };
export type Cnf$Leaf = { $: "Leaf", val: Norm };
export type Job = (rt: Runtime, chs: Choices) => Cnf;
// Parser state
// ============
export type Parser = {
src: string,
pos: number,
};
export type Runtime = {
book: Book,
itrs: number,
fresh: number,
};
// Constructors
// ============
// Creates a variable term.
function Var(nam: string): Term {
return { $: "Var", nam };
}
// Creates a reference term.
function Ref(nam: string): Term {
return { $: "Ref", nam };
}
// Creates a lambda term.
function Lam(nam: string, bod: Term): Term {
return { $: "Lam", nam, bod };
}
// Creates an application term.
function App(fun: Term, arg: Term): Term {
return { $: "App", fun, arg };
}
// Creates a match term.
function Mat(css: Case[]): Term {
return { $: "Mat", css };
}
// Creates a constructor term.
function Ctr(nam: string, fds: Term[]): Term {
return { $: "Ctr", nam, fds };
}
// Creates a numeric term.
function Num(val: number): Term {
return { $: "Num", val };
}
// Creates an eraser term.
function Era(): Term {
return { $: "Era" };
}
// Creates a superposition term.
function Sup(lab: string, lft: Term, rgt: Term, cap: number): Term {
return { $: "Sup", lab, lft, rgt, cap };
}
// Creates a let term.
function Let(nam: string, val: Term, bod: Term): Term {
return { $: "Let", nam, val, bod };
}
// Creates an equality term.
function Eql(lft: Term, rgt: Term): Term {
return { $: "Eql", lft, rgt };
}
// Creates an and term.
function And(lft: Term, rgt: Term): Term {
return { $: "And", lft, rgt };
}
// Creates a term suspension.
function Sterm(term: Term, env: Env): Susp {
return { $: "Term", term, env };
}
// Creates a value suspension.
function Sval(val: Val): Susp {
return { $: "Val", val };
}
// Creates an application suspension.
function Sapp(fun: Susp, arg: Susp): Susp {
return { $: "App", fun, arg };
}
// Creates an equality suspension.
function Seql(lft: Susp, rgt: Susp): Susp {
return { $: "Eql", lft, rgt };
}
// Creates an and suspension.
function Sand(lft: Susp, rgt: Susp): Susp {
return { $: "And", lft, rgt };
}
// Creates a many-field equality suspension.
function Smany(lft: Susp[], rgt: Susp[], idx: number): Susp {
return { $: "Many", lft, rgt, idx };
}
// Creates a normal variable.
function NVar(nam: string): Norm {
return { $: "Var", nam };
}
// Creates a normal lambda.
function NLam(nam: string, bod: Norm): Norm {
return { $: "Lam", nam, bod };
}
// Creates a normal application.
function NApp(fun: Norm, arg: Norm): Norm {
return { $: "App", fun, arg };
}
// Creates a normal match.
function NMat(css: NormCase[]): Norm {
return { $: "Mat", css };
}
// Creates a normal constructor.
function NCtr(nam: string, fds: Norm[]): Norm {
return { $: "Ctr", nam, fds };
}
// Creates a normal number.
function NNum(val: number): Norm {
return { $: "Num", val };
}
// Source preprocessing
// ====================
// Removes line comments from a source file.
function strip_comments(src: string): string {
var out = "";
var lin = src.split(/\r?\n/g);
for (var idx = 0; idx < lin.length; idx++) {
var row = lin[idx];
var cut = row.indexOf("//");
if (cut >= 0) {
row = row.slice(0, cut);
}
out += row + "\n";
}
return out;
}
// Parser utilities
// ================
// Returns the current character.
function peek(ps: Parser): string {
return ps.src[ps.pos] || "";
}
// Returns a character at an offset.
function peek_at(ps: Parser, off: number): string {
return ps.src[ps.pos + off] || "";
}
// Tests whether parsing reached the end.
function at_end(ps: Parser): boolean {
return ps.pos >= ps.src.length;
}
// Skips whitespace.
function skip(ps: Parser): void {
while (!at_end(ps) && /\s/.test(peek(ps))) {
ps.pos += 1;
}
}
// Tests whether the source starts with a string.
function starts(ps: Parser, txt: string): boolean {
return ps.src.startsWith(txt, ps.pos);
}
// Consumes a string if present.
function match(ps: Parser, txt: string): boolean {
skip(ps);
if (!starts(ps, txt)) {
return false;
}
ps.pos += txt.length;
return true;
}
// Consumes a required string.
function consume(ps: Parser, txt: string): void {
skip(ps);
if (!starts(ps, txt)) {
throw new Error("expected '" + txt + "' at byte " + ps.pos);
}
ps.pos += txt.length;
}
// Tests whether a character can start a name.
function is_name_start(chr: string): boolean {
return /^[A-Za-z_$]$/.test(chr);
}
// Tests whether a character can continue a name.
function is_name_char(chr: string): boolean {
return /^[A-Za-z0-9_$]$/.test(chr);
}
// Parses a name.
function parse_name(ps: Parser): string {
skip(ps);
var chr = peek(ps);
if (!is_name_start(chr)) {
throw new Error("expected name at byte " + ps.pos);
}
var beg = ps.pos;
ps.pos += 1;
while (is_name_char(peek(ps))) {
ps.pos += 1;
}
return ps.src.slice(beg, ps.pos);
}
// Program parser
// ==============
// Parses a complete program.
function parse_program(src: string): Book {
var ps: Parser = { src: strip_comments(src), pos: 0 };
var bk: Book = new Map();
while (true) {
skip(ps);
if (at_end(ps)) {
return bk;
}
consume(ps, "@");
var nam = parse_name(ps);
consume(ps, "=");
var val = parse_term(ps);
bk.set(nam, val);
}
}
// Term parser
// ===========
// Parses a term.
function parse_term(ps: Parser): Term {
return parse_eql(ps);
}
// Parses equality.
function parse_eql(ps: Parser): Term {
var lhs = parse_cons(ps);
skip(ps);
if (starts(ps, "===")) {
ps.pos += 3;
var rhs = parse_eql(ps);
return Eql(lhs, rhs);
}
return lhs;
}
// Parses cons syntax.
function parse_cons(ps: Parser): Term {
var lhs = parse_post(ps);
skip(ps);
if (starts(ps, "<>")) {
ps.pos += 2;
var rhs = parse_term(ps);
return Ctr("Cons", [lhs, rhs]);
}
return lhs;
}
// Parses postfix function calls.
function parse_post(ps: Parser): Term {
var out = parse_atom(ps);
while (true) {
skip(ps);
if (!starts(ps, "(")) {
return out;
}
ps.pos += 1;
skip(ps);
if (starts(ps, ")")) {
ps.pos += 1;
continue;
}
while (true) {
var arg = parse_term(ps);
out = App(out, arg);
skip(ps);
if (starts(ps, ",")) {
ps.pos += 1;
skip(ps);
}
if (starts(ps, ")")) {
ps.pos += 1;
break;
}
}
}
}
// Parses an atomic term.
function parse_atom(ps: Parser): Term {
skip(ps);
var chr = peek(ps);
if (chr === "λ") {
ps.pos += 1;
return parse_lam(ps);
}
if (chr === "!") {
ps.pos += 1;
return parse_let(ps);
}
if (chr === "&") {
ps.pos += 1;
return parse_sup(ps);
}
if (chr === "@") {
ps.pos += 1;
return Ref(parse_name(ps));
}
if (chr === "(") {
ps.pos += 1;
var trm = parse_term(ps);
consume(ps, ")");
return trm;
}
if (chr === "[") {
return parse_list(ps);
}
if (/^[0-9]$/.test(chr)) {
return parse_num_or_nat(ps);
}
return Var(parse_name(ps));
}
// Parses a lambda or lambda-match body.
function parse_lam(ps: Parser): Term {
skip(ps);
if (starts(ps, "{")) {
return parse_matcher(ps);
}
var nms: string[] = [];
while (true) {
skip(ps);
if (starts(ps, "&")) {
ps.pos += 1;
}
nms.push(parse_name(ps));
skip(ps);
if (starts(ps, ",")) {
ps.pos += 1;
continue;
}
consume(ps, ".");
break;
}
var bod = parse_term(ps);
for (var idx = nms.length - 1; idx >= 0; idx--) {
bod = Lam(nms[idx], bod);
}
return bod;
}
// Parses a lambda-match term.
function parse_matcher(ps: Parser): Term {
consume(ps, "{");
var css: Case[] = [];
while (true) {
skip(ps);
if (starts(ps, "}")) {
ps.pos += 1;
return Mat(css);
}
var cse = parse_case(ps);
css.push(cse);
skip(ps);
if (starts(ps, ";")) {
ps.pos += 1;
}
}
}
// Parses one match case.
function parse_case(ps: Parser): Case {
skip(ps);
if (starts(ps, "[]")) {
ps.pos += 2;
consume(ps, ":");
return { $: "Ctr", nam: "Nil", bod: parse_term(ps) };
}
if (starts(ps, "<>")) {
ps.pos += 2;
consume(ps, ":");
return { $: "Ctr", nam: "Cons", bod: parse_term(ps) };
}
var num = parse_digits(ps);
if (starts(ps, "n")) {
ps.pos += 1;
if (num === 1 && starts(ps, "+")) {
ps.pos += 1;
consume(ps, ":");
return { $: "Ctr", nam: "Succ", bod: parse_term(ps) };
}
if (num === 0) {
consume(ps, ":");
return { $: "Ctr", nam: "Zero", bod: parse_term(ps) };
}
throw new Error("unsupported nat match at byte " + ps.pos);
}
consume(ps, ":");
return { $: "Num", val: num, bod: parse_term(ps) };
}
// Parses a non-empty digit sequence.
function parse_digits(ps: Parser): number {
skip(ps);
var beg = ps.pos;
while (/^[0-9]$/.test(peek(ps))) {
ps.pos += 1;
}
if (beg === ps.pos) {
throw new Error("expected number at byte " + ps.pos);
}
return Number(ps.src.slice(beg, ps.pos));
}
// Parses let syntax.
function parse_let(ps: Parser): Term {
skip(ps);
if (starts(ps, "!")) {
throw new Error("strict lets are outside SupVM's subset");
}
if (starts(ps, "&")) {
ps.pos += 1;
}
var nam = parse_name(ps);
skip(ps);
if (starts(ps, "&")) {
throw new Error("explicit DUP lets are outside SupVM's subset");
}
consume(ps, "=");
var val = parse_term(ps);
skip(ps);
if (starts(ps, ";")) {
ps.pos += 1;
}
var bod = parse_term(ps);
return Let(nam, val, bod);
}
// Parses a superposition or eraser.
function parse_sup(ps: Parser): Term {
skip(ps);
if (starts(ps, "{}")) {
ps.pos += 2;
return Era();
}
var lab = parse_name(ps);
skip(ps);
var cap = 0;
if (starts(ps, "[")) {
cap = parse_sup_captures(ps);
}
consume(ps, "{");
var lft = parse_term(ps);
skip(ps);
if (starts(ps, ";") || starts(ps, ",")) {
ps.pos += 1;
}
var rgt = parse_term(ps);
skip(ps);
if (starts(ps, ";") || starts(ps, ",")) {
ps.pos += 1;
}
consume(ps, "}");
return Sup(lab, lft, rgt, cap);
}
// Parses and ignores sup-fork captures.
function parse_sup_captures(ps: Parser): number {
consume(ps, "[");
var cap = 0;
while (true) {
skip(ps);
if (starts(ps, "]")) {
ps.pos += 1;
return cap;
}
if (starts(ps, "&")) {
ps.pos += 1;
}
parse_name(ps);
cap += 1;
skip(ps);
if (starts(ps, ",")) {
ps.pos += 1;
}
}
}
// Parses a list literal.
function parse_list(ps: Parser): Term {
consume(ps, "[");
var els: Term[] = [];
skip(ps);
if (starts(ps, "]")) {
ps.pos += 1;
return Ctr("Nil", []);
}
while (true) {
els.push(parse_term(ps));
skip(ps);
if (starts(ps, ",")) {
ps.pos += 1;
skip(ps);
}
if (starts(ps, "]")) {
ps.pos += 1;
break;
}
}
var out = Ctr("Nil", []);
for (var idx = els.length - 1; idx >= 0; idx--) {
out = Ctr("Cons", [els[idx], out]);
}
return out;
}
// Parses a number or nat literal.
function parse_num_or_nat(ps: Parser): Term {
var num = parse_digits(ps);
if (!starts(ps, "n")) {
return Num(num);
}
ps.pos += 1;
var end: Term = Ctr("Zero", []);
if (starts(ps, "+")) {
ps.pos += 1;
end = parse_term(ps);
}
for (var idx = 0; idx < num; idx++) {
end = Ctr("Succ", [end]);
}
return end;
}
// Evaluation
// ==========
// Creates a runtime.
function runtime_new(book: Book): Runtime {
return { book, itrs: 0, fresh: 0 };
}
// Increments the interaction counter.
function tick(rt: Runtime): void {
rt.itrs += 1;
}
// Forces a suspension to weak-head normal form.
function force(rt: Runtime, chs: Choices, sus: Susp): Val {
switch (sus.$) {
case "Term": {
return eval_term(rt, chs, sus.term, sus.env);
}
case "Val": {
return eval_val(rt, chs, sus.val);
}
case "App": {
var fun = force(rt, chs, sus.fun);
return apply_val(rt, chs, fun, sus.arg);
}
case "Eql": {
return eval_eql(rt, chs, sus.lft, sus.rgt);
}
case "And": {
return eval_and(rt, chs, sus.lft, sus.rgt);
}
case "Many": {
return eval_many(rt, chs, sus.lft, sus.rgt, sus.idx);
}
}
}
// Resolves a value against already-made choices.
function eval_val(rt: Runtime, chs: Choices, val: Val): Val {
if (val.$ === "Sup") {
var pic = chs.get(val.lab);
if (pic === 0) {
return force(rt, chs, val.lft);
}
if (pic === 1) {
return force(rt, chs, val.rgt);
}
}
return val;
}
// Evaluates a term to weak-head normal form.
function eval_term(rt: Runtime, chs: Choices, trm: Term, env: Env): Val {
switch (trm.$) {
case "Var": {
var got = env.get(trm.nam);
if (got === undefined) {
return { $: "Var", nam: trm.nam };
}
return force(rt, chs, got);
}
case "Ref": {
var def = rt.book.get(trm.nam);
if (def === undefined) {
return { $: "Var", nam: "@" + trm.nam };
}
return eval_term(rt, chs, def, new Map());
}
case "Lam": {
return { $: "Lam", nam: trm.nam, bod: trm.bod, env };
}
case "Mat": {
return { $: "Mat", css: trm.css, env };
}
case "App": {
var fun = force(rt, chs, Sterm(trm.fun, env));
return apply_val(rt, chs, fun, Sterm(trm.arg, env));
}
case "Let": {
tick(rt);
var nenv = new Map(env);
nenv.set(trm.nam, Sterm(trm.val, env));
return force(rt, chs, Sterm(trm.bod, nenv));
}
case "Sup": {
rt.itrs += trm.cap;
var pic = chs.get(trm.lab);
if (pic === 0) {
return force(rt, chs, Sterm(trm.lft, env));
}
if (pic === 1) {
return force(rt, chs, Sterm(trm.rgt, env));
}
return { $: "Sup", lab: trm.lab, lft: Sterm(trm.lft, env), rgt: Sterm(trm.rgt, env) };
}
case "Era": {
return { $: "Era" };
}
case "Num": {
return { $: "Num", val: trm.val };
}
case "Ctr": {
var fds = trm.fds.map(fld => Sterm(fld, env));
return { $: "Ctr", nam: trm.nam, fds };
}
case "Eql": {
return eval_eql(rt, chs, Sterm(trm.lft, env), Sterm(trm.rgt, env));
}
case "And": {
return eval_and(rt, chs, Sterm(trm.lft, env), Sterm(trm.rgt, env));
}
}
}
// Applies a value to an argument suspension.
function apply_val(rt: Runtime, chs: Choices, fun: Val, arg: Susp): Val {
fun = eval_val(rt, chs, fun);
switch (fun.$) {
case "Era": {
return { $: "Era" };
}
case "Lam": {
tick(rt);
var nenv = new Map(fun.env);
nenv.set(fun.nam, arg);
return force(rt, chs, Sterm(fun.bod, nenv));
}
case "Mat": {
return apply_mat(rt, chs, fun, arg);
}
case "Sup": {
tick(rt);
return { $: "Sup", lab: fun.lab, lft: Sapp(fun.lft, arg), rgt: Sapp(fun.rgt, arg) };
}
default: {
return { $: "App", fun, arg };
}
}
}
// Applies a matcher to an argument.
function apply_mat(rt: Runtime, chs: Choices, mat: Val$Mat, arg: Susp): Val {
var val = force(rt, chs, arg);
val = eval_val(rt, chs, val);
switch (val.$) {
case "Era": {
return { $: "Era" };
}
case "Sup": {
tick(rt);
var mvl: Val = mat;
return { $: "Sup", lab: val.lab, lft: Sapp(Sval(mvl), val.lft), rgt: Sapp(Sval(mvl), val.rgt) };
}
case "Ctr": {
return apply_mat_ctr(rt, chs, mat, val);
}
case "Num": {
return apply_mat_num(rt, chs, mat, val);
}
default: {
return { $: "App", fun: mat, arg: Sval(val) };
}
}
}
// Applies a matcher to a constructor.
function apply_mat_ctr(rt: Runtime, chs: Choices, mat: Val$Mat, ctr: Val$Ctr): Val {
for (var idx = 0; idx < mat.css.length; idx++) {
var cse = mat.css[idx];
if (cse.$ !== "Ctr") {
tick(rt);
continue;
}
if (cse.nam !== ctr.nam) {
tick(rt);
continue;
}
tick(rt);
var out: Susp = Sterm(cse.bod, mat.env);
for (var f = 0; f < ctr.fds.length; f++) {
out = Sapp(out, ctr.fds[f]);
}
return force(rt, chs, out);
}
return { $: "App", fun: mat, arg: Sval(ctr) };
}
// Applies a matcher to a number.
function apply_mat_num(rt: Runtime, chs: Choices, mat: Val$Mat, num: Val$Num): Val {
for (var idx = 0; idx < mat.css.length; idx++) {
var cse = mat.css[idx];
if (cse.$ !== "Num") {
tick(rt);
continue;
}
if (cse.val !== num.val) {
tick(rt);
continue;
}
tick(rt);
return force(rt, chs, Sterm(cse.bod, mat.env));
}
return { $: "App", fun: mat, arg: Sval(num) };
}
// Evaluates a structural equality.
function eval_eql(rt: Runtime, chs: Choices, lft: Susp, rgt: Susp): Val {
var lfv = force(rt, chs, lft);
lfv = eval_val(rt, chs, lfv);
if (lfv.$ === "Era") {
return { $: "Era" };
}
if (lfv.$ === "Sup") {
tick(rt);
return { $: "Sup", lab: lfv.lab, lft: Seql(lfv.lft, rgt), rgt: Seql(lfv.rgt, rgt) };
}
var rgv = force(rt, chs, rgt);
rgv = eval_val(rt, chs, rgv);
if (rgv.$ === "Era") {
return { $: "Era" };
}
if (rgv.$ === "Sup") {
tick(rt);
return { $: "Sup", lab: rgv.lab, lft: Seql(lft, rgv.lft), rgt: Seql(lft, rgv.rgt) };
}
return eval_eql_val(rt, chs, lfv, rgv);
}
// Compares two weak-head values structurally.
function eval_eql_val(rt: Runtime, chs: Choices, lft: Val, rgt: Val): Val {
if (lft.$ === "Num" && rgt.$ === "Num") {
tick(rt);
return { $: "Num", val: lft.val === rgt.val ? 1 : 0 };
}
if (lft.$ === "Ctr" && rgt.$ === "Ctr") {
tick(rt);
if (lft.nam !== rgt.nam || lft.fds.length !== rgt.fds.length) {
return { $: "Num", val: 0 };
}
if (lft.fds.length === 0) {
return { $: "Num", val: 1 };
}
return force(rt, chs, Smany(lft.fds, rgt.fds, 0));
}
if (lft.$ === "Var" && rgt.$ === "Var") {
tick(rt);
return { $: "Num", val: lft.nam === rgt.nam ? 1 : 0 };
}
tick(rt);
return { $: "Num", val: 0 };
}
// Evaluates a sequence of field equalities.
function eval_many(rt: Runtime, chs: Choices, lft: Susp[], rgt: Susp[], idx: number): Val {
var pos = idx;
while (pos < lft.length) {
var cur = force(rt, chs, Seql(lft[pos], rgt[pos]));
cur = eval_val(rt, chs, cur);
if (cur.$ === "Era") {
return cur;
}
if (cur.$ === "Sup") {
var nxt = Smany(lft, rgt, pos + 1);
return { $: "Sup", lab: cur.lab, lft: Sand(cur.lft, nxt), rgt: Sand(cur.rgt, nxt) };
}
if (cur.$ !== "Num") {
tick(rt);
return { $: "Num", val: 0 };
}
if (pos + 1 < lft.length) {
tick(rt);
}
if (cur.val === 0) {
return cur;
}
pos += 1;
}
return { $: "Num", val: 1 };
}
// Evaluates boolean and.
function eval_and(rt: Runtime, chs: Choices, lft: Susp, rgt: Susp): Val {
var val = force(rt, chs, lft);
val = eval_val(rt, chs, val);
switch (val.$) {
case "Era": {
return val;
}
case "Sup": {
tick(rt);
return { $: "Sup", lab: val.lab, lft: Sand(val.lft, rgt), rgt: Sand(val.rgt, rgt) };
}
case "Num": {
tick(rt);
if (val.val === 0) {
return { $: "Num", val: 0 };
}
return force(rt, chs, rgt);
}
default: {
tick(rt);
return { $: "Num", val: 0 };
}
}
}
// CNF / collapse
// ==============
// Maps a CNF result.
function cnf_map(cnf: Cnf, fnc: (val: Norm) => Norm): Cnf {
switch (cnf.$) {
case "Leaf": {
return { $: "Leaf", val: fnc(cnf.val) };
}
case "Era": {
return cnf;
}
case "Fork": {
var lab = cnf.lab;
var lft = cnf.lft;
var rgt = cnf.rgt;
return {
$: "Fork",
lab,
lft: (rt, chs) => cnf_map(lft(rt, chs), fnc),
rgt: (rt, chs) => cnf_map(rgt(rt, chs), fnc),
};
}
}
}
// Binds a CNF result.
function cnf_bind(cnf: Cnf, fnc: (val: Norm) => Cnf): Cnf {
switch (cnf.$) {
case "Leaf": {
return fnc(cnf.val);
}
case "Era": {
return cnf;
}
case "Fork": {
var lab = cnf.lab;
var lft = cnf.lft;
var rgt = cnf.rgt;
return {
$: "Fork",
lab,
lft: (rt, chs) => cnf_bind(lft(rt, chs), fnc),
rgt: (rt, chs) => cnf_bind(rgt(rt, chs), fnc),
};
}
}
}
// Converts a suspension to collapsed normal form, lifting one SUP.
function cnf_susp(rt: Runtime, chs: Choices, sus: Susp): Cnf {
var val = force(rt, chs, sus);
return cnf_val(rt, chs, val);
}
// Converts a value to collapsed normal form, lifting one SUP.
function cnf_val(rt: Runtime, chs: Choices, val: Val): Cnf {
val = eval_val(rt, chs, val);
switch (val.$) {
case "Era": {
return { $: "Era" };
}
case "Num": {
return { $: "Leaf", val: NNum(val.val) };
}
case "Var": {
return { $: "Leaf", val: NVar(val.nam) };
}
case "Sup": {
var pic = chs.get(val.lab);
if (pic === 0) {
return cnf_susp(rt, chs, val.lft);
}
if (pic === 1) {
return cnf_susp(rt, chs, val.rgt);
}
return {
$: "Fork",
lab: val.lab,
lft: (nrt, nch) => cnf_susp(nrt, nch, val.lft),
rgt: (nrt, nch) => cnf_susp(nrt, nch, val.rgt),
};
}
case "Lam": {
var nam = fresh_var(rt);
var nenv = new Map(val.env);
nenv.set(val.nam, Sval({ $: "Var", nam }));
var bod = cnf_susp(rt, chs, Sterm(val.bod, nenv));
return cnf_map(bod, out => NLam(nam, out));
}
case "Mat": {
return cnf_cases(rt, chs, val.css, val.env, 0, []);
}
case "Ctr": {
return cnf_fields(rt, chs, val.fds, 0, [], fds => NCtr(val.nam, fds));
}
case "App": {
var fun = cnf_val(rt, chs, val.fun);
return cnf_bind(fun, nfn => {
var arg = cnf_susp(rt, chs, val.arg);
return cnf_map(arg, nar => NApp(nfn, nar));
});
}
}
}
// Normalizes match cases.
function cnf_cases(rt: Runtime, chs: Choices, css: Case[], env: Env, idx: number, acc: NormCase[]): Cnf {
if (idx >= css.length) {
return { $: "Leaf", val: NMat(acc) };
}
var cse = css[idx];
var bod = cnf_susp(rt, chs, Sterm(cse.bod, env));
return cnf_bind(bod, nbod => {
var out = acc.slice();
if (cse.$ === "Ctr") {
out.push({ $: "Ctr", nam: cse.nam, bod: nbod });
} else {
out.push({ $: "Num", val: cse.val, bod: nbod });
}
return cnf_cases(rt, chs, css, env, idx + 1, out);
});
}
// Normalizes a list of child suspensions.
function cnf_fields(rt: Runtime, chs: Choices, fds: Susp[], idx: number, acc: Norm[], fin: (fds: Norm[]) => Norm): Cnf {
if (idx >= fds.length) {
return { $: "Leaf", val: fin(acc) };
}
var fld = cnf_susp(rt, chs, fds[idx]);
return cnf_bind(fld, nfd => {
var out = acc.slice();
out.push(nfd);
return cnf_fields(rt, chs, fds, idx + 1, out, fin);
});
}
// Creates a fresh internal variable name.
function fresh_var(rt: Runtime): string {
var out = "$" + rt.fresh;
rt.fresh += 1;
return out;
}
// Copies a choice map and adds one choice.
function choice_ext(chs: Choices, lab: string, pic: Pick): Choices {
var out = new Map(chs);
out.set(lab, pic);
return out;
}
// Runs collapse and returns the first leaf.
function collapse(rt: Runtime, root: Susp, lim: number): Norm | null {
type Item = { job: Job, chs: Choices };
var que: Item[] = [{ job: (nrt, nch) => cnf_susp(nrt, nch, root), chs: new Map() }];
var pos = 0;
var got = 0;
while (pos < que.length) {
var itm = que[pos++];
var cnf = itm.job(rt, itm.chs);
switch (cnf.$) {
case "Era": {
break;
}
case "Leaf": {
got += 1;
if (got >= lim) {
return cnf.val;
}
break;
}
case "Fork": {
que.push({ job: cnf.lft, chs: choice_ext(itm.chs, cnf.lab, 0) });
que.push({ job: cnf.rgt, chs: choice_ext(itm.chs, cnf.lab, 1) });
break;
}
}
}
return null;
}
// Pretty printer
// ==============
// Prints a base-26 alpha name.
function alpha_name(num: number, base: string): string {
if (num <= 0) {
return "_";
}
var chr = base.charCodeAt(0);
var out = "";
var cur = num;
while (cur > 0) {
cur -= 1;
out = String.fromCharCode(chr + (cur % 26)) + out;
cur = Math.floor(cur / 26);
}
return out;
}
// Prints a normal term.
function show_norm(trm: Norm): string {
return show_go(trm, new Map(), 0);
}
// Prints a normal term recursively.
function show_go(trm: Norm, ctx: Map<string, string>, dep: number): string {
switch (trm.$) {
case "Var": {
return ctx.get(trm.nam) || trm.nam;
}
case "Num": {
return String(trm.val);
}
case "Lam": {
var nam = alpha_name(dep + 1, "a");
var nctx = new Map(ctx);
nctx.set(trm.nam, nam);
return "λ" + nam + "." + show_go(trm.bod, nctx, dep + 1);
}
case "Mat": {
var buf = "λ{";
for (var idx = 0; idx < trm.css.length; idx++) {
if (idx > 0) {
buf += ";";
}
var cse = trm.css[idx];
buf += show_case_key(cse) + ":" + show_go(cse.bod, ctx, dep);
}
return buf + "}";
}
case "App": {
return show_app(trm, ctx, dep);
}
case "Ctr": {
return show_ctr(trm, ctx, dep);
}
}
}
// Prints an application spine.
function show_app(trm: Norm, ctx: Map<string, string>, dep: number): string {
var args: Norm[] = [];
var cur = trm;
while (cur.$ === "App") {
args.push(cur.arg);
cur = cur.fun;
}
var out = show_go(cur, ctx, dep);
if (cur.$ === "Lam") {
out = "(" + out + ")";
}
out += "(";
for (var idx = args.length - 1; idx >= 0; idx--) {
if (idx !== args.length - 1) {
out += ",";
}
out += show_go(args[idx], ctx, dep);
}
return out + ")";
}
// Prints a match key.
function show_case_key(cse: NormCase): string {
if (cse.$ === "Num") {
return String(cse.val);
}
switch (cse.nam) {
case "Zero": {
return "0n";
}
case "Succ": {
return "1n+";
}
case "Nil": {
return "[]";
}
case "Cons": {
return "<>";
}
default: {
return "#" + cse.nam;
}
}
}
// Prints a constructor.
function show_ctr(trm: Norm$Ctr, ctx: Map<string, string>, dep: number): string {
var nat = nat_view(trm);
if (nat !== null) {
if (nat.rest === null) {
return String(nat.num) + "n";
}
return String(nat.num) + "n+" + show_go(nat.rest, ctx, dep);
}
if (is_list_end(trm)) {
return "[]";
}
if (trm.nam === "Cons") {
var arr = list_view(trm);
if (arr.end === null) {
var out = "[";
for (var idx = 0; idx < arr.items.length; idx++) {
if (idx > 0) {
out += ",";
}
out += show_go(arr.items[idx], ctx, dep);
}
return out + "]";
}
return show_go(trm.fds[0], ctx, dep) + "<>" + show_go(trm.fds[1], ctx, dep);
}
var buf = "#" + trm.nam + "{";
for (var idx = 0; idx < trm.fds.length; idx++) {
if (idx > 0) {
buf += ",";
}
buf += show_go(trm.fds[idx], ctx, dep);
}
return buf + "}";
}
// Tests whether a constructor is nil.
function is_list_end(trm: Norm): boolean {
return trm.$ === "Ctr" && trm.nam === "Nil" && trm.fds.length === 0;
}
// Views a proper or improper list.
function list_view(trm: Norm): { items: Norm[], end: Norm | null } {
var cur = trm;
var out: Norm[] = [];
while (cur.$ === "Ctr" && cur.nam === "Cons" && cur.fds.length === 2) {
out.push(cur.fds[0]);
cur = cur.fds[1];
}
if (is_list_end(cur)) {
return { items: out, end: null };
}
return { items: out, end: cur };
}
// Views a natural-number constructor.
function nat_view(trm: Norm): { num: number, rest: Norm | null } | null {
var cur: Norm = trm;
var num = 0;
while (cur.$ === "Ctr" && cur.nam === "Succ" && cur.fds.length === 1) {
num += 1;
cur = cur.fds[0];
}
if (cur.$ === "Ctr" && cur.nam === "Zero" && cur.fds.length === 0) {
return { num, rest: null };
}
if (num > 0) {
return { num, rest: cur };
}
return null;
}
// CLI
// ===
// Parses command-line arguments.
function parse_args(argv: string[]): { file: string | null, collapse: number, stats: boolean } {
var file: string | null = null;
var col = 0;
var sts = false;
for (var idx = 2; idx < argv.length; idx++) {
var arg = argv[idx];
if (arg === "-s" || arg === "--stats") {
sts = true;
} else if (arg === "-C" || arg === "--collapse") {
idx += 1;
col = Number(argv[idx] || "-1");
} else if (arg.startsWith("-C")) {
col = Number(arg.slice(2) || "-1");
} else if (arg.startsWith("--collapse=")) {
col = Number(arg.slice("--collapse=".length) || "-1");
} else if (arg === "-h" || arg === "--help") {
print_help();
process.exit(0);
} else if (!arg.startsWith("-")) {
file = arg;
}
}
return { file, collapse: col, stats: sts };
}
// Prints help.
function print_help(): void {
console.log("Usage: node SupVM.ts <file.hvm> -C1 [-s]");
}
// Runs the command-line interface.
function main(): void {
var cfg = parse_args(process.argv);
if (cfg.file === null) {
print_help();
process.exit(1);
}
var src = fs.readFileSync(cfg.file, "utf8");
var book = parse_program(src);
var main = book.get("main");
if (main === undefined) {
throw new Error("missing @main definition");
}
var rt = runtime_new(book);
var lim = cfg.collapse === 0 ? 1 : cfg.collapse;
if (lim < 0) {
lim = 1;
}
var got = collapse(rt, Sterm(Ref("main"), new Map()), lim);
if (got !== null) {
console.log(show_norm(got) + " #" + rt.itrs);
}
if (cfg.stats) {
console.log("- Itrs: " + rt.itrs + " interactions");
}
}
// Runs the CLI entry point.
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment