refactor crate loading

This commit is contained in:
nora 2023-08-02 14:32:42 +02:00
parent dd93453943
commit beb0321382
9 changed files with 131 additions and 99 deletions

View file

@ -47,6 +47,11 @@ module.exports = {
],
// No, I will use `type` instead of `interface`.
"@typescript-eslint/consistent-type-definitions": ["error", "type"],
// This lint is horrible with noisy false positives every time there are typescript errors.
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
// Useful extra lints that are not on by default:
"@typescript-eslint/explicit-module-boundary-types": "warn",
// This has caused several bugs before. Thanks eslint!

View file

@ -41,6 +41,8 @@ export type Typecked = {
typeckResults: HasTypeckResults;
};
export type Final = Typecked;
export type AnyPhase = {
res: No | HasRes;
defPath: No | HasDefPath;
@ -57,6 +59,8 @@ export type Crate<P extends Phase> = {
packageName: string;
} & P["typeckResults"];
export type DepCrate = Crate<Final>;
export type Ident = {
name: string;
span: Span;

49
src/context.ts Normal file
View file

@ -0,0 +1,49 @@
import { Crate, DepCrate, Final, Item, ItemId, Phase } from "./ast";
import { DUMMY_SPAN, Span } from "./error";
import { Ids, unwrap } from "./utils";
export type CrateLoader = (
gcx: GlobalContext,
name: string,
span: Span
) => DepCrate;
/**
* The global context containing information about the _global compilation session_,
* like loaded crates.
* Notably, the global context is _not_ supposed to have information specific to the "local crate",
* because with the current compilation model, there is no "local crate" in a session.
*
* There is a "downstream"/"binary"/"final" crate with crateId=0, where `function main()` lives, but
* dependencies (which also use the same context) do not care about that.
*/
export class GlobalContext {
public depCrates: Crate<Final>[] = [];
public crateId: Ids = new Ids();
constructor(public crateLoader: CrateLoader) {}
public findItem<P extends Phase>(
id: ItemId,
localCrate: Crate<P>
): Item<P | Final> {
const crate = unwrap(
[localCrate, ...this.depCrates].find((crate) => crate.id === id.crateId)
);
if (id.itemIdx === 0) {
// Return a synthetic module representing the crate root.
return {
kind: "mod",
node: {
contents: crate.rootItems,
name: crate.packageName,
},
span: DUMMY_SPAN,
id,
};
}
return unwrap(crate.itemsById.get(id));
}
}

View file

@ -9,8 +9,8 @@ import { writeModuleWatToString } from "./wasm/wat";
import fs from "fs";
import path from "path";
import { exec } from "child_process";
import { Crate, Built, Typecked } from "./ast";
import { Ids } from "./utils";
import { Crate, Built, Typecked, DepCrate } from "./ast";
import { GlobalContext, CrateLoader } from "./context";
const INPUT = `
extern mod std;
@ -105,6 +105,9 @@ function main() {
process.exit(1);
}
const gcx = new GlobalContext(loadCrate);
const mainCrate = gcx.crateId.next();
withErrorPrinter(
input,
filename,
@ -117,7 +120,7 @@ function main() {
console.log(tokens);
}
const ast: Crate<Built> = parse(packageName, tokens, 0);
const ast: Crate<Built> = parse(packageName, tokens, mainCrate);
if (debug.has("ast")) {
console.log("-----AST---------------");
@ -131,7 +134,7 @@ function main() {
if (debug.has("resolved")) {
console.log("-----AST resolved------");
}
const [resolved, crates] = resolve(ast, loadCrate);
const resolved = resolve(gcx, ast);
if (debug.has("resolved")) {
const resolvedPrinted = printAst(resolved);
console.log(resolvedPrinted);
@ -140,7 +143,7 @@ function main() {
if (debug.has("typecked")) {
console.log("-----AST typecked------");
}
const typecked: Crate<Typecked> = typeck(resolved, crates);
const typecked: Crate<Typecked> = typeck(gcx, resolved);
if (debug.has("typecked")) {
const typeckPrinted = printAst(typecked);
console.log(typeckPrinted);
@ -149,7 +152,7 @@ function main() {
if (debug.has("wat")) {
console.log("-----wasm--------------");
}
const wasmModule = lowerToWasm([typecked, ...crates]);
const wasmModule = lowerToWasm([typecked, ...gcx.depCrates]);
const moduleStringColor = writeModuleWatToString(wasmModule, true);
const moduleString = writeModuleWatToString(wasmModule);
@ -186,42 +189,54 @@ function main() {
);
}
function loadCrate(
const loadCrate: CrateLoader = (
gcx: GlobalContext,
name: string,
span: Span,
crateId: Ids,
existingCrates: Crate<Typecked>[]
): [Crate<Typecked>, Crate<Typecked>[]] {
span: Span
): DepCrate => {
// We really, really want a good algorithm for finding crates.
// But right now we just look for files in the CWD.
const existing = existingCrates.find((crate) => crate.packageName === name);
const existing = gcx.depCrates.find((crate) => crate.packageName === name);
if (existing) {
return [existing, []];
return existing;
}
const filename = `${name}.nil`;
let input: string;
try {
input = fs.readFileSync(filename, { encoding: "utf-8" });
} catch (e) {
const options = [`${name}.nil`, `${name}/${name}.mod.nil`];
let input: string | undefined = undefined;
let filename: string | undefined = undefined;
options.forEach((tryName) => {
try {
input = fs.readFileSync(tryName, { encoding: "utf-8" });
filename = tryName;
} catch (e) {}
});
if (input === undefined || filename === undefined) {
throw new CompilerError(
`failed to load ${name}, could not fine \`${filename}\``,
`failed to load ${name}, could not find ${options.join(" or ")}`,
span
);
}
const inputString: string = input;
return withErrorPrinter(
input,
inputString,
filename,
() => {
const tokens = tokenize(input);
const ast = parse(name, tokens, crateId.next());
const [resolved, crates] = resolve(ast, loadCrate);
(): DepCrate => {
const crateId = gcx.crateId.next();
const tokens = tokenize(inputString);
const ast = parse(name, tokens, crateId);
const resolved = resolve(gcx, ast);
console.log(resolved);
const typecked = typeck(resolved, [...existingCrates, ...crates]);
return [typecked, crates];
const typecked = typeck(gcx, resolved);
gcx.depCrates.push(typecked);
return typecked;
},
() => {
throw new CompilerError(
@ -230,6 +245,6 @@ function loadCrate(
);
}
);
}
};
main();

View file

@ -23,7 +23,7 @@ it("should compute struct layout correctly", () => {
},
"types": [
{
"offset": 0,
"offset": 4,
"type": "i32",
},
],

View file

@ -2,6 +2,7 @@ import {
Crate,
Expr,
ExprBlock,
Final,
Folder,
FunctionDef,
GlobalItem,
@ -60,7 +61,7 @@ export type Context = {
reservedHeapMemoryStart: number;
funcIndices: ComplexMap<Resolution, FuncOrImport>;
globalIndices: ComplexMap<Resolution, wasm.GlobalIdx>;
crates: Crate<Typecked>[];
crates: Crate<Final>[];
relocations: Relocation[];
knownDefPaths: ComplexMap<string[], ItemId>;
};
@ -154,7 +155,7 @@ function getKnownDefPaths(
return knows;
}
export function lower(crates: Crate<Typecked>[]): wasm.Module {
export function lower(crates: Crate<Final>[]): wasm.Module {
const knownDefPaths = getKnownDefPaths(crates);
const mod: wasm.Module = {

View file

@ -17,37 +17,20 @@ import {
superFoldItem,
superFoldType,
ExternItem,
Typecked,
findCrateItem,
} from "./ast";
import { CompilerError, Span, spanMerge } from "./error";
import { ComplexMap, Ids, unwrap } from "./utils";
import { GlobalContext } from "./context";
import { CompilerError, spanMerge } from "./error";
import { ComplexMap } from "./utils";
const BUILTIN_SET = new Set<string>(BUILTINS);
export type CrateLoader = (
name: string,
span: Span,
crateId: Ids,
existingCrates: Crate<Typecked>[]
) => [Crate<Typecked>, Crate<Typecked>[]];
type Context = {
ast: Crate<Built>;
crates: Crate<Typecked>[];
gcx: GlobalContext;
modContentsCache: ComplexMap<ItemId, Map<string, ItemId>>;
newItemsById: ComplexMap<ItemId, Item<Resolved>>;
crateLoader: CrateLoader;
crateId: Ids;
};
function findItem(cx: Context, id: ItemId): Item<Built> {
const crate = unwrap(
[cx.ast, ...cx.crates].find((crate) => crate.id === id.crateId)
);
return findCrateItem(crate, id);
}
function resolveModItem(
cx: Context,
mod: ModItem<Built> | ExternItem,
@ -64,15 +47,7 @@ function resolveModItem(
if ("contents" in mod) {
contents = new Map(mod.contents.map((item) => [item.node.name, item.id]));
} else {
const [loadedCrate, itsDeps] = cx.crateLoader(
item.node.name,
item.span,
cx.crateId,
cx.crates
);
cx.crates.push(loadedCrate);
cx.crates.push(...itsDeps);
const loadedCrate = cx.gcx.crateLoader(cx.gcx, item.node.name, item.span);
contents = new Map(
loadedCrate.rootItems.map((item) => [item.node.name, item.id])
);
@ -83,30 +58,23 @@ function resolveModItem(
}
export function resolve(
ast: Crate<Built>,
crateLoader: CrateLoader
): [Crate<Resolved>, Crate<Typecked>[]] {
const crateId = new Ids();
crateId.next(); // Local crate.
gcx: GlobalContext,
ast: Crate<Built>
): Crate<Resolved> {
const cx: Context = {
ast,
crates: [],
gcx,
modContentsCache: new ComplexMap(),
newItemsById: new ComplexMap(),
crateLoader,
crateId,
};
const rootItems = resolveModule(cx, [ast.packageName], ast.rootItems);
return [
{
id: ast.id,
itemsById: cx.newItemsById,
rootItems,
packageName: ast.packageName,
},
cx.crates,
];
return {
id: ast.id,
itemsById: cx.newItemsById,
rootItems,
packageName: ast.packageName,
};
}
function resolveModule(
@ -280,7 +248,7 @@ function resolveModule(
lhs.kind === "ident" ? [lhs.value.name] : lhs.segments;
if (res.kind === "item") {
const module = findItem(cx, res.id);
const module = cx.gcx.findItem(res.id, cx.ast);
if (module.kind === "mod" || module.kind === "extern") {
if (typeof expr.field.value === "number") {

View file

@ -31,9 +31,10 @@ import {
findCrateItem,
StructLiteralField,
} from "./ast";
import { GlobalContext } from "./context";
import { CompilerError, Span } from "./error";
import { printTy } from "./printer";
import { ComplexMap, unwrap } from "./utils";
import { ComplexMap } from "./utils";
function mkTyFn(params: Ty[], returnTy: Ty): Ty {
return { kind: "fn", params, returnTy };
@ -123,17 +124,15 @@ function lowerAstTyBase(
}
export function typeck(
ast: Crate<Resolved>,
otherCrates: Crate<Typecked>[]
gcx: GlobalContext,
ast: Crate<Resolved>
): Crate<Typecked> {
const itemTys = new ComplexMap<ItemId, Ty | null>();
function typeOfItem(itemId: ItemId, cause: Span): Ty {
if (itemId.crateId !== ast.id) {
const crate = unwrap(
otherCrates.find((crate) => crate.id === itemId.crateId)
);
const item = findCrateItem(crate, itemId);
const item = gcx.findItem(itemId, ast);
switch (item.kind) {
case "function":
case "import":
@ -239,23 +238,13 @@ export function typeck(
);
}
function findItem(itemId: ItemId): Item<Resolved> {
if (itemId.crateId === ast.id) {
return findCrateItem(ast, itemId);
}
return findCrateItem(
unwrap(otherCrates.find((crate) => crate.id === itemId.crateId)),
itemId
);
}
const checker: Folder<Resolved, Typecked> = {
...mkDefaultFolder(),
itemInner(item: Item<Resolved>): Item<Typecked> {
switch (item.kind) {
case "function": {
const fnTy = typeOfItem(item.id, item.span) as TyFn;
const body = checkBody(item.node.body, fnTy, typeOfItem, findItem);
const body = checkBody(gcx, ast, item.node.body, fnTy, typeOfItem);
const returnType = item.node.returnType && {
...item.node.returnType,
@ -604,10 +593,11 @@ export class InferContext {
}
export function checkBody(
gcx: GlobalContext,
ast: Crate<Resolved>,
body: Expr<Resolved>,
fnTy: TyFn,
typeOfItem: (itemId: ItemId, cause: Span) => Ty,
findItem: (itemId: ItemId) => Item<Resolved>
typeOfItem: (itemId: ItemId, cause: Span) => Ty
): Expr<Typecked> {
const localTys = [...fnTy.params];
const loopState: { hasBreak: boolean; loopId: LoopId }[] = [];
@ -697,7 +687,7 @@ export function checkBody(
case "local":
break;
case "item": {
const item = findItem(res.id);
const item = gcx.findItem(res.id, ast);
if (item.kind !== "global") {
throw new CompilerError("cannot assign to item", expr.span);
}