giving up on bootstrapping due to parser perf
initially, I wanted Kind2 to be bootstrapped, but I'm facing
difficulties, because pure hvm parser is still much slower than the
typescript parser; a few seconds vs instant to parse the entire
codebase. I think HVM2 will need to have proper immutable strings (i.e.,
nodes that expand as if they were cons-strings, but are stored as
buffers) before it can have fast parsers. as such, I'm making the
decision to NOT bootstrap it now. that is a tough decision becase,
honestly, if we don't do it as soon as possible in the development
process, it is very unlikely we'll ever do it. but the tech isn't ready
for it, and we need to move forward, so, that's the only solution.
as such, I'll be rewritting the typescript to Rust, and we'll have a
very similar design to former Kind2, with Rust doing the parsing, IO and
error reporting, and a checker.hvm1 / checker.hvm2 file doing the actual
type-checking (and HOAS-based type-level evaluation).
also, I believe we'll not even generate .hvm from a .kind bootstrap.
while losing types is significant, being able to manually edit the HVM
is very helpful to debugging, it simplifies and accelerates the
development process and removes many complications that are bought by
bootstrapping
2024-02-23 20:28:04 +03:00
|
|
|
#!/usr/bin/env ts-node
|
2024-02-22 23:01:05 +03:00
|
|
|
|
giving up on bootstrapping due to parser perf
initially, I wanted Kind2 to be bootstrapped, but I'm facing
difficulties, because pure hvm parser is still much slower than the
typescript parser; a few seconds vs instant to parse the entire
codebase. I think HVM2 will need to have proper immutable strings (i.e.,
nodes that expand as if they were cons-strings, but are stored as
buffers) before it can have fast parsers. as such, I'm making the
decision to NOT bootstrap it now. that is a tough decision becase,
honestly, if we don't do it as soon as possible in the development
process, it is very unlikely we'll ever do it. but the tech isn't ready
for it, and we need to move forward, so, that's the only solution.
as such, I'll be rewritting the typescript to Rust, and we'll have a
very similar design to former Kind2, with Rust doing the parsing, IO and
error reporting, and a checker.hvm1 / checker.hvm2 file doing the actual
type-checking (and HOAS-based type-level evaluation).
also, I believe we'll not even generate .hvm from a .kind bootstrap.
while losing types is significant, being able to manually edit the HVM
is very helpful to debugging, it simplifies and accelerates the
development process and removes many complications that are bought by
bootstrapping
2024-02-23 20:28:04 +03:00
|
|
|
// TODO: move the parser to HVM
|
2024-02-23 05:43:20 +03:00
|
|
|
|
giving up on bootstrapping due to parser perf
initially, I wanted Kind2 to be bootstrapped, but I'm facing
difficulties, because pure hvm parser is still much slower than the
typescript parser; a few seconds vs instant to parse the entire
codebase. I think HVM2 will need to have proper immutable strings (i.e.,
nodes that expand as if they were cons-strings, but are stored as
buffers) before it can have fast parsers. as such, I'm making the
decision to NOT bootstrap it now. that is a tough decision becase,
honestly, if we don't do it as soon as possible in the development
process, it is very unlikely we'll ever do it. but the tech isn't ready
for it, and we need to move forward, so, that's the only solution.
as such, I'll be rewritting the typescript to Rust, and we'll have a
very similar design to former Kind2, with Rust doing the parsing, IO and
error reporting, and a checker.hvm1 / checker.hvm2 file doing the actual
type-checking (and HOAS-based type-level evaluation).
also, I believe we'll not even generate .hvm from a .kind bootstrap.
while losing types is significant, being able to manually edit the HVM
is very helpful to debugging, it simplifies and accelerates the
development process and removes many complications that are bought by
bootstrapping
2024-02-23 20:28:04 +03:00
|
|
|
// ICC: Parser, Stringifier and CLI
|
|
|
|
// ================================
|
|
|
|
|
|
|
|
// List
|
|
|
|
// ----
|
|
|
|
|
|
|
|
type List<A> =
|
|
|
|
| { tag: "Cons"; head: A; tail: List<A> }
|
|
|
|
| { tag: "Nil"; };
|
|
|
|
|
|
|
|
// Constructors
|
|
|
|
const Cons = <A>(head: A, tail: List<A>): List<A> => ({ tag: "Cons", head, tail });
|
|
|
|
const Nil = <A>(): List<A> => ({ tag: "Nil" });
|
|
|
|
|
|
|
|
// Term
|
|
|
|
// ----
|
|
|
|
|
|
|
|
type Term =
|
|
|
|
| { $: "All"; nam: string; inp: Term; bod: (x:Term)=> Term } // ∀(x: inp) bod
|
|
|
|
| { $: "Lam"; nam: string; bod: (x:Term)=> Term } // λx bod
|
|
|
|
| { $: "App"; fun: Term; arg: Term } // (fun arg)
|
|
|
|
| { $: "Ann"; val: Term; typ: Term } // {val:typ}
|
|
|
|
| { $: "Slf"; nam: string; bod: (x:Term)=> Term } // $x bod
|
|
|
|
| { $: "Ins"; val: Term } // ~val
|
|
|
|
| { $: "Set" } // *
|
|
|
|
| { $: "U60" } // #U60
|
|
|
|
| { $: "Num"; val: BigInt } // #num
|
|
|
|
| { $: "Op2"; opr: string; fst: Term; snd: Term } // #(<opr> fst snd)
|
|
|
|
| { $: "Mat"; nam: string; x: Term; z: Term; s: (x:Term) => Term; p: (x: Term) => Term } // #match k = x { zero: z; succ: s }: p
|
|
|
|
| { $: "Txt"; txt: string } // "text"
|
|
|
|
| { $: "Ref"; nam: string; val?: Term }
|
|
|
|
| { $: "Let"; nam: string; val: Term; bod: (x:Term)=> Term }
|
|
|
|
| { $: "Hol"; nam: string }
|
|
|
|
| { $: "Var"; nam: string; idx: number };
|
|
|
|
|
|
|
|
// Constructors
|
|
|
|
export const All = (nam: string, inp: Term, bod: (x:Term)=> Term): Term => ({ $: "All", nam, inp, bod });
|
|
|
|
export const Lam = (nam: string, bod: (x:Term)=> Term): Term => ({ $: "Lam", nam, bod });
|
|
|
|
export const App = (fun: Term, arg: Term): Term => ({ $: "App", fun, arg });
|
|
|
|
export const Ann = (val: Term, typ: Term): Term => ({ $: "Ann", val, typ });
|
|
|
|
export const Slf = (nam: string, bod: (x:Term)=> Term): Term => ({ $: "Slf", nam, bod });
|
|
|
|
export const Ins = (val: Term): Term => ({ $: "Ins", val });
|
|
|
|
export const Set = (): Term => ({ $: "Set" });
|
|
|
|
export const U60 = (): Term => ({ $: "U60" });
|
|
|
|
export const Num = (val: BigInt): Term => ({ $: "Num", val });
|
|
|
|
export const Op2 = (opr: string, fst: Term, snd: Term): Term => ({ $: "Op2", opr, fst, snd });
|
|
|
|
export const Mat = (nam: string, x: Term, z: Term, s: (x:Term) => Term, p: (x:Term) => Term): Term => ({ $: "Mat", nam, x, z, s, p });
|
|
|
|
export const Txt = (txt: string): Term => ({ $: "Txt", txt });
|
|
|
|
export const Ref = (nam: string, val?: Term): Term => ({ $: "Ref", nam, val });
|
|
|
|
export const Let = (nam: string, val: Term, bod: (x:Term)=> Term): Term => ({ $: "Let", nam, val, bod });
|
|
|
|
export const Hol = (nam: string): Term => ({ $: "Hol", nam });
|
|
|
|
export const Var = (nam: string, idx: number): Term => ({ $: "Var", nam, idx });
|
|
|
|
|
|
|
|
// Book
|
|
|
|
// ----
|
|
|
|
|
|
|
|
type Book = {[name: string]: Term};
|
|
|
|
|
|
|
|
// Stringifier
|
|
|
|
// -----------
|
|
|
|
|
|
|
|
export const name = (numb: number): string => {
|
|
|
|
let name = '';
|
|
|
|
do {
|
|
|
|
name = String.fromCharCode(97 + (numb % 26)) + name;
|
|
|
|
numb = Math.floor(numb / 26) - 1;
|
|
|
|
} while (numb >= 0);
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
|
|
|
export const context = (numb: number): string => {
|
|
|
|
var names = [];
|
|
|
|
for (var i = 0; i < numb; ++i) {
|
|
|
|
names.push(name(i));
|
|
|
|
}
|
|
|
|
return "["+names.join(",")+"]";
|
|
|
|
}
|
|
|
|
|
|
|
|
const compile_oper = (opr: string): string => {
|
|
|
|
switch (opr) {
|
|
|
|
case "+" : return "ADD";
|
|
|
|
case "-" : return "SUB";
|
|
|
|
case "*" : return "MUL";
|
|
|
|
case "/" : return "DIV";
|
|
|
|
case "%" : return "MOD";
|
|
|
|
case "==" : return "EQ";
|
|
|
|
case "!=" : return "NE";
|
|
|
|
case "<" : return "LT";
|
|
|
|
case ">" : return "GT";
|
|
|
|
case "<=" : return "LTE";
|
|
|
|
case ">=" : return "GTE";
|
|
|
|
case "&" : return "AND";
|
|
|
|
case "|" : return "OR";
|
|
|
|
case "^" : return "XOR";
|
|
|
|
case "<<" : return "LSH";
|
|
|
|
case ">>" : return "RSH";
|
|
|
|
default: throw new Error("Unknown operator: " + opr);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
export const compile = (term: Term, used_refs: any, dep: number = 0): string => {
|
|
|
|
switch (term.$) {
|
|
|
|
case "All": return `(All "${term.nam}" ${compile(term.inp, used_refs, dep)} λ${name(dep)} ${compile(term.bod(Var(term.nam,dep)), used_refs, dep + 1)})`;
|
|
|
|
case "Lam": return `(Lam "${term.nam}" λ${name(dep)} ${compile(term.bod(Var(term.nam,dep)), used_refs, dep + 1)})`;
|
|
|
|
case "App": return `(App ${compile(term.fun, used_refs, dep)} ${compile(term.arg, used_refs, dep)})`;
|
|
|
|
case "Ann": return `(Ann ${compile(term.val, used_refs, dep)} ${compile(term.typ, used_refs, dep)})`;
|
|
|
|
case "Slf": return `(Slf "${term.nam}" λ${name(dep)} ${compile(term.bod(Var(term.nam,dep)), used_refs, dep + 1)})`;
|
|
|
|
case "Ins": return `(Ins ${compile(term.val, used_refs, dep)})`;
|
|
|
|
case "Set": return `(Set)`;
|
|
|
|
case "U60": return `(U60)`;
|
|
|
|
case "Num": return `(Num ${term.val.toString()})`;
|
|
|
|
case "Op2": return `(Op2 ${compile_oper(term.opr)} ${compile(term.fst, used_refs, dep)} ${compile(term.snd, used_refs, dep)})`;
|
|
|
|
case "Mat": return `(Mat "${term.nam}" ${compile(term.x, used_refs, dep)} ${compile(term.z, used_refs, dep)} λ${name(dep)} ${compile(term.s(Var(term.nam,dep)), used_refs, dep + 1)} λ${name(dep)} ${compile(term.p(Var(term.nam,dep)), used_refs, dep + 1)})`;
|
|
|
|
case "Txt": return `(Txt \`${term.txt}\`)`;
|
|
|
|
case "Hol": return `(Hol "${term.nam}" ${context(dep)})`;
|
|
|
|
case "Var": return name(term.idx);
|
|
|
|
case "Ref": return (used_refs[term.nam] = 1), ("Book." + term.nam);
|
|
|
|
case "Let": return `(Let "${term.nam}" ${compile(term.val, used_refs, dep)} λ${name(dep)} ${compile(term.bod(Var(term.nam,dep)), used_refs, dep + 1)})`;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Parser
|
|
|
|
// ------
|
|
|
|
|
|
|
|
export type Scope = List<[string, Term]>;
|
|
|
|
|
|
|
|
export function find<A>(list: Scope, nam: string): Term {
|
|
|
|
switch (list.tag) {
|
|
|
|
case "Cons": return list.head[0] === nam ? list.head[1] : find(list.tail, nam);
|
|
|
|
case "Nil" : return Ref(nam);
|
2024-02-22 23:01:05 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
giving up on bootstrapping due to parser perf
initially, I wanted Kind2 to be bootstrapped, but I'm facing
difficulties, because pure hvm parser is still much slower than the
typescript parser; a few seconds vs instant to parse the entire
codebase. I think HVM2 will need to have proper immutable strings (i.e.,
nodes that expand as if they were cons-strings, but are stored as
buffers) before it can have fast parsers. as such, I'm making the
decision to NOT bootstrap it now. that is a tough decision becase,
honestly, if we don't do it as soon as possible in the development
process, it is very unlikely we'll ever do it. but the tech isn't ready
for it, and we need to move forward, so, that's the only solution.
as such, I'll be rewritting the typescript to Rust, and we'll have a
very similar design to former Kind2, with Rust doing the parsing, IO and
error reporting, and a checker.hvm1 / checker.hvm2 file doing the actual
type-checking (and HOAS-based type-level evaluation).
also, I believe we'll not even generate .hvm from a .kind bootstrap.
while losing types is significant, being able to manually edit the HVM
is very helpful to debugging, it simplifies and accelerates the
development process and removes many complications that are bought by
bootstrapping
2024-02-23 20:28:04 +03:00
|
|
|
export function skip(code: string): string {
|
|
|
|
while (true) {
|
|
|
|
if (code.slice(0, 2) === "//") {
|
|
|
|
do { code = code.slice(1); } while (code.length != 0 && code[0] != "\n");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (code[0] === "\n" || code[0] === " ") {
|
|
|
|
code = code.slice(1);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return code;
|
2024-02-22 23:01:05 +03:00
|
|
|
}
|
|
|
|
|
giving up on bootstrapping due to parser perf
initially, I wanted Kind2 to be bootstrapped, but I'm facing
difficulties, because pure hvm parser is still much slower than the
typescript parser; a few seconds vs instant to parse the entire
codebase. I think HVM2 will need to have proper immutable strings (i.e.,
nodes that expand as if they were cons-strings, but are stored as
buffers) before it can have fast parsers. as such, I'm making the
decision to NOT bootstrap it now. that is a tough decision becase,
honestly, if we don't do it as soon as possible in the development
process, it is very unlikely we'll ever do it. but the tech isn't ready
for it, and we need to move forward, so, that's the only solution.
as such, I'll be rewritting the typescript to Rust, and we'll have a
very similar design to former Kind2, with Rust doing the parsing, IO and
error reporting, and a checker.hvm1 / checker.hvm2 file doing the actual
type-checking (and HOAS-based type-level evaluation).
also, I believe we'll not even generate .hvm from a .kind bootstrap.
while losing types is significant, being able to manually edit the HVM
is very helpful to debugging, it simplifies and accelerates the
development process and removes many complications that are bought by
bootstrapping
2024-02-23 20:28:04 +03:00
|
|
|
export function is_name_char(c: string): boolean {
|
|
|
|
return /[a-zA-Z0-9_.-]/.test(c);
|
2024-02-22 23:01:05 +03:00
|
|
|
}
|
|
|
|
|
giving up on bootstrapping due to parser perf
initially, I wanted Kind2 to be bootstrapped, but I'm facing
difficulties, because pure hvm parser is still much slower than the
typescript parser; a few seconds vs instant to parse the entire
codebase. I think HVM2 will need to have proper immutable strings (i.e.,
nodes that expand as if they were cons-strings, but are stored as
buffers) before it can have fast parsers. as such, I'm making the
decision to NOT bootstrap it now. that is a tough decision becase,
honestly, if we don't do it as soon as possible in the development
process, it is very unlikely we'll ever do it. but the tech isn't ready
for it, and we need to move forward, so, that's the only solution.
as such, I'll be rewritting the typescript to Rust, and we'll have a
very similar design to former Kind2, with Rust doing the parsing, IO and
error reporting, and a checker.hvm1 / checker.hvm2 file doing the actual
type-checking (and HOAS-based type-level evaluation).
also, I believe we'll not even generate .hvm from a .kind bootstrap.
while losing types is significant, being able to manually edit the HVM
is very helpful to debugging, it simplifies and accelerates the
development process and removes many complications that are bought by
bootstrapping
2024-02-23 20:28:04 +03:00
|
|
|
export function is_oper_char(c: string): boolean {
|
|
|
|
return /[+\-*/%<>=&|^!~]/.test(c);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function parse_word(is_letter: (c: string) => boolean, code: string): [string, string] {
|
|
|
|
code = skip(code);
|
|
|
|
var name = "";
|
|
|
|
while (is_letter(code[0]||"")) {
|
|
|
|
name = name + code[0];
|
|
|
|
code = code.slice(1);
|
|
|
|
}
|
|
|
|
return [code, name];
|
2024-02-22 23:01:05 +03:00
|
|
|
}
|
|
|
|
|
giving up on bootstrapping due to parser perf
initially, I wanted Kind2 to be bootstrapped, but I'm facing
difficulties, because pure hvm parser is still much slower than the
typescript parser; a few seconds vs instant to parse the entire
codebase. I think HVM2 will need to have proper immutable strings (i.e.,
nodes that expand as if they were cons-strings, but are stored as
buffers) before it can have fast parsers. as such, I'm making the
decision to NOT bootstrap it now. that is a tough decision becase,
honestly, if we don't do it as soon as possible in the development
process, it is very unlikely we'll ever do it. but the tech isn't ready
for it, and we need to move forward, so, that's the only solution.
as such, I'll be rewritting the typescript to Rust, and we'll have a
very similar design to former Kind2, with Rust doing the parsing, IO and
error reporting, and a checker.hvm1 / checker.hvm2 file doing the actual
type-checking (and HOAS-based type-level evaluation).
also, I believe we'll not even generate .hvm from a .kind bootstrap.
while losing types is significant, being able to manually edit the HVM
is very helpful to debugging, it simplifies and accelerates the
development process and removes many complications that are bought by
bootstrapping
2024-02-23 20:28:04 +03:00
|
|
|
export function parse_name(code: string): [string, string] {
|
|
|
|
return parse_word(is_name_char, code);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function parse_oper(code: string): [string, string] {
|
|
|
|
return parse_word(is_oper_char, code);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function parse_text(code: string, text: string): [string, null] {
|
|
|
|
code = skip(code);
|
|
|
|
if (text === "") {
|
|
|
|
return [code, null];
|
|
|
|
} else if (code[0] === text[0]) {
|
|
|
|
return parse_text(code.slice(1), text.slice(1));
|
|
|
|
} else {
|
|
|
|
throw "parse error";
|
|
|
|
}
|
2024-02-23 03:31:53 +03:00
|
|
|
}
|
|
|
|
|
giving up on bootstrapping due to parser perf
initially, I wanted Kind2 to be bootstrapped, but I'm facing
difficulties, because pure hvm parser is still much slower than the
typescript parser; a few seconds vs instant to parse the entire
codebase. I think HVM2 will need to have proper immutable strings (i.e.,
nodes that expand as if they were cons-strings, but are stored as
buffers) before it can have fast parsers. as such, I'm making the
decision to NOT bootstrap it now. that is a tough decision becase,
honestly, if we don't do it as soon as possible in the development
process, it is very unlikely we'll ever do it. but the tech isn't ready
for it, and we need to move forward, so, that's the only solution.
as such, I'll be rewritting the typescript to Rust, and we'll have a
very similar design to former Kind2, with Rust doing the parsing, IO and
error reporting, and a checker.hvm1 / checker.hvm2 file doing the actual
type-checking (and HOAS-based type-level evaluation).
also, I believe we'll not even generate .hvm from a .kind bootstrap.
while losing types is significant, being able to manually edit the HVM
is very helpful to debugging, it simplifies and accelerates the
development process and removes many complications that are bought by
bootstrapping
2024-02-23 20:28:04 +03:00
|
|
|
export function parse_term(code: string): [string, (ctx: Scope) => Term] {
|
|
|
|
code = skip(code);
|
|
|
|
// ALL: `∀(x: A) B[x]` -SUGAR
|
|
|
|
if (code[0] === "∀") {
|
|
|
|
var [code, nam] = parse_name(code.slice(2));
|
|
|
|
var [code, _ ] = parse_text(code, ":");
|
|
|
|
var [code, inp] = parse_term(code);
|
|
|
|
var [code, _ ] = parse_text(code, ")");
|
|
|
|
var [code, bod] = parse_term(code);
|
|
|
|
return [code, ctx => All(nam, inp(ctx), x => bod(Cons([nam, x], ctx)))];
|
|
|
|
}
|
|
|
|
// LAM: `λx f`
|
|
|
|
if (code[0] === "λ") {
|
|
|
|
var [code, nam] = parse_name(code.slice(1));
|
|
|
|
var [code, bod] = parse_term(code);
|
|
|
|
return [code, ctx => Lam(nam, x => bod(Cons([nam, x], ctx)))];
|
|
|
|
}
|
|
|
|
// APP: `(f x y z ...)`
|
|
|
|
if (code[0] === "(") {
|
|
|
|
var [code, fun] = parse_term(code.slice(1));
|
|
|
|
var args: ((ctx: Scope) => Term)[] = [];
|
|
|
|
while (code[0] !== ")") {
|
|
|
|
var [code, arg] = parse_term(code);
|
|
|
|
args.push(arg);
|
|
|
|
code = skip(code);
|
2024-02-23 05:43:20 +03:00
|
|
|
}
|
giving up on bootstrapping due to parser perf
initially, I wanted Kind2 to be bootstrapped, but I'm facing
difficulties, because pure hvm parser is still much slower than the
typescript parser; a few seconds vs instant to parse the entire
codebase. I think HVM2 will need to have proper immutable strings (i.e.,
nodes that expand as if they were cons-strings, but are stored as
buffers) before it can have fast parsers. as such, I'm making the
decision to NOT bootstrap it now. that is a tough decision becase,
honestly, if we don't do it as soon as possible in the development
process, it is very unlikely we'll ever do it. but the tech isn't ready
for it, and we need to move forward, so, that's the only solution.
as such, I'll be rewritting the typescript to Rust, and we'll have a
very similar design to former Kind2, with Rust doing the parsing, IO and
error reporting, and a checker.hvm1 / checker.hvm2 file doing the actual
type-checking (and HOAS-based type-level evaluation).
also, I believe we'll not even generate .hvm from a .kind bootstrap.
while losing types is significant, being able to manually edit the HVM
is very helpful to debugging, it simplifies and accelerates the
development process and removes many complications that are bought by
bootstrapping
2024-02-23 20:28:04 +03:00
|
|
|
var [code, _] = parse_text(code, ")");
|
|
|
|
return [code, ctx => args.reduce((f, x) => App(f, x(ctx)), fun(ctx))];
|
|
|
|
}
|
|
|
|
// ANN: `{x : T}`
|
|
|
|
if (code[0] === "{") {
|
|
|
|
var [code, val] = parse_term(code.slice(1));
|
|
|
|
var [code, _ ] = parse_text(code, ":");
|
|
|
|
var [code, typ] = parse_term(code);
|
|
|
|
var [code, _ ] = parse_text(code, "}");
|
|
|
|
return [code, ctx => Ann(val(ctx), typ(ctx))];
|
|
|
|
}
|
|
|
|
// SLF: `$x T`
|
|
|
|
if (code[0] === "$") {
|
|
|
|
var [code, nam] = parse_name(code.slice(1));
|
|
|
|
var [code, bod] = parse_term(code);
|
|
|
|
return [code, ctx => Slf(nam, x => bod(Cons([nam, x], ctx)))];
|
|
|
|
}
|
|
|
|
// INS: `~x`
|
|
|
|
if (code[0] === "~") {
|
|
|
|
var [code, val] = parse_term(code.slice(1));
|
|
|
|
return [code, ctx => Ins(val(ctx))];
|
|
|
|
}
|
|
|
|
// SET: `*`
|
|
|
|
if (code[0] === "*") {
|
|
|
|
return [code.slice(1), ctx => Set()];
|
|
|
|
}
|
|
|
|
// LET: `let x = A in B`
|
|
|
|
if (code.slice(0,4) === "let ") {
|
|
|
|
var [code, nam] = parse_name(code.slice(4));
|
|
|
|
var [code, _ ] = parse_text(code, "=");
|
|
|
|
var [code, val] = parse_term(code);
|
|
|
|
var [code, bod] = parse_term(code);
|
|
|
|
return [code, ctx => Let(nam, val(ctx), x => bod(Cons([nam, x], ctx)))];
|
|
|
|
}
|
|
|
|
// U60: `#U60`
|
|
|
|
if (code.slice(0,4) === "#U60") {
|
|
|
|
return [code.slice(4), ctx => U60()];
|
|
|
|
}
|
|
|
|
// OP2: `#(<opr> fst snd)`
|
|
|
|
if (code.slice(0,2) === "#(") {
|
|
|
|
var [code, opr] = parse_oper(code.slice(2));
|
|
|
|
var [code, fst] = parse_term(code);
|
|
|
|
var [code, snd] = parse_term(code);
|
|
|
|
var [code, _] = parse_text(code, ")");
|
|
|
|
return [code, ctx => Op2(opr, fst(ctx), snd(ctx))];
|
|
|
|
}
|
|
|
|
// MAT: `#match x = val { #0: z; #+: s }: p`
|
|
|
|
if (code.slice(0,7) === "#match ") {
|
|
|
|
var [code, nam] = parse_name(code.slice(7));
|
|
|
|
var [code, _ ] = parse_text(code, "=");
|
|
|
|
var [code, x] = parse_term(code);
|
|
|
|
var [code, _ ] = parse_text(code, "{");
|
|
|
|
var [code, _ ] = parse_text(code, "#0:");
|
|
|
|
var [code, z] = parse_term(code);
|
|
|
|
var [code, _ ] = parse_text(code, "#+:");
|
|
|
|
var [code, s] = parse_term(code);
|
|
|
|
var [code, _ ] = parse_text(code, "}");
|
|
|
|
var [code, _ ] = parse_text(code, ":");
|
|
|
|
var [code, p] = parse_term(code);
|
|
|
|
return [code, ctx => Mat(nam, x(ctx), z(ctx), k => s(Cons([nam+"-1", k], ctx)), k => p(Cons([nam, k], ctx)))];
|
|
|
|
}
|
|
|
|
// NUM: `#num`
|
|
|
|
if (code[0] === "#") {
|
|
|
|
var [code, num] = parse_name(code.slice(1));
|
|
|
|
return [code, ctx => Num(BigInt(num))];
|
|
|
|
}
|
|
|
|
// CHR: `'a'` -- char syntax sugar
|
|
|
|
if (code[0] === "'") {
|
|
|
|
var [code, chr] = [code.slice(2), code[1]];
|
|
|
|
var [code, _] = parse_text(code, "'");
|
|
|
|
return [code, ctx => Num(BigInt(chr.charCodeAt(0)))];
|
|
|
|
}
|
|
|
|
// STR: `"text"` -- string syntax sugar
|
|
|
|
if (code[0] === "\"" || code[0] === "`") {
|
|
|
|
var str = "";
|
|
|
|
var end = code[0];
|
|
|
|
code = code.slice(1);
|
|
|
|
while (code[0] !== end) {
|
|
|
|
str += code[0];
|
|
|
|
code = code.slice(1);
|
|
|
|
}
|
|
|
|
code = code.slice(1);
|
|
|
|
return [code, ctx => Txt(str)];
|
|
|
|
}
|
|
|
|
// HOL: `?name`
|
|
|
|
if (code[0] === "?") {
|
|
|
|
var [code, nam] = parse_name(code.slice(1));
|
|
|
|
return [code, ctx => Hol(nam)];
|
|
|
|
}
|
|
|
|
// VAR: `name`
|
|
|
|
var [code, nam] = parse_name(code);
|
|
|
|
if (nam.length > 0) {
|
|
|
|
return [code, ctx => find(ctx, nam)];
|
|
|
|
}
|
|
|
|
throw "parse error";
|
|
|
|
}
|
|
|
|
|
|
|
|
export function do_parse_term(code: string): Term {
|
|
|
|
return parse_term(code)[1](Nil());
|
|
|
|
}
|
|
|
|
|
|
|
|
export function parse_def(code: string): [string, {nam: string, val: Term}] {
|
|
|
|
var [code, nam] = parse_name(skip(code));
|
|
|
|
if (skip(code)[0] === ":") {
|
|
|
|
var [code, _ ] = parse_text(code, ":");
|
|
|
|
var [code, typ] = parse_term(code);
|
|
|
|
var [code, _ ] = parse_text(code, "=");
|
|
|
|
var [code, val] = parse_term(code);
|
|
|
|
return [code, {nam: nam, val: Ann(val(Nil()), typ(Nil()))}];
|
|
|
|
} else {
|
|
|
|
var [code, _ ] = parse_text(code, "=");
|
|
|
|
var [code, val] = parse_term(code);
|
|
|
|
return [code, {nam: nam, val: val(Nil())}];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function parse_book(code: string): Book {
|
|
|
|
var book : Book = {};
|
|
|
|
while (code.length > 0) {
|
|
|
|
var [code, def] = parse_def(code);
|
|
|
|
book[def.nam] = def.val;
|
|
|
|
code = skip(code);
|
2024-02-23 05:43:20 +03:00
|
|
|
}
|
|
|
|
return book;
|
|
|
|
}
|
|
|
|
|
giving up on bootstrapping due to parser perf
initially, I wanted Kind2 to be bootstrapped, but I'm facing
difficulties, because pure hvm parser is still much slower than the
typescript parser; a few seconds vs instant to parse the entire
codebase. I think HVM2 will need to have proper immutable strings (i.e.,
nodes that expand as if they were cons-strings, but are stored as
buffers) before it can have fast parsers. as such, I'm making the
decision to NOT bootstrap it now. that is a tough decision becase,
honestly, if we don't do it as soon as possible in the development
process, it is very unlikely we'll ever do it. but the tech isn't ready
for it, and we need to move forward, so, that's the only solution.
as such, I'll be rewritting the typescript to Rust, and we'll have a
very similar design to former Kind2, with Rust doing the parsing, IO and
error reporting, and a checker.hvm1 / checker.hvm2 file doing the actual
type-checking (and HOAS-based type-level evaluation).
also, I believe we'll not even generate .hvm from a .kind bootstrap.
while losing types is significant, being able to manually edit the HVM
is very helpful to debugging, it simplifies and accelerates the
development process and removes many complications that are bought by
bootstrapping
2024-02-23 20:28:04 +03:00
|
|
|
export function do_parse_book(code: string): Book {
|
|
|
|
return parse_book(code);
|
|
|
|
}
|
|
|
|
|
|
|
|
// CLI
|
|
|
|
// ---
|
|
|
|
|
|
|
|
import * as fs from "fs";
|
|
|
|
import { execSync } from "child_process";
|
|
|
|
|
|
|
|
export function main() {
|
|
|
|
// Loads Kind's HVM checker.
|
|
|
|
var kind2_hvm1 = fs.readFileSync(__dirname + "/kind2.hvm1", "utf8");
|
|
|
|
|
|
|
|
// Loads all local ".kind2" files.
|
|
|
|
const code = fs.readdirSync(".")
|
|
|
|
.filter(file => file.endsWith(".kind2"))
|
|
|
|
.map(file => fs.readFileSync("./"+file, "utf8"))
|
|
|
|
.join("\n");
|
|
|
|
|
|
|
|
// Parses into book.
|
|
|
|
const book = do_parse_book(code);
|
|
|
|
|
|
|
|
// Compiles book to hvm1.
|
|
|
|
//var book_hvm1 = "Names = [" + Object.keys(book).map(x => '"'+x+'"').join(",") + "]\n";
|
|
|
|
//var ref_count = 0;
|
|
|
|
var used_refs = {};
|
|
|
|
var book_hvm1 = "";
|
|
|
|
for (let name in book) {
|
|
|
|
book_hvm1 += "Book." + name + " = (Ref \"" + name + "\" " + compile(book[name], used_refs) + ")\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
// Checks for unbound definitions
|
|
|
|
for (var ref_name in used_refs) {
|
|
|
|
if (!book[ref_name]) {
|
|
|
|
throw "Unbound definition: " + ref_name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Gets arguments.
|
|
|
|
const args = process.argv.slice(2);
|
|
|
|
const func = args[0];
|
|
|
|
const name = args[1];
|
|
|
|
|
|
|
|
// Creates main.
|
|
|
|
var main_hvm1 = "";
|
|
|
|
switch (func) {
|
|
|
|
case "check": {
|
|
|
|
main_hvm1 = "Main = (Checker Book." + name + ")\n";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case "run": {
|
|
|
|
main_hvm1 = "Main = (Normalizer Book." + name + ")\n";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default: {
|
|
|
|
console.log("Usage: kind2 [check|run] <name>");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generates the 'kind2.hvm1' file.
|
|
|
|
var checker_hvm1 = [kind2_hvm1, book_hvm1, main_hvm1].join("\n\n");
|
|
|
|
|
|
|
|
// Saves locally.
|
|
|
|
fs.writeFileSync("./.kind2.hvm1", checker_hvm1);
|
|
|
|
|
|
|
|
// Runs 'hvm1 kind2.hvm1 -s -L -1'
|
|
|
|
|
|
|
|
//const output = execSync("hvm1 run .kind2.hvm1 -s -L -1").toString();
|
|
|
|
|
|
|
|
//for (let name in book) {
|
|
|
|
//console.log("Checking: " + name);
|
|
|
|
|
|
|
|
const output = execSync("hvm1 run -t 1 -c -f .kind2.hvm1 \"(Main)\"").toString();
|
|
|
|
//const output = execSync(`hvm1 run -t 1 -c -f .kind2.hvm1 "(Checker Book.${name})"`).toString();
|
|
|
|
try {
|
|
|
|
var check_text = output.slice(output.indexOf("[["), output.indexOf("RWTS")).trim();
|
|
|
|
var stats_text = output.slice(output.indexOf("RWTS"));
|
|
|
|
var [logs, check] = JSON.parse(check_text);
|
|
|
|
logs.reverse();
|
|
|
|
for (var log of logs) {
|
|
|
|
console.log(log);
|
|
|
|
}
|
|
|
|
console.log(check ? "Check!" : "Error.");
|
|
|
|
console.log("");
|
|
|
|
console.log(stats_text);
|
|
|
|
} catch (e) {
|
|
|
|
console.log(output);
|
|
|
|
}
|
|
|
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
};
|
2024-02-23 03:31:53 +03:00
|
|
|
|
giving up on bootstrapping due to parser perf
initially, I wanted Kind2 to be bootstrapped, but I'm facing
difficulties, because pure hvm parser is still much slower than the
typescript parser; a few seconds vs instant to parse the entire
codebase. I think HVM2 will need to have proper immutable strings (i.e.,
nodes that expand as if they were cons-strings, but are stored as
buffers) before it can have fast parsers. as such, I'm making the
decision to NOT bootstrap it now. that is a tough decision becase,
honestly, if we don't do it as soon as possible in the development
process, it is very unlikely we'll ever do it. but the tech isn't ready
for it, and we need to move forward, so, that's the only solution.
as such, I'll be rewritting the typescript to Rust, and we'll have a
very similar design to former Kind2, with Rust doing the parsing, IO and
error reporting, and a checker.hvm1 / checker.hvm2 file doing the actual
type-checking (and HOAS-based type-level evaluation).
also, I believe we'll not even generate .hvm from a .kind bootstrap.
while losing types is significant, being able to manually edit the HVM
is very helpful to debugging, it simplifies and accelerates the
development process and removes many complications that are bought by
bootstrapping
2024-02-23 20:28:04 +03:00
|
|
|
main();
|