structemit/main.ts
2025-03-18 16:00:00 +01:00

447 lines
13 KiB
TypeScript

import * as parser from "./parser.out.js";
import * as yaml from "jsr:@std/yaml";
import * as ast from "./ast.ts";
import * as cjg from "https://raw.githubusercontent.com/camper0008/cjsongen/refs/heads/main/mod.ts";
async function main() {
const filename = Deno.args[0];
const text = await Deno.readTextFile(filename);
const ast = ((): ast.Struct[] | undefined => {
try {
return parser.parse(text, {
grammarSource: filename,
});
} catch (e) {
if (e instanceof parser.SyntaxError) {
console.error(
e.format([{ source: filename, text }]),
);
return undefined;
}
throw e;
}
})();
if (!ast) {
return;
}
//console.log(yaml.stringify(ast));
const rep = new Reporter(text);
const re = new Resolver(ast, rep).resolve();
const units = new DependencyTracer(ast, re, rep).trace();
const unitMap = mapUnitIds(units);
new CircularDependencyFinder(unitMap, rep)
.findCircularDependencies();
const entries = entryPoints(units, rep);
if (rep.errorOccured) {
console.log("Errors occured. Stopping...");
}
// const hir: cjg.repr.hir.Struct = {
// name: "Product",
// values: {
// value: "int",
// },
// };
//
// const mir = cjg.repr.mir.fromHir(hir);
// const nodes = cjg.repr.fromMir(mir);
// const structDefCode = cjg.gen.typedef.structDef(nodes);
// const serDefCode = cjg.gen.json.serializerDef(nodes);
// const serImplCode = cjg.gen.json.serializerImpl(nodes);
// console.log(structDefCode);
// console.log(serDefCode);
// console.log(serImplCode);
}
type MonoUnit = {
id: number;
unit: Unit;
generics?: Map<number, ast.Ty>;
};
class Monomorphizer {
public constructor(
private units: Unit[],
) {}
public monomorph(): MonoUnit[] {
const monoUnits: MonoUnit[] = [];
for (const unit of this.units) {
}
return monoUnits;
}
}
class CircularDependencyFinder {
private hasCirc = new Set<number>();
public constructor(
private units: Map<number, Unit>,
private rep: Reporter,
) {}
public findCircularDependencies() {
for (const id of this.units.keys()) {
this.searchDependencies(id);
}
}
private searchDependencies(
id: number,
defined = new Set<number>(),
) {
if (this.hasCirc.has(id)) {
return;
}
defined.add(id);
const unit = this.units.get(id)!;
for (const depId of unit.dependencies) {
const dep = this.units.get(depId)!;
if (defined.has(depId)) {
this.rep.error(
`circular dependency between '${unit.struct.ident}' and '${dep.struct.ident}'`,
);
this.hasCirc.add(unit.id);
return;
}
this.searchDependencies(depId, new Set(defined));
}
}
}
function entryPoints(units: Unit[], rep: Reporter): Set<number> {
const entries = units.filter((unit) => unit.dependencies.size === 0);
if (entries.length === 0) {
rep.error(
"no entry points in dependency graph (everything depends on something else)",
);
}
return new Set(entries.map((entry) => entry.struct.id));
}
function mapUnitIds(units: Unit[]): Map<number, Unit> {
return new Map(units.map((v, i) => [i, v]));
}
type Unit = {
id: number;
struct: ast.Struct;
dependencies: Set<number>;
};
class DependencyTracer {
public constructor(
private ast: ast.Struct[],
private re: Map<number, Resol>,
private rep: Reporter,
) {}
public trace(): Unit[] {
const units: Unit[] = [];
for (const struct of this.ast) {
const dependencies = new StructTracer(struct, this.re).trace();
units.push({ id: struct.id, struct, dependencies });
}
return units;
}
}
class StructTracer {
private dependencies = new Set<number>();
public constructor(
private struct: ast.Struct,
private re: Map<number, Resol>,
) {}
public trace(): Set<number> {
for (const field of this.struct.fields) {
this.traceTy(field.ty);
}
return this.dependencies;
}
private traceTy(ty: ast.Ty) {
const k = ty.kind;
switch (k.tag) {
case "array":
this.traceTy(k.ty);
return;
case "generic": {
const re = this.re.get(ty.id)!;
if (re.tag !== "struct") {
return;
}
this.dependencies.add(re.struct.id);
for (const ty of k.args) {
this.traceTy(ty);
}
return;
}
case "struct_literal":
for (const field of k.fields) {
this.traceTy(field.ty);
}
return;
case "ident": {
const re = this.re.get(ty.id)!;
if (re.tag !== "struct") {
return;
}
this.dependencies.add(re.struct.id);
return;
}
case "int":
return;
}
const _: never = k;
}
}
type Resol =
| { tag: "error" }
| { tag: "undefined" }
| { tag: "struct"; struct: ast.Struct }
| { tag: "generic"; struct: ast.Struct; idx: number }
| { tag: "primitive" };
type Rib = {
defs: Map<string, Resol>;
};
class Resolver {
private resols = new Map<number, Resol>();
private ribs: Rib[] = [{ defs: new Map() }];
public constructor(
private ast: ast.Struct[],
private rep: Reporter,
) {}
public resolve(): Map<number, Resol> {
for (const struct of this.ast) {
const res = this.define(struct.ident, { tag: "struct", struct });
if (res === "already defined") {
this.reportAlreadyDefined(struct.ident, struct.loc);
}
}
for (const struct of this.ast) {
this.ribs.push({ defs: new Map() });
if (struct.generics) {
for (const [idx, ident] of struct.generics.params.entries()) {
const res = this.define(ident, {
tag: "generic",
struct,
idx,
});
if (res === "already defined") {
this.reportAlreadyDefined(ident, struct.loc);
}
}
}
const fields = new Map<string, ast.Field>();
for (const field of struct.fields) {
this.resolveTy(field.ty);
if (fields.has(field.ident)) {
this.rep.error(
`field '${field.ident}' already defined`,
field.loc,
);
const otherField = fields.get(field.ident)!;
this.rep.info(
`original field '${otherField.ident}' defined here`,
otherField.loc,
);
continue;
}
fields.set(field.ident, field);
}
this.ribs.pop();
}
return this.resols;
}
private resolveTy(ty: ast.Ty) {
const k = ty.kind;
switch (k.tag) {
case "array":
this.resolveTy(k.ty);
return;
case "generic": {
const re = this.resolveIdent(k.ident);
if (re.tag !== "struct") {
this.rep.error(`identifier '${k.ident}' is not a struct`);
this.resols.set(ty.id, { tag: "error" });
return;
}
if (!re.struct.generics) {
this.rep.error(
`struct '${re.struct.ident}' does not accept generics`,
);
this.resols.set(ty.id, { tag: "error" });
return;
}
if (re.struct.generics.params.length !== k.args.length) {
this.rep.error(
`incorrect amount of generic paramters, expected ${re.struct.generics.params.length}, got ${k.args.length}`,
);
this.resols.set(ty.id, { tag: "error" });
return;
}
this.resols.set(ty.id, re);
for (const arg of k.args) {
this.resolveTy(arg);
}
return;
}
case "struct_literal": {
const fields = new Map<string, ast.Field>();
for (const field of k.fields) {
this.resolveTy(field.ty);
if (fields.has(field.ident)) {
this.rep.error(
`field '${field.ident}' already defined`,
field.loc,
);
const otherField = fields.get(field.ident)!;
this.rep.info(
`original field '${otherField.ident}' defined here`,
otherField.loc,
);
continue;
}
fields.set(field.ident, field);
}
return;
}
case "ident": {
const re = this.resolveIdent(k.ident);
if (re.tag === "undefined") {
this.rep.error(
`identifier '${k.ident}' is not defined`,
ty.loc,
);
}
this.resols.set(ty.id, re);
return;
}
case "int":
return;
}
const _: never = k;
}
private reportAlreadyDefined(ident: string, loc: ast.Loc) {
const re = this.resolveIdent(ident);
switch (re.tag) {
case "error":
case "undefined":
throw new Error();
case "struct":
this.rep.error(`identifier '${ident}' already defined`, loc);
this.rep.info(
`struct '${re.struct.ident}' defined here`,
re.struct.loc,
);
break;
case "generic":
this.rep.error(`identifier '${ident}' already defined`, loc);
this.rep.info(
`generic parameter '${
re.struct.generics!.params[re.idx]
}' defined here`,
re.struct.loc,
);
break;
case "primitive":
this.rep.error(`cannot redefine primitive '${ident}'`, loc);
break;
}
}
private static primitives = ["bool", "int", "float", "str"];
private resolveIdent(ident: string): Resol {
if (Resolver.primitives.includes(ident)) {
return { tag: "primitive" };
}
for (const rib of this.ribs.toReversed()) {
if (rib.defs.has(ident)) {
return rib.defs.get(ident)!;
}
}
return { tag: "undefined" };
}
private define(ident: string, re: Resol): "ok" | "already defined" {
if (this.rib().defs.has(ident)) {
return "already defined";
}
this.rib().defs.set(ident, re);
return "ok";
}
private rib(): Rib {
return this.ribs.at(-1)!;
}
}
class Reporter {
public errorOccured = false;
public constructor(
private text: string,
) {}
public error(msg: string, loc?: ast.Loc) {
this.errorOccured = true;
console.error(
`%cerror%c: ${msg}%c`,
"color: red; font-weight: bold",
"font-weight: bold",
"",
);
if (loc) {
this.printLoc(loc);
}
}
public info(msg: string, loc?: ast.Loc) {
console.error(
`%cinfo%c: ${msg}%c`,
"color: cyan; font-weight: bold",
"font-weight: bold",
"",
);
if (loc) {
this.printLoc(loc, "cyan");
}
}
private printLoc(loc: ast.Loc, caretColor = "red") {
const line = this.text.split("\n")[loc.start.line - 1];
const posPad = " ".repeat(loc.start.column - 1);
const lineNr = loc.start.line.toString().padStart(3, " ");
const lPad = " ".repeat(lineNr.length + 1);
const pos = `./${loc.source}:${loc.start.line}:${loc.start.column}`;
console.error(
`%c --> ${pos}\n${lPad}|\n${lineNr} |%c${line}%c\n${lPad}|${posPad}%c^%c`,
"color: gray",
"color: lightgray",
"color: gray",
`color: ${caretColor}; font-weight: bold`,
"",
);
}
}
if (import.meta.main) {
main();
}