mirror of
https://github.com/Noratrieb/riverdelta.git
synced 2026-01-15 08:55:04 +01:00
start implememting crate loading
This commit is contained in:
parent
a0599342ea
commit
b3b0bc2e6f
12 changed files with 249 additions and 165 deletions
|
|
@ -17,6 +17,7 @@ module.exports = {
|
||||||
// Some silly rules forbidding things that are not wrong:
|
// Some silly rules forbidding things that are not wrong:
|
||||||
"no-constant-condition": "off",
|
"no-constant-condition": "off",
|
||||||
"no-empty": "off",
|
"no-empty": "off",
|
||||||
|
"@typescript-eslint/no-empty-function": "off",
|
||||||
// Typescript already checks problematic fallthrough.
|
// Typescript already checks problematic fallthrough.
|
||||||
// The eslint rule is a bit dumb and also complains about
|
// The eslint rule is a bit dumb and also complains about
|
||||||
// obvious clear fallthrough like `case "a": case "b"`.
|
// obvious clear fallthrough like `case "a": case "b"`.
|
||||||
|
|
|
||||||
59
src/ast.ts
59
src/ast.ts
|
|
@ -1,12 +1,6 @@
|
||||||
import { Span } from "./error";
|
import { Span } from "./error";
|
||||||
import { LitIntType } from "./lexer";
|
import { LitIntType } from "./lexer";
|
||||||
|
|
||||||
export type Ast<P extends Phase> = {
|
|
||||||
rootItems: Item<P>[];
|
|
||||||
itemsById: Map<ItemId, Item<P>>;
|
|
||||||
packageName: string;
|
|
||||||
} & P["typeckResults"];
|
|
||||||
|
|
||||||
export type Phase = {
|
export type Phase = {
|
||||||
res: unknown;
|
res: unknown;
|
||||||
defPath: unknown;
|
defPath: unknown;
|
||||||
|
|
@ -45,6 +39,7 @@ export type Typecked = {
|
||||||
ty: HasTy;
|
ty: HasTy;
|
||||||
typeckResults: HasTypeckResults;
|
typeckResults: HasTypeckResults;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AnyPhase = {
|
export type AnyPhase = {
|
||||||
res: No | HasRes;
|
res: No | HasRes;
|
||||||
defPath: No | HasDefPath;
|
defPath: No | HasDefPath;
|
||||||
|
|
@ -52,6 +47,15 @@ export type AnyPhase = {
|
||||||
typeckResults: No | HasTypeckResults;
|
typeckResults: No | HasTypeckResults;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CrateId = number;
|
||||||
|
|
||||||
|
export type Crate<P extends Phase> = {
|
||||||
|
id: CrateId;
|
||||||
|
rootItems: Item<P>[];
|
||||||
|
itemsById: Map<ItemId, Item<P>>;
|
||||||
|
packageName: string;
|
||||||
|
} & P["typeckResults"];
|
||||||
|
|
||||||
export type Ident = {
|
export type Ident = {
|
||||||
name: string;
|
name: string;
|
||||||
span: Span;
|
span: Span;
|
||||||
|
|
@ -80,6 +84,10 @@ export type ItemKind<P extends Phase> =
|
||||||
| {
|
| {
|
||||||
kind: "mod";
|
kind: "mod";
|
||||||
node: ModItem<P>;
|
node: ModItem<P>;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
kind: "extern";
|
||||||
|
node: ExternItem;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Item<P extends Phase> = ItemKind<P> & {
|
export type Item<P extends Phase> = ItemKind<P> & {
|
||||||
|
|
@ -123,17 +131,10 @@ export type ImportDef<P extends Phase> = {
|
||||||
|
|
||||||
export type ModItem<P extends Phase> = {
|
export type ModItem<P extends Phase> = {
|
||||||
name: string;
|
name: string;
|
||||||
modKind: ModItemKind<P>;
|
contents: Item<P>[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ModItemKind<P extends Phase> =
|
export type ExternItem = { name: string };
|
||||||
| {
|
|
||||||
kind: "inline";
|
|
||||||
contents: Item<P>[];
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
kind: "extern";
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ExprEmpty = { kind: "empty" };
|
export type ExprEmpty = { kind: "empty" };
|
||||||
|
|
||||||
|
|
@ -486,7 +487,7 @@ export const TY_I32: Ty = { kind: "i32" };
|
||||||
export const TY_NEVER: Ty = { kind: "never" };
|
export const TY_NEVER: Ty = { kind: "never" };
|
||||||
|
|
||||||
export type TypeckResults = {
|
export type TypeckResults = {
|
||||||
main: Resolution;
|
main: Resolution | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
// folders
|
// folders
|
||||||
|
|
@ -521,7 +522,7 @@ export function mkDefaultFolder<
|
||||||
newItemsById: new Map(),
|
newItemsById: new Map(),
|
||||||
item(item) {
|
item(item) {
|
||||||
const newItem = this.itemInner(item);
|
const newItem = this.itemInner(item);
|
||||||
this.newItemsById.set(item.id, newItem);
|
this.newItemsById.set(newItem.id, newItem);
|
||||||
return newItem;
|
return newItem;
|
||||||
},
|
},
|
||||||
itemInner(_item) {
|
itemInner(_item) {
|
||||||
|
|
@ -534,14 +535,15 @@ export function mkDefaultFolder<
|
||||||
}
|
}
|
||||||
|
|
||||||
export function foldAst<From extends Phase, To extends Phase>(
|
export function foldAst<From extends Phase, To extends Phase>(
|
||||||
ast: Ast<From>,
|
ast: Crate<From>,
|
||||||
folder: Folder<From, To>
|
folder: Folder<From, To>
|
||||||
): Ast<To> {
|
): Crate<To> {
|
||||||
if ((folder.item as any)[ITEM_DEFAULT] !== ITEM_DEFAULT) {
|
if ((folder.item as any)[ITEM_DEFAULT] !== ITEM_DEFAULT) {
|
||||||
throw new Error("must not override `item` on folders");
|
throw new Error("must not override `item` on folders");
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
id: ast.id,
|
||||||
rootItems: ast.rootItems.map((item) => folder.item(item)),
|
rootItems: ast.rootItems.map((item) => folder.item(item)),
|
||||||
itemsById: folder.newItemsById,
|
itemsById: folder.newItemsById,
|
||||||
typeckResults: "typeckResults" in ast ? ast.typeckResults : undefined,
|
typeckResults: "typeckResults" in ast ? ast.typeckResults : undefined,
|
||||||
|
|
@ -603,29 +605,18 @@ export function superFoldItem<From extends Phase, To extends Phase>(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case "mod": {
|
case "mod": {
|
||||||
let kind: ModItemKind<To>;
|
|
||||||
const { modKind: itemKind } = item.node;
|
|
||||||
switch (itemKind.kind) {
|
|
||||||
case "inline":
|
|
||||||
kind = {
|
|
||||||
kind: "inline",
|
|
||||||
contents: itemKind.contents.map((item) => folder.item(item)),
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case "extern":
|
|
||||||
kind = { kind: "extern" };
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
kind: "mod",
|
kind: "mod",
|
||||||
node: {
|
node: {
|
||||||
name: item.node.name,
|
name: item.node.name,
|
||||||
modKind: kind,
|
contents: item.node.contents.map((item) => folder.item(item)),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case "extern": {
|
||||||
|
return { ...item, kind: "extern" };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,19 +23,19 @@ export class CompilerError extends Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function withErrorHandler(input: string, f: () => void): void {
|
export function withErrorPrinter(input: string, filename: string, f: () => void): void {
|
||||||
try {
|
try {
|
||||||
f();
|
f();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof CompilerError) {
|
if (e instanceof CompilerError) {
|
||||||
renderError(input, e);
|
renderError(input, filename, e);
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderError(input: string, e: CompilerError) {
|
function renderError(input: string, filename: string, e: CompilerError) {
|
||||||
const lineSpans = lines(input);
|
const lineSpans = lines(input);
|
||||||
const line =
|
const line =
|
||||||
e.span.start === Number.MAX_SAFE_INTEGER
|
e.span.start === Number.MAX_SAFE_INTEGER
|
||||||
|
|
@ -49,6 +49,8 @@ function renderError(input: string, e: CompilerError) {
|
||||||
const lineIdx = lineSpans.indexOf(line);
|
const lineIdx = lineSpans.indexOf(line);
|
||||||
const lineNo = lineIdx + 1;
|
const lineNo = lineIdx + 1;
|
||||||
console.error(`error: ${e.message}`);
|
console.error(`error: ${e.message}`);
|
||||||
|
console.error(` --> ${filename}:${lineNo}`);
|
||||||
|
|
||||||
|
|
||||||
console.error(`${lineNo} | ${spanToSnippet(input, line)}`);
|
console.error(`${lineNo} | ${spanToSnippet(input, line)}`);
|
||||||
const startRelLine =
|
const startRelLine =
|
||||||
|
|
|
||||||
69
src/index.ts
69
src/index.ts
|
|
@ -1,4 +1,4 @@
|
||||||
import { withErrorHandler } from "./error";
|
import { CompilerError, Span, withErrorPrinter } from "./error";
|
||||||
import { isValidIdent, tokenize } from "./lexer";
|
import { isValidIdent, tokenize } from "./lexer";
|
||||||
import { lower as lowerToWasm } from "./lower";
|
import { lower as lowerToWasm } from "./lower";
|
||||||
import { parse } from "./parser";
|
import { parse } from "./parser";
|
||||||
|
|
@ -9,23 +9,23 @@ import { writeModuleWatToString } from "./wasm/wat";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { exec } from "child_process";
|
import { exec } from "child_process";
|
||||||
import { Ast, Built, Typecked } from "./ast";
|
import { Crate, Built, Typecked } from "./ast";
|
||||||
|
import { Ids } from "./utils";
|
||||||
|
|
||||||
const INPUT = `
|
const INPUT = `
|
||||||
function main() = (
|
extern mod std;
|
||||||
prIntln(0);
|
|
||||||
);
|
|
||||||
|
|
||||||
function prIntln(x: Int) = (
|
function main() = (
|
||||||
print("\n");
|
std.pow(10, 2);
|
||||||
);
|
);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
|
let filename: string;
|
||||||
let input: string;
|
let input: string;
|
||||||
let packageName: string;
|
let packageName: string;
|
||||||
if (process.argv.length > 2) {
|
if (process.argv.length > 2) {
|
||||||
const filename = process.argv[2];
|
filename = process.argv[2];
|
||||||
if (path.extname(filename) !== ".nil") {
|
if (path.extname(filename) !== ".nil") {
|
||||||
console.error(
|
console.error(
|
||||||
`error: filename must have \`.nil\` extension: \`${filename}\``
|
`error: filename must have \`.nil\` extension: \`${filename}\``
|
||||||
|
|
@ -36,6 +36,7 @@ function main() {
|
||||||
input = fs.readFileSync(filename, { encoding: "utf-8" });
|
input = fs.readFileSync(filename, { encoding: "utf-8" });
|
||||||
packageName = path.basename(filename, ".nil");
|
packageName = path.basename(filename, ".nil");
|
||||||
} else {
|
} else {
|
||||||
|
filename = "<hardcoded>";
|
||||||
input = INPUT;
|
input = INPUT;
|
||||||
packageName = "test";
|
packageName = "test";
|
||||||
}
|
}
|
||||||
|
|
@ -49,14 +50,14 @@ function main() {
|
||||||
|
|
||||||
console.log(`package name: '${packageName}'`);
|
console.log(`package name: '${packageName}'`);
|
||||||
|
|
||||||
withErrorHandler(input, () => {
|
withErrorPrinter(input, filename, () => {
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
|
|
||||||
const tokens = tokenize(input);
|
const tokens = tokenize(input);
|
||||||
console.log("-----TOKENS------------");
|
console.log("-----TOKENS------------");
|
||||||
console.log(tokens);
|
console.log(tokens);
|
||||||
|
|
||||||
const ast: Ast<Built> = parse(packageName, tokens);
|
const ast: Crate<Built> = parse(packageName, tokens, 0);
|
||||||
console.log("-----AST---------------");
|
console.log("-----AST---------------");
|
||||||
|
|
||||||
console.dir(ast.rootItems, { depth: 50 });
|
console.dir(ast.rootItems, { depth: 50 });
|
||||||
|
|
@ -66,17 +67,17 @@ function main() {
|
||||||
console.log(printed);
|
console.log(printed);
|
||||||
|
|
||||||
console.log("-----AST resolved------");
|
console.log("-----AST resolved------");
|
||||||
const resolved = resolve(ast);
|
const [resolved, crates] = resolve(ast, loadCrate);
|
||||||
const resolvedPrinted = printAst(resolved);
|
const resolvedPrinted = printAst(resolved);
|
||||||
console.log(resolvedPrinted);
|
console.log(resolvedPrinted);
|
||||||
|
|
||||||
console.log("-----AST typecked------");
|
console.log("-----AST typecked------");
|
||||||
const typecked: Ast<Typecked> = typeck(resolved);
|
const typecked: Crate<Typecked> = typeck(resolved);
|
||||||
const typeckPrinted = printAst(typecked);
|
const typeckPrinted = printAst(typecked);
|
||||||
console.log(typeckPrinted);
|
console.log(typeckPrinted);
|
||||||
|
|
||||||
console.log("-----wasm--------------");
|
console.log("-----wasm--------------");
|
||||||
const wasmModule = lowerToWasm(typecked);
|
const wasmModule = lowerToWasm([typecked, ...crates]);
|
||||||
const moduleStringColor = writeModuleWatToString(wasmModule, true);
|
const moduleStringColor = writeModuleWatToString(wasmModule, true);
|
||||||
const moduleString = writeModuleWatToString(wasmModule);
|
const moduleString = writeModuleWatToString(wasmModule);
|
||||||
|
|
||||||
|
|
@ -105,4 +106,46 @@ function main() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadCrate(
|
||||||
|
name: string,
|
||||||
|
span: Span,
|
||||||
|
crateId: Ids,
|
||||||
|
existingCrates: Crate<Typecked>[]
|
||||||
|
): [Crate<Typecked>, Crate<Typecked>[]] {
|
||||||
|
// 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);
|
||||||
|
if (existing) {
|
||||||
|
return [existing, []];
|
||||||
|
}
|
||||||
|
|
||||||
|
const filename = `${name}.nil`;
|
||||||
|
let input;
|
||||||
|
try {
|
||||||
|
input = fs.readFileSync(filename, { encoding: "utf-8" });
|
||||||
|
} catch (e) {
|
||||||
|
throw new CompilerError(
|
||||||
|
`failed to load ${name}, could not fine \`${filename}\``,
|
||||||
|
span
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const tokens = tokenize(input);
|
||||||
|
const ast = parse(name, tokens, crateId.next());
|
||||||
|
const [resolved, crates] = resolve(ast, loadCrate);
|
||||||
|
const typecked = typeck(resolved);
|
||||||
|
return [typecked, crates];
|
||||||
|
} catch (e) {
|
||||||
|
withErrorPrinter(input, filename, () => {
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
throw new CompilerError(
|
||||||
|
`failed to load crate ${name}: crate contains errors`,
|
||||||
|
span
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
main();
|
main();
|
||||||
|
|
|
||||||
|
|
@ -230,10 +230,7 @@ export function tokenize(input: string): Token[] {
|
||||||
}
|
}
|
||||||
|
|
||||||
let type: LitIntType = "Int";
|
let type: LitIntType = "Int";
|
||||||
console.log(input[i + 2]);
|
|
||||||
if (input[i + 1] === "_" && isIdentStart(input[i + 2])) {
|
if (input[i + 1] === "_" && isIdentStart(input[i + 2])) {
|
||||||
console.log("yes", input.slice(i + 2, i + 5));
|
|
||||||
|
|
||||||
if (input.slice(i + 2, i + 5) === "Int") {
|
if (input.slice(i + 2, i + 5) === "Int") {
|
||||||
i += 4;
|
i += 4;
|
||||||
type = "Int";
|
type = "Int";
|
||||||
|
|
|
||||||
23
src/lower.ts
23
src/lower.ts
|
|
@ -1,5 +1,5 @@
|
||||||
import {
|
import {
|
||||||
Ast,
|
Crate,
|
||||||
Expr,
|
Expr,
|
||||||
ExprBlock,
|
ExprBlock,
|
||||||
FunctionDef,
|
FunctionDef,
|
||||||
|
|
@ -39,7 +39,7 @@ export type Context = {
|
||||||
funcTypes: ComplexMap<wasm.FuncType, wasm.TypeIdx>;
|
funcTypes: ComplexMap<wasm.FuncType, wasm.TypeIdx>;
|
||||||
reservedHeapMemoryStart: number;
|
reservedHeapMemoryStart: number;
|
||||||
funcIndices: ComplexMap<Resolution, FuncOrImport>;
|
funcIndices: ComplexMap<Resolution, FuncOrImport>;
|
||||||
ast: Ast<Typecked>;
|
crates: Crate<Typecked>[];
|
||||||
relocations: Relocation[];
|
relocations: Relocation[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -89,7 +89,7 @@ function appendData(cx: Context, newData: Uint8Array): number {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function lower(ast: Ast<Typecked>): wasm.Module {
|
export function lower(crates: Crate<Typecked>[]): wasm.Module {
|
||||||
const mod: wasm.Module = {
|
const mod: wasm.Module = {
|
||||||
types: [],
|
types: [],
|
||||||
funcs: [],
|
funcs: [],
|
||||||
|
|
@ -119,7 +119,7 @@ export function lower(ast: Ast<Typecked>): wasm.Module {
|
||||||
funcTypes: new ComplexMap(),
|
funcTypes: new ComplexMap(),
|
||||||
funcIndices: new ComplexMap(),
|
funcIndices: new ComplexMap(),
|
||||||
reservedHeapMemoryStart: 0,
|
reservedHeapMemoryStart: 0,
|
||||||
ast,
|
crates,
|
||||||
relocations: [],
|
relocations: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -135,15 +135,12 @@ export function lower(ast: Ast<Typecked>): wasm.Module {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "mod": {
|
case "mod": {
|
||||||
if (item.node.modKind.kind === "inline") {
|
lowerMod(item.node.contents);
|
||||||
lowerMod(item.node.modKind.contents);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
crates.forEach((ast) => lowerMod(ast.rootItems));
|
||||||
lowerMod(ast.rootItems);
|
|
||||||
|
|
||||||
const HEAP_ALIGN = 0x08;
|
const HEAP_ALIGN = 0x08;
|
||||||
cx.reservedHeapMemoryStart =
|
cx.reservedHeapMemoryStart =
|
||||||
|
|
@ -151,7 +148,7 @@ export function lower(ast: Ast<Typecked>): wasm.Module {
|
||||||
? (mod.datas[0].init.length + (HEAP_ALIGN - 1)) & ~(HEAP_ALIGN - 1)
|
? (mod.datas[0].init.length + (HEAP_ALIGN - 1)) & ~(HEAP_ALIGN - 1)
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
addRt(cx, ast);
|
addRt(cx, crates);
|
||||||
|
|
||||||
// THE LINKER
|
// THE LINKER
|
||||||
const offset = cx.mod.imports.length;
|
const offset = cx.mod.imports.length;
|
||||||
|
|
@ -863,14 +860,16 @@ function todo(msg: string): never {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make the program runnable using wasi-preview-1
|
// Make the program runnable using wasi-preview-1
|
||||||
function addRt(cx: Context, ast: Ast<Typecked>) {
|
function addRt(cx: Context, crates: Crate<Typecked>[]) {
|
||||||
const { mod } = cx;
|
const { mod } = cx;
|
||||||
|
|
||||||
|
const crate0 = unwrap(crates.find((crate) => crate.id === 0));
|
||||||
|
|
||||||
const mainCall: wasm.Instr = { kind: "call", func: 9999999 };
|
const mainCall: wasm.Instr = { kind: "call", func: 9999999 };
|
||||||
cx.relocations.push({
|
cx.relocations.push({
|
||||||
kind: "funccall",
|
kind: "funccall",
|
||||||
instr: mainCall,
|
instr: mainCall,
|
||||||
res: ast.typeckResults.main,
|
res: unwrap(crate0.typeckResults.main),
|
||||||
});
|
});
|
||||||
|
|
||||||
const start: wasm.Func = {
|
const start: wasm.Func = {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import {
|
import {
|
||||||
ARITH_FACTOR_KINDS,
|
ARITH_FACTOR_KINDS,
|
||||||
ARITH_TERM_KINDS,
|
ARITH_TERM_KINDS,
|
||||||
Ast,
|
Crate,
|
||||||
BinaryKind,
|
BinaryKind,
|
||||||
COMPARISON_KINDS,
|
COMPARISON_KINDS,
|
||||||
mkDefaultFolder,
|
mkDefaultFolder,
|
||||||
|
|
@ -27,6 +27,7 @@ import {
|
||||||
superFoldItem,
|
superFoldItem,
|
||||||
Built,
|
Built,
|
||||||
Parsed,
|
Parsed,
|
||||||
|
ExternItem,
|
||||||
} from "./ast";
|
} from "./ast";
|
||||||
import { CompilerError, Span, spanMerge } from "./error";
|
import { CompilerError, Span, spanMerge } from "./error";
|
||||||
import { BaseToken, Token, TokenIdent, TokenLitString } from "./lexer";
|
import { BaseToken, Token, TokenIdent, TokenLitString } from "./lexer";
|
||||||
|
|
@ -34,7 +35,11 @@ import { Ids } from "./utils";
|
||||||
|
|
||||||
type Parser<T> = (t: Token[]) => [Token[], T];
|
type Parser<T> = (t: Token[]) => [Token[], T];
|
||||||
|
|
||||||
export function parse(packageName: string, t: Token[]): Ast<Built> {
|
export function parse(
|
||||||
|
packageName: string,
|
||||||
|
t: Token[],
|
||||||
|
crateId: number
|
||||||
|
): Crate<Built> {
|
||||||
const items: Item<Parsed>[] = [];
|
const items: Item<Parsed>[] = [];
|
||||||
|
|
||||||
while (t.length > 0) {
|
while (t.length > 0) {
|
||||||
|
|
@ -43,7 +48,7 @@ export function parse(packageName: string, t: Token[]): Ast<Built> {
|
||||||
items.push(item);
|
items.push(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ast = buildAst(packageName, items);
|
const ast: Crate<Built> = buildCrate(packageName, items, crateId);
|
||||||
|
|
||||||
validateAst(ast);
|
validateAst(ast);
|
||||||
|
|
||||||
|
|
@ -137,14 +142,13 @@ function parseItem(t: Token[]): [Token[], Item<Parsed>] {
|
||||||
let name;
|
let name;
|
||||||
[t, name] = expectNext<TokenIdent>(t, "identifier");
|
[t, name] = expectNext<TokenIdent>(t, "identifier");
|
||||||
|
|
||||||
const node: ModItem<Parsed> = {
|
const node: ExternItem = {
|
||||||
name: name.ident,
|
name: name.ident,
|
||||||
modKind: { kind: "extern" },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
[t] = expectNext(t, ";");
|
[t] = expectNext(t, ";");
|
||||||
|
|
||||||
return [t, { kind: "mod", node, span: name.span, id: 0 }];
|
return [t, { kind: "extern", node, span: name.span, id: 0 }];
|
||||||
} else if (tok.kind === "mod") {
|
} else if (tok.kind === "mod") {
|
||||||
let name;
|
let name;
|
||||||
[t, name] = expectNext<TokenIdent>(t, "identifier");
|
[t, name] = expectNext<TokenIdent>(t, "identifier");
|
||||||
|
|
@ -165,7 +169,7 @@ function parseItem(t: Token[]): [Token[], Item<Parsed>] {
|
||||||
|
|
||||||
const node: ModItem<Parsed> = {
|
const node: ModItem<Parsed> = {
|
||||||
name: name.ident,
|
name: name.ident,
|
||||||
modKind: { kind: "inline", contents },
|
contents,
|
||||||
};
|
};
|
||||||
|
|
||||||
return [t, { kind: "mod", node, span: name.span, id: 0 }];
|
return [t, { kind: "mod", node, span: name.span, id: 0 }];
|
||||||
|
|
@ -658,7 +662,7 @@ function unexpectedToken(token: Token, expected: string): never {
|
||||||
throw new CompilerError(`unexpected token, expected ${expected}`, token.span);
|
throw new CompilerError(`unexpected token, expected ${expected}`, token.span);
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateAst(ast: Ast<Built>) {
|
function validateAst(ast: Crate<Built>) {
|
||||||
const seenItemIds = new Set();
|
const seenItemIds = new Set();
|
||||||
|
|
||||||
const validator: Folder<Built, Built> = {
|
const validator: Folder<Built, Built> = {
|
||||||
|
|
@ -719,11 +723,16 @@ function validateAst(ast: Ast<Built>) {
|
||||||
foldAst(ast, validator);
|
foldAst(ast, validator);
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildAst(packageName: string, rootItems: Item<Parsed>[]): Ast<Built> {
|
function buildCrate(
|
||||||
|
packageName: string,
|
||||||
|
rootItems: Item<Parsed>[],
|
||||||
|
crateId: number
|
||||||
|
): Crate<Built> {
|
||||||
const itemId = new Ids();
|
const itemId = new Ids();
|
||||||
const loopId = new Ids();
|
const loopId = new Ids();
|
||||||
|
|
||||||
const ast: Ast<Built> = {
|
const ast: Crate<Built> = {
|
||||||
|
id: crateId,
|
||||||
rootItems,
|
rootItems,
|
||||||
itemsById: new Map(),
|
itemsById: new Map(),
|
||||||
packageName,
|
packageName,
|
||||||
|
|
@ -733,7 +742,6 @@ function buildAst(packageName: string, rootItems: Item<Parsed>[]): Ast<Built> {
|
||||||
...mkDefaultFolder(),
|
...mkDefaultFolder(),
|
||||||
itemInner(item: Item<Parsed>): Item<Built> {
|
itemInner(item: Item<Parsed>): Item<Built> {
|
||||||
const id = itemId.next();
|
const id = itemId.next();
|
||||||
ast.itemsById.set(id, item);
|
|
||||||
return { ...superFoldItem(item, this), id };
|
return { ...superFoldItem(item, this), id };
|
||||||
},
|
},
|
||||||
expr(expr: Expr<Parsed>): Expr<Built> {
|
expr(expr: Expr<Parsed>): Expr<Built> {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import {
|
import {
|
||||||
AnyPhase,
|
AnyPhase,
|
||||||
Ast,
|
Crate,
|
||||||
Expr,
|
Expr,
|
||||||
FunctionDef,
|
FunctionDef,
|
||||||
IdentWithRes,
|
IdentWithRes,
|
||||||
|
|
@ -15,7 +15,7 @@ import {
|
||||||
tyIsUnit,
|
tyIsUnit,
|
||||||
} from "./ast";
|
} from "./ast";
|
||||||
|
|
||||||
export function printAst(ast: Ast<AnyPhase>): string {
|
export function printAst(ast: Crate<AnyPhase>): string {
|
||||||
return ast.rootItems.map(printItem).join("\n");
|
return ast.rootItems.map(printItem).join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,6 +39,9 @@ function printItem(item: Item<AnyPhase>): string {
|
||||||
case "mod": {
|
case "mod": {
|
||||||
return id + printMod(item.node);
|
return id + printMod(item.node);
|
||||||
}
|
}
|
||||||
|
case "extern": {
|
||||||
|
return id + `extern mod ${item.node.name};`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -73,14 +76,7 @@ function printImportDef(def: ImportDef<AnyPhase>): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
function printMod(mod: ModItem<AnyPhase>): string {
|
function printMod(mod: ModItem<AnyPhase>): string {
|
||||||
switch (mod.modKind.kind) {
|
return `mod ${mod.name} (\n${mod.contents.map(printItem).join("\n ")});`;
|
||||||
case "inline":
|
|
||||||
return `mod ${mod.name} (\n${mod.modKind.contents
|
|
||||||
.map(printItem)
|
|
||||||
.join("\n ")});`;
|
|
||||||
case "extern":
|
|
||||||
return `extern mod ${mod.name};`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function printExpr(expr: Expr<AnyPhase>, indent: number): string {
|
function printExpr(expr: Expr<AnyPhase>, indent: number): string {
|
||||||
|
|
|
||||||
122
src/resolve.ts
122
src/resolve.ts
|
|
@ -1,5 +1,5 @@
|
||||||
import {
|
import {
|
||||||
Ast,
|
Crate,
|
||||||
BUILTINS,
|
BUILTINS,
|
||||||
Built,
|
Built,
|
||||||
BuiltinName,
|
BuiltinName,
|
||||||
|
|
@ -16,56 +16,89 @@ import {
|
||||||
superFoldExpr,
|
superFoldExpr,
|
||||||
superFoldItem,
|
superFoldItem,
|
||||||
superFoldType,
|
superFoldType,
|
||||||
|
ExternItem,
|
||||||
|
Typecked,
|
||||||
} from "./ast";
|
} from "./ast";
|
||||||
import { CompilerError, spanMerge, todo } from "./error";
|
import { CompilerError, Span, spanMerge } from "./error";
|
||||||
import { unwrap } from "./utils";
|
import { Ids, unwrap } from "./utils";
|
||||||
|
|
||||||
const BUILTIN_SET = new Set<string>(BUILTINS);
|
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 = {
|
type Context = {
|
||||||
ast: Ast<Built>;
|
ast: Crate<Built>;
|
||||||
|
crates: Crate<Typecked>[];
|
||||||
modContentsCache: Map<ItemId, Map<string, ItemId>>;
|
modContentsCache: Map<ItemId, Map<string, ItemId>>;
|
||||||
newItemsById: Map<ItemId, Item<Resolved>>;
|
newItemsById: Map<ItemId, Item<Resolved>>;
|
||||||
|
crateLoader: CrateLoader;
|
||||||
|
crateId: Ids;
|
||||||
};
|
};
|
||||||
|
|
||||||
function resolveModItem(
|
function resolveModItem(
|
||||||
cx: Context,
|
cx: Context,
|
||||||
mod: ModItem<Built>,
|
mod: ModItem<Built> | ExternItem,
|
||||||
modId: ItemId,
|
item: Item<Built>,
|
||||||
name: string
|
name: string
|
||||||
): ItemId | undefined {
|
): ItemId | undefined {
|
||||||
const cachedContents = cx.modContentsCache.get(modId);
|
const cachedContents = cx.modContentsCache.get(item.id);
|
||||||
if (cachedContents) {
|
if (cachedContents) {
|
||||||
return cachedContents.get(name);
|
return cachedContents.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (mod.modKind.kind) {
|
let contents: Map<string, ItemId>;
|
||||||
case "inline": {
|
|
||||||
const contents = new Map(
|
if ("contents" in mod) {
|
||||||
mod.modKind.contents.map((item) => [item.node.name, item.id])
|
contents = new Map(mod.contents.map((item) => [item.node.name, item.id]));
|
||||||
);
|
} else {
|
||||||
cx.modContentsCache.set(modId, contents);
|
const [loadedCrate, itsDeps] = cx.crateLoader(
|
||||||
return contents.get(name);
|
item.node.name,
|
||||||
}
|
item.span,
|
||||||
case "extern": {
|
cx.crateId,
|
||||||
todo("extern mod items");
|
cx.crates
|
||||||
}
|
);
|
||||||
|
cx.crates.push(loadedCrate);
|
||||||
|
cx.crates.push(...itsDeps);
|
||||||
|
|
||||||
|
contents = new Map(
|
||||||
|
loadedCrate.rootItems.map((item) => [item.node.name, item.id])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cx.modContentsCache.set(item.id, contents);
|
||||||
|
return contents.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolve(ast: Ast<Built>): Ast<Resolved> {
|
export function resolve(
|
||||||
|
ast: Crate<Built>,
|
||||||
|
crateLoader: CrateLoader
|
||||||
|
): [Crate<Resolved>, Crate<Typecked>[]] {
|
||||||
|
const crateId = new Ids();
|
||||||
|
crateId.next(); // Local crate.
|
||||||
const cx: Context = {
|
const cx: Context = {
|
||||||
ast,
|
ast,
|
||||||
|
crates: [],
|
||||||
modContentsCache: new Map(),
|
modContentsCache: new Map(),
|
||||||
newItemsById: new Map(),
|
newItemsById: new Map(),
|
||||||
|
crateLoader,
|
||||||
|
crateId,
|
||||||
};
|
};
|
||||||
|
|
||||||
const rootItems = resolveModule(cx, [ast.packageName], ast.rootItems);
|
const rootItems = resolveModule(cx, [ast.packageName], ast.rootItems);
|
||||||
return {
|
return [
|
||||||
itemsById: cx.newItemsById,
|
{
|
||||||
rootItems,
|
id: ast.id,
|
||||||
packageName: ast.packageName,
|
itemsById: cx.newItemsById,
|
||||||
};
|
rootItems,
|
||||||
|
packageName: ast.packageName,
|
||||||
|
},
|
||||||
|
cx.crates,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveModule(
|
function resolveModule(
|
||||||
|
|
@ -129,7 +162,7 @@ function resolveModule(
|
||||||
|
|
||||||
const resolver: Folder<Built, Resolved> = {
|
const resolver: Folder<Built, Resolved> = {
|
||||||
...mkDefaultFolder(),
|
...mkDefaultFolder(),
|
||||||
itemInner(item) {
|
itemInner(item): Item<Resolved> {
|
||||||
const defPath = [...modName, item.node.name];
|
const defPath = [...modName, item.node.name];
|
||||||
|
|
||||||
switch (item.kind) {
|
switch (item.kind) {
|
||||||
|
|
@ -162,20 +195,23 @@ function resolveModule(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case "mod": {
|
case "mod": {
|
||||||
if (item.node.modKind.kind === "inline") {
|
const contents = resolveModule(cx, defPath, item.node.contents);
|
||||||
const contents = resolveModule(
|
return {
|
||||||
cx,
|
...item,
|
||||||
defPath,
|
kind: "mod",
|
||||||
item.node.modKind.contents
|
node: { ...item.node, contents },
|
||||||
);
|
defPath,
|
||||||
return {
|
};
|
||||||
...item,
|
}
|
||||||
kind: "mod",
|
case "extern": {
|
||||||
node: { ...item.node, modKind: { kind: "inline", contents } },
|
const node: ExternItem = {
|
||||||
defPath,
|
...item.node,
|
||||||
};
|
};
|
||||||
}
|
return {
|
||||||
break;
|
...item,
|
||||||
|
node,
|
||||||
|
defPath,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -230,7 +266,11 @@ function resolveModule(
|
||||||
|
|
||||||
if (res.kind === "item") {
|
if (res.kind === "item") {
|
||||||
const module = unwrap(cx.ast.itemsById.get(res.id));
|
const module = unwrap(cx.ast.itemsById.get(res.id));
|
||||||
if (module.kind === "mod") {
|
console.log("nested", module.kind, res.id, cx.ast.itemsById);
|
||||||
|
|
||||||
|
if (module.kind === "mod" || module.kind === "extern") {
|
||||||
|
console.log("resolve");
|
||||||
|
|
||||||
if (typeof expr.field.value === "number") {
|
if (typeof expr.field.value === "number") {
|
||||||
throw new CompilerError(
|
throw new CompilerError(
|
||||||
"module contents cannot be indexed with a number",
|
"module contents cannot be indexed with a number",
|
||||||
|
|
@ -241,7 +281,7 @@ function resolveModule(
|
||||||
const pathResItem = resolveModItem(
|
const pathResItem = resolveModItem(
|
||||||
cx,
|
cx,
|
||||||
module.node,
|
module.node,
|
||||||
module.id,
|
module,
|
||||||
expr.field.value
|
expr.field.value
|
||||||
);
|
);
|
||||||
if (pathResItem === undefined) {
|
if (pathResItem === undefined) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import {
|
import {
|
||||||
Ast,
|
Crate,
|
||||||
BuiltinName,
|
BuiltinName,
|
||||||
COMPARISON_KINDS,
|
COMPARISON_KINDS,
|
||||||
mkDefaultFolder,
|
mkDefaultFolder,
|
||||||
|
|
@ -14,7 +14,6 @@ import {
|
||||||
ItemId,
|
ItemId,
|
||||||
LOGICAL_KINDS,
|
LOGICAL_KINDS,
|
||||||
LoopId,
|
LoopId,
|
||||||
ModItemKind,
|
|
||||||
Resolution,
|
Resolution,
|
||||||
Resolved,
|
Resolved,
|
||||||
Ty,
|
Ty,
|
||||||
|
|
@ -116,7 +115,7 @@ function lowerAstTyBase(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function typeck(ast: Ast<Resolved>): Ast<Typecked> {
|
export function typeck(ast: Crate<Resolved>): Crate<Typecked> {
|
||||||
const itemTys = new Map<number, Ty | null>();
|
const itemTys = new Map<number, Ty | null>();
|
||||||
function typeOfItem(index: ItemId, cause: Span): Ty {
|
function typeOfItem(index: ItemId, cause: Span): Ty {
|
||||||
const item = unwrap(ast.itemsById.get(index));
|
const item = unwrap(ast.itemsById.get(index));
|
||||||
|
|
@ -162,7 +161,13 @@ export function typeck(ast: Ast<Resolved>): Ast<Typecked> {
|
||||||
}
|
}
|
||||||
case "mod": {
|
case "mod": {
|
||||||
throw new CompilerError(
|
throw new CompilerError(
|
||||||
`module ${item.node.name} is not a type`,
|
`module ${item.node.name} cannot be used as a type or value`,
|
||||||
|
cause
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "extern": {
|
||||||
|
throw new CompilerError(
|
||||||
|
`extern declaration ${item.node.name} cannot be used as a type or value`,
|
||||||
cause
|
cause
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -297,30 +302,20 @@ export function typeck(ast: Ast<Resolved>): Ast<Typecked> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case "mod": {
|
case "mod": {
|
||||||
switch (item.node.modKind.kind) {
|
return {
|
||||||
case "inline": {
|
...item,
|
||||||
const modKind: ModItemKind<Typecked> = {
|
node: {
|
||||||
kind: "inline",
|
...item.node,
|
||||||
contents: item.node.modKind.contents.map((item) =>
|
contents: item.node.contents.map((item) => this.item(item)),
|
||||||
this.item(item)
|
},
|
||||||
),
|
};
|
||||||
};
|
}
|
||||||
|
case "extern": {
|
||||||
return {
|
// Nothing to check.
|
||||||
...item,
|
return {
|
||||||
node: {
|
...item,
|
||||||
...item.node,
|
node: { ...item.node },
|
||||||
modKind,
|
};
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case "extern":
|
|
||||||
// Nothing to check.
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
node: { ...item.node, modKind: { ...item.node.modKind } },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -355,16 +350,19 @@ export function typeck(ast: Ast<Resolved>): Ast<Typecked> {
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!main) {
|
if (ast.id === 0) {
|
||||||
throw new CompilerError(`\`main\` function not found`, {
|
// Only the final id=0 crate needs and cares about main.
|
||||||
start: 0,
|
if (!main) {
|
||||||
end: 1,
|
throw new CompilerError(`\`main\` function not found`, {
|
||||||
});
|
start: 0,
|
||||||
}
|
end: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
typecked.typeckResults = {
|
typecked.typeckResults = {
|
||||||
main: { kind: "item", id: main.id },
|
main: { kind: "item", id: main.id },
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return typecked;
|
return typecked;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ export function encodeUtf8(s: string): Uint8Array {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Ids {
|
export class Ids {
|
||||||
nextId = 0;
|
private nextId = 0;
|
||||||
|
|
||||||
public next(): number {
|
public next(): number {
|
||||||
return this.nextId++;
|
return this.nextId++;
|
||||||
|
|
|
||||||
9
std.nil
Normal file
9
std.nil
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
function pow(base: Int, exp: Int): Int = (
|
||||||
|
let acc = 1;
|
||||||
|
loop (
|
||||||
|
if exp == 0 then break;
|
||||||
|
acc = acc * base;
|
||||||
|
exp = exp - 1;
|
||||||
|
);
|
||||||
|
acc
|
||||||
|
);
|
||||||
Loading…
Add table
Add a link
Reference in a new issue