init
This commit is contained in:
commit
1298b69388
47
demo.hlcsl
Normal file
47
demo.hlcsl
Normal file
@ -0,0 +1,47 @@
|
||||
|
||||
(def nand (a b vin) (r) (
|
||||
(let (a_and_b) (relay_default_off a b))
|
||||
(set r (relay_default_on a_and_b vin))
|
||||
))
|
||||
|
||||
(def not (a) (r) (
|
||||
(set r (nand a a))
|
||||
))
|
||||
|
||||
(def and (a b) (r) (
|
||||
(set r (not (nand a b)))
|
||||
))
|
||||
|
||||
(def or (a b) (r) (
|
||||
(set r (nand (not a) (not b)))
|
||||
))
|
||||
|
||||
(def xor (a b) (r) (
|
||||
(set r (and (or a b) (not (and a b))))
|
||||
))
|
||||
|
||||
(def xor_optimized (a b) (r) (
|
||||
(let (c) (nand a b))
|
||||
(set r (nand (nand a c) (nand b c)))
|
||||
))
|
||||
|
||||
(def half_add (a b) (r0 r1) (
|
||||
(set r0 (xor a b))
|
||||
(set r1 (and a b))
|
||||
))
|
||||
|
||||
(def add (a b carry) (r0 r1) (
|
||||
(let (d0 d1) (half_add a b))
|
||||
(let (e0 e1) (half_add carry d0))
|
||||
(set r0 e0)
|
||||
(set r1 (or d1 e1))
|
||||
))
|
||||
|
||||
(def add2 (a0 a1 b0 b1 carry_in) (r0 r1 carry_out) (
|
||||
(let (d0 d1) (add a0 b0 carry_in))
|
||||
(let (e0 e1) (add a1 b1 d1))
|
||||
(set r0 d0)
|
||||
(set r1 e0)
|
||||
(set carry_out e1)
|
||||
))
|
||||
|
||||
5
deno.jsonc
Normal file
5
deno.jsonc
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"fmt": {
|
||||
"indentWidth": 4
|
||||
}
|
||||
}
|
||||
79
src/ast.ts
Normal file
79
src/ast.ts
Normal file
@ -0,0 +1,79 @@
|
||||
export interface Visitor {
|
||||
visitDef?(def: Def): void | "stop";
|
||||
visitStmt?(stmt: Stmt): void | "stop";
|
||||
visitExpr?(expr: Expr): void | "stop";
|
||||
}
|
||||
|
||||
export class Def {
|
||||
constructor(
|
||||
public line: number,
|
||||
public ident: string,
|
||||
public inputs: string[],
|
||||
public outputs: string[],
|
||||
public stmts: Stmt[],
|
||||
) {}
|
||||
|
||||
visit(v: Visitor) {
|
||||
if (v.visitDef?.(this) === "stop") return;
|
||||
for (const stmt of this.stmts) {
|
||||
stmt.visit(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Stmt {
|
||||
constructor(
|
||||
public line: number,
|
||||
public kind: StmtKind,
|
||||
) {}
|
||||
|
||||
visit(v: Visitor) {
|
||||
if (v.visitStmt?.(this) === "stop") return;
|
||||
this.visitSubtree(v);
|
||||
}
|
||||
|
||||
visitSubtree(v: Visitor) {
|
||||
const k = this.kind;
|
||||
switch (k.tag) {
|
||||
case "Set":
|
||||
k.subject.visit(v);
|
||||
k.expr.visit(v);
|
||||
break;
|
||||
case "Let":
|
||||
k.expr.visit(v);
|
||||
break;
|
||||
default:
|
||||
k satisfies never;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type StmtKind =
|
||||
| { tag: "Set"; subject: Expr; expr: Expr }
|
||||
| { tag: "Let"; idents: string[]; expr: Expr };
|
||||
|
||||
export class Expr {
|
||||
constructor(
|
||||
public line: number,
|
||||
public kind: ExprKind,
|
||||
) {}
|
||||
|
||||
visit(v: Visitor) {
|
||||
if (v.visitExpr?.(this) === "stop") return;
|
||||
const k = this.kind;
|
||||
switch (k.tag) {
|
||||
case "Ident":
|
||||
break;
|
||||
case "Call":
|
||||
k.callee.visit(v);
|
||||
k.args.map((e) => e.visit(v));
|
||||
break;
|
||||
default:
|
||||
k satisfies never;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type ExprKind =
|
||||
| { tag: "Ident"; ident: string }
|
||||
| { tag: "Call"; callee: Expr; args: Expr[] };
|
||||
308
src/front.ts
Normal file
308
src/front.ts
Normal file
@ -0,0 +1,308 @@
|
||||
import { Def, Expr, Stmt } from "./ast.ts";
|
||||
|
||||
function appended<T>(vs: T[], v: T): T[] {
|
||||
vs.push(v);
|
||||
return vs;
|
||||
}
|
||||
|
||||
export type Tok = {
|
||||
ty: string;
|
||||
text: string;
|
||||
line: number;
|
||||
};
|
||||
|
||||
export function tokenize(text: string): Tok[] {
|
||||
return text
|
||||
.replace(/\/\/[^\n]*/g, "")
|
||||
.replace(/[\(\)\n]/g, " $& ")
|
||||
.split(/[ \t\r]+/)
|
||||
.filter((tok) => tok !== "")
|
||||
.reduce<[{ tok: string; line: number }[], number]>(
|
||||
([toks, line], tok) =>
|
||||
tok != "\n"
|
||||
? [appended(toks, { tok, line }), line]
|
||||
: [toks, line + 1],
|
||||
[[], 1],
|
||||
)[0]
|
||||
.map(({ tok, line }) => ({ ty: tok, text: tok, line }))
|
||||
.map((tok) => {
|
||||
if (/^[a-zA-Z_][a-zA-Z_0-9]*$/.test(tok.text)) {
|
||||
tok.ty = "ident";
|
||||
}
|
||||
return tok;
|
||||
});
|
||||
}
|
||||
|
||||
export type SExpr = {
|
||||
line: number;
|
||||
ty: "Ident" | "List";
|
||||
ident?: string;
|
||||
exprs?: SExpr[];
|
||||
};
|
||||
|
||||
function parseSExpr(toks: Tok[]): [Tok[], SExpr | null] {
|
||||
if (toks.length == 0) {
|
||||
return [toks, null];
|
||||
}
|
||||
const line = toks[0].line;
|
||||
if (toks[0].ty === "ident") {
|
||||
const ident = toks[0].text;
|
||||
return [toks.slice(1), { line, ty: "Ident", ident }];
|
||||
} else if (toks[0].ty === "(") {
|
||||
toks = toks.slice(1);
|
||||
const exprs: SExpr[] = [];
|
||||
while (toks.length != 0 && toks[0].ty !== ")") {
|
||||
let expr: SExpr | null;
|
||||
[toks, expr] = parseSExpr(toks);
|
||||
if (!expr) {
|
||||
throw new Error(`expected expression on line ${line}`);
|
||||
}
|
||||
exprs.push(expr);
|
||||
}
|
||||
if (toks.length == 0 || toks[0].ty != ")") {
|
||||
throw new Error(
|
||||
`expected ')' on line ${
|
||||
toks.at(0)?.line ?? "(eof)"
|
||||
}, '(' on line ${line}`,
|
||||
);
|
||||
}
|
||||
return [toks.slice(1), { line, ty: "List", exprs }];
|
||||
} else {
|
||||
throw new Error(`malformed on line ${line}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function parseSExprs(toks: Tok[]): SExpr[] {
|
||||
const exprs: SExpr[] = [];
|
||||
while (toks.length != 0) {
|
||||
let expr: SExpr | null;
|
||||
[toks, expr] = parseSExpr(toks);
|
||||
exprs.push(expr!);
|
||||
}
|
||||
return exprs;
|
||||
}
|
||||
|
||||
class SExprMatcher {
|
||||
private failed = false;
|
||||
private captures = new Map<string, SExpr>();
|
||||
|
||||
constructor(private s: SExpr) {}
|
||||
|
||||
match(): Record<string, SExpr> | null {
|
||||
return this.failed ? null : Object.fromEntries(this.captures.entries());
|
||||
}
|
||||
|
||||
ident(val?: string): this {
|
||||
if (this.s.ty !== "Ident" || val && this.s.ident! !== val) {
|
||||
this.failed = true;
|
||||
return this;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
list(length?: number): this {
|
||||
if (
|
||||
this.s.ty !== "List" || (length && this.s.exprs!.length !== length)
|
||||
) {
|
||||
this.failed = true;
|
||||
return this;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
at(idx: number, func?: (s: SExprMatcher) => SExprMatcher): this {
|
||||
if (this.s.ty !== "List" || idx >= this.s.exprs!.length) {
|
||||
this.failed = true;
|
||||
return this;
|
||||
}
|
||||
if (func) {
|
||||
const inner = new SExprMatcher(this.s.exprs![idx]);
|
||||
func(inner);
|
||||
for (const [key, val] of inner.captures.entries()) {
|
||||
this.captures.set(key, val);
|
||||
}
|
||||
this.failed = this.failed || inner.failed;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
all(func: (s: SExprMatcher) => SExprMatcher): this {
|
||||
if (this.s.ty !== "List") {
|
||||
this.failed = true;
|
||||
return this;
|
||||
}
|
||||
for (const s1 of this.s.exprs!) {
|
||||
const inner = new SExprMatcher(s1);
|
||||
func(inner);
|
||||
for (const [key, val] of inner.captures.entries()) {
|
||||
this.captures.set(key, val);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
capture(id: string): this {
|
||||
this.captures.set(id, this.s);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
export function parseAst(ss: SExpr[]): Def[] {
|
||||
const defs: Def[] = [];
|
||||
for (const s of ss) {
|
||||
defs.push(parseDef(s));
|
||||
}
|
||||
return defs;
|
||||
}
|
||||
|
||||
function parseDef(s: SExpr): Def {
|
||||
const m = new SExprMatcher(s)
|
||||
.list(5)
|
||||
.at(0, (s) => s.ident("def"))
|
||||
.at(1, (s) => s.ident().capture("ident"))
|
||||
.at(2, (s) => s.all((s) => s.ident()).capture("inputs"))
|
||||
.at(3, (s) => s.all((s) => s.ident()).capture("outputs"))
|
||||
.at(4, (s) => s.list().capture("stmts"))
|
||||
.match();
|
||||
if (!m) {
|
||||
throw new Error(`malformed def on line ${s.line}`);
|
||||
}
|
||||
return new Def(
|
||||
s.line,
|
||||
m.ident.ident!,
|
||||
m.inputs.exprs!.map((s) => s.ident!),
|
||||
m.outputs.exprs!.map((s) => s.ident!),
|
||||
m.stmts.exprs!.map((s) => parseStmt(s)),
|
||||
);
|
||||
}
|
||||
|
||||
function parseStmt(s: SExpr): Stmt {
|
||||
const set_match = new SExprMatcher(s)
|
||||
.list(3)
|
||||
.at(0, (s) => s.ident("set"))
|
||||
.at(1, (s) => s.ident().capture("subject"))
|
||||
.at(2, (s) => s.capture("expr"))
|
||||
.match();
|
||||
if (set_match) {
|
||||
const m = set_match;
|
||||
return new Stmt(
|
||||
s.line,
|
||||
{
|
||||
tag: "Set",
|
||||
subject: parseExpr(m.subject),
|
||||
expr: parseExpr(m.expr),
|
||||
},
|
||||
);
|
||||
}
|
||||
const let_match = new SExprMatcher(s)
|
||||
.list(3)
|
||||
.at(0, (s) => s.ident("let"))
|
||||
.at(1, (s) => s.all((s) => s.ident()).capture("idents"))
|
||||
.at(2, (s) => s.capture("expr"))
|
||||
.match();
|
||||
if (let_match) {
|
||||
const m = let_match;
|
||||
|
||||
return new Stmt(
|
||||
s.line,
|
||||
{
|
||||
tag: "Let",
|
||||
idents: m.idents.exprs!.map((s) => s.ident!),
|
||||
expr: parseExpr(m.expr),
|
||||
},
|
||||
);
|
||||
}
|
||||
throw new Error(`malformed statement on line ${s.line}`);
|
||||
}
|
||||
|
||||
function parseExpr(s: SExpr): Expr {
|
||||
if (s.ty === "Ident") {
|
||||
return new Expr(s.line, { tag: "Ident", ident: s.ident! });
|
||||
} else if (s.ty === "List") {
|
||||
if (s.exprs!.length === 0) {
|
||||
throw new Error(`empty expression not allowed, on line ${s.line}`);
|
||||
}
|
||||
if (s.exprs![0].ty !== "Ident") {
|
||||
throw new Error(`callee must be an identifier, on line ${s.line}`);
|
||||
}
|
||||
return new Expr(
|
||||
s.line,
|
||||
{
|
||||
tag: "Call",
|
||||
callee: parseExpr(s.exprs![0]),
|
||||
args: s.exprs!.slice(1).map((s) => parseExpr(s)),
|
||||
},
|
||||
);
|
||||
} else {
|
||||
throw new Error(`expected expression, on line ${s.line}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function resolve(defs: Def[]): Map<Def, Map<Expr, Sym>> {
|
||||
const defSyms = new Map<string, Def>();
|
||||
for (const def of defs) {
|
||||
defSyms.set(def.ident, def);
|
||||
}
|
||||
const resols = new Map<Def, Map<Expr, Sym>>();
|
||||
for (const def of defs) {
|
||||
const defResolver = new DefResolver(def, defSyms);
|
||||
defResolver.resolve();
|
||||
resols.set(def, defResolver.resols);
|
||||
}
|
||||
return resols;
|
||||
}
|
||||
|
||||
export type Sym =
|
||||
| { tag: "Builtin" }
|
||||
| { tag: "Input"; idx: number }
|
||||
| { tag: "Output"; idx: number }
|
||||
| { tag: "Node"; stmt: Stmt; idx: number }
|
||||
| { tag: "Def"; def: Def };
|
||||
|
||||
class DefResolver {
|
||||
private syms = new Map<string, Sym>([
|
||||
["relay_default_off", { tag: "Builtin" }],
|
||||
["relay_default_on", { tag: "Builtin" }],
|
||||
]);
|
||||
public resols = new Map<Expr, Sym>();
|
||||
|
||||
constructor(private def: Def, private defSyms: Map<string, Def>) {}
|
||||
|
||||
resolve() {
|
||||
for (const [idx, ident] of this.def.inputs.entries()) {
|
||||
this.syms.set(ident, { tag: "Input", idx });
|
||||
}
|
||||
for (const [idx, ident] of this.def.outputs.entries()) {
|
||||
this.syms.set(ident, { tag: "Output", idx });
|
||||
}
|
||||
const { syms, defSyms, resols } = this;
|
||||
this.def.visit({
|
||||
visitStmt(stmt) {
|
||||
stmt.visitSubtree(this);
|
||||
if (stmt.kind.tag === "Let") {
|
||||
for (const [idx, ident] of stmt.kind.idents.entries()) {
|
||||
syms.set(ident, { tag: "Node", stmt, idx });
|
||||
}
|
||||
}
|
||||
return "stop";
|
||||
},
|
||||
visitExpr(expr) {
|
||||
if (expr.kind.tag === "Ident") {
|
||||
const sym = syms.get(expr.kind.ident);
|
||||
if (sym) {
|
||||
resols.set(expr, sym);
|
||||
return;
|
||||
}
|
||||
const defSym = defSyms.get(expr.kind.ident);
|
||||
if (defSym) {
|
||||
resols.set(expr, { tag: "Def", def: defSym });
|
||||
return;
|
||||
}
|
||||
throw new Error(
|
||||
`unresolved identifier '${expr.kind.ident}', on line ${expr.line}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
220
src/main.ts
Normal file
220
src/main.ts
Normal file
@ -0,0 +1,220 @@
|
||||
import * as front from "./front.ts";
|
||||
import * as ast from "./ast.ts";
|
||||
|
||||
class Ins {
|
||||
constructor(
|
||||
public line: number,
|
||||
public kind: InsKind,
|
||||
) {}
|
||||
}
|
||||
|
||||
type InsKind =
|
||||
| { tag: "Input"; idx: number }
|
||||
| { tag: "Set"; idx: number; value: Ins }
|
||||
| { tag: "RelayDefaultOff" | "RelayDefaultOn"; args: Ins[] }
|
||||
| { tag: "Call"; def: ast.Def; args: Ins[] }
|
||||
| { tag: "Elem"; value: Ins; idx: number };
|
||||
|
||||
class InsCx {
|
||||
public insts: Ins[] = [];
|
||||
|
||||
makeInput(line: number, idx: number): Ins {
|
||||
return this.make(line, { tag: "Input", idx });
|
||||
}
|
||||
makeSet(line: number, idx: number, value: Ins): Ins {
|
||||
return this.make(line, { tag: "Set", idx, value });
|
||||
}
|
||||
makeRelayDefaultOff(line: number, args: Ins[]): Ins {
|
||||
return this.make(line, { tag: "RelayDefaultOff", args });
|
||||
}
|
||||
makeRelayDefaultOn(line: number, args: Ins[]): Ins {
|
||||
return this.make(line, { tag: "RelayDefaultOn", args });
|
||||
}
|
||||
makeCall(line: number, def: ast.Def, args: Ins[]): Ins {
|
||||
return this.make(line, { tag: "Call", def, args });
|
||||
}
|
||||
makeElem(line: number, value: Ins, idx: number): Ins {
|
||||
return this.make(line, { tag: "Elem", value, idx });
|
||||
}
|
||||
|
||||
private make(line: number, kind: InsKind) {
|
||||
const ins = new Ins(line, kind);
|
||||
this.insts.push(ins);
|
||||
return ins;
|
||||
}
|
||||
|
||||
pretty(): string {
|
||||
let result = "";
|
||||
let regIds = 0;
|
||||
const insRegs = new Map<Ins, number>();
|
||||
|
||||
const r = (i: Ins): string => {
|
||||
if (!insRegs.has(i)) {
|
||||
insRegs.set(i, regIds++);
|
||||
}
|
||||
return `%${insRegs.get(i)!}`;
|
||||
};
|
||||
|
||||
for (const ins of this.insts) {
|
||||
switch (ins.kind.tag) {
|
||||
case "Input":
|
||||
result += ` ${r(ins)} = input ${ins.kind.idx}\n`;
|
||||
break;
|
||||
case "Set":
|
||||
result += ` set ${ins.kind.idx}, ${r(ins.kind.value)}\n`;
|
||||
break;
|
||||
case "RelayDefaultOff":
|
||||
result += ` ${r(ins)} = relay_default_off ${
|
||||
r(ins.kind.args[0])
|
||||
} ${r(ins.kind.args[1])}\n`;
|
||||
break;
|
||||
case "RelayDefaultOn":
|
||||
result += ` ${r(ins)} = relay_default_on ${
|
||||
r(ins.kind.args[0])
|
||||
} ${r(ins.kind.args[1])}\n`;
|
||||
break;
|
||||
case "Call":
|
||||
result += ` ${r(ins)} = call @${ins.kind.def.ident} ${
|
||||
r(ins.kind.args[0])
|
||||
} ${r(ins.kind.args[1])}\n`;
|
||||
break;
|
||||
case "Elem":
|
||||
result += ` ${r(ins)} = elem ${
|
||||
r(ins.kind.value)
|
||||
} ${ins.kind.idx}\n`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class DefLowerer {
|
||||
private symIns = new Map<front.Sym, Ins>();
|
||||
private letIns = new Map<ast.Stmt, Ins[]>();
|
||||
|
||||
constructor(
|
||||
private cx: InsCx,
|
||||
private def: ast.Def,
|
||||
private resols: Map<ast.Expr, front.Sym>,
|
||||
) {}
|
||||
|
||||
lower() {
|
||||
for (const stmt of this.def.stmts) {
|
||||
this.lowerStmt(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
lowerStmt(stmt: ast.Stmt) {
|
||||
switch (stmt.kind.tag) {
|
||||
case "Set": {
|
||||
const re = this.resols.get(stmt.kind.subject)!;
|
||||
switch (re.tag) {
|
||||
case "Output":
|
||||
this.cx.makeSet(
|
||||
stmt.line,
|
||||
re.idx,
|
||||
this.lowerExpr(stmt.kind.expr),
|
||||
);
|
||||
break;
|
||||
case "Builtin":
|
||||
case "Input":
|
||||
case "Node":
|
||||
case "Def":
|
||||
throw new Error();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "Let": {
|
||||
const value = this.lowerExpr(stmt.kind.expr);
|
||||
this.letIns.set(
|
||||
stmt,
|
||||
stmt.kind.idents.map((_, i) =>
|
||||
this.cx.makeElem(stmt.line, value, i)
|
||||
),
|
||||
);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
stmt.kind satisfies never;
|
||||
}
|
||||
}
|
||||
|
||||
lowerExpr(expr: ast.Expr): Ins {
|
||||
switch (expr.kind.tag) {
|
||||
case "Ident": {
|
||||
const re = this.resols.get(expr)!;
|
||||
switch (re.tag) {
|
||||
case "Input":
|
||||
return this.cx.makeInput(expr.line, re.idx);
|
||||
case "Node": {
|
||||
return this.letIns.get(re.stmt)![re.idx];
|
||||
}
|
||||
case "Builtin":
|
||||
case "Output":
|
||||
case "Def":
|
||||
throw new Error();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "Call": {
|
||||
const re = this.resols.get(expr.kind.callee)!;
|
||||
switch (re.tag) {
|
||||
case "Builtin": {
|
||||
if (expr.kind.callee.kind.tag !== "Ident") {
|
||||
throw new Error();
|
||||
}
|
||||
switch (expr.kind.callee.kind.ident) {
|
||||
case "relay_default_off":
|
||||
return this.cx.makeRelayDefaultOff(
|
||||
expr.line,
|
||||
expr.kind.args.map((expr) =>
|
||||
this.lowerExpr(expr)
|
||||
),
|
||||
);
|
||||
case "relay_default_on":
|
||||
return this.cx.makeRelayDefaultOn(
|
||||
expr.line,
|
||||
expr.kind.args.map((expr) =>
|
||||
this.lowerExpr(expr)
|
||||
),
|
||||
);
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "Def": {
|
||||
return this.cx.makeCall(
|
||||
expr.line,
|
||||
re.def,
|
||||
expr.kind.args.map((expr) => this.lowerExpr(expr)),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "Input":
|
||||
case "Output":
|
||||
case "Node":
|
||||
throw new Error();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
expr.kind satisfies never;
|
||||
}
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
const text = await Deno.readTextFile(Deno.args[0]);
|
||||
const toks = front.tokenize(text);
|
||||
const sexprs = front.parseSExprs(toks);
|
||||
const defs = front.parseAst(sexprs);
|
||||
const resols = front.resolve(defs);
|
||||
|
||||
for (const def of defs) {
|
||||
console.log(`${def.ident}:`);
|
||||
const cx = new InsCx();
|
||||
new DefLowerer(cx, def, resols.get(def)!).lower();
|
||||
console.log(cx.pretty());
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user