diff --git a/src/ast.ts b/src/ast.ts index 502fb55..3ed4330 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -1,6 +1,6 @@ -import { DUMMY_SPAN, LoadedFile, Span } from "./error"; +import { LoadedFile, Span } from "./error"; import { LitIntType } from "./lexer"; -import { ComplexMap, unwrap } from "./utils"; +import { ComplexMap } from "./utils"; export type Phase = { res: unknown; @@ -57,7 +57,7 @@ export type Crate

= { rootItems: Item

[]; itemsById: ComplexMap>; packageName: string; - rootFile: LoadedFile, + rootFile: LoadedFile; } & P["typeckResults"]; export type DepCrate = Crate; @@ -85,6 +85,10 @@ export class ItemId { return new ItemId(999999, 999999); } + static crateRoot(crate: CrateId): ItemId { + return new ItemId(crate, 0); + } + toString(): string { if (this.crateId === 0) { return `${this.itemIdx}`; @@ -535,29 +539,6 @@ export type TypeckResults = { main: Resolution | undefined; }; -export function findCrateItem

( - crate: Crate

, - id: ItemId -): Item

{ - if (id.crateId !== crate.id) { - throw new Error("trying to get item from the wrong crate"); - } - 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)); -} - // folders export type FoldFn = (value: From) => To; diff --git a/src/context.ts b/src/context.ts index d418368..cd30c7c 100644 --- a/src/context.ts +++ b/src/context.ts @@ -1,5 +1,5 @@ import { Crate, DepCrate, Final, Item, ItemId, Phase } from "./ast"; -import { DUMMY_SPAN, Span } from "./error"; +import { Span } from "./error"; import { Ids, unwrap } from "./utils"; import fs from "fs"; import path from "path"; @@ -20,17 +20,19 @@ export type CrateLoader = ( * dependencies (which also use the same context) do not care about that. */ export class GlobalContext { - public depCrates: Crate[] = []; + public finalizedCrates: Crate[] = []; public crateId: Ids = new Ids(); constructor(public opts: Options, public crateLoader: CrateLoader) {} public findItem

( id: ItemId, - localCrate: Crate

+ localCrate?: Crate

): Item

{ const crate = unwrap( - [localCrate, ...this.depCrates].find((crate) => crate.id === id.crateId) + [...(localCrate ? [localCrate] : []), ...this.finalizedCrates].find( + (crate) => crate.id === id.crateId + ) ); if (id.itemIdx === 0) { @@ -41,7 +43,7 @@ export class GlobalContext { contents: crate.rootItems, name: crate.packageName, }, - span: DUMMY_SPAN, + span: Span.startOfFile(crate.rootFile), id, }; } diff --git a/src/error.ts b/src/error.ts index d9797b6..1bb4a1a 100644 --- a/src/error.ts +++ b/src/error.ts @@ -3,30 +3,35 @@ export type LoadedFile = { content: string; }; -export type Span = { - start: number; - end: number; - file: LoadedFile; -}; +export class Span { + constructor( + public start: number, + public end: number, + public file: LoadedFile + ) {} -export function spanMerge(a: Span, b: Span): Span { - if (a.file !== b.file) { - throw new Error("cannot merge spans from different files"); + public merge(b: Span): Span { + if (this.file !== b.file) { + throw new Error("cannot merge spans from different files"); + } + + return new Span( + Math.min(this.start, b.start), + Math.max(this.end, b.end), + this.file + ); } - return { - start: Math.min(a.start, b.start), - end: Math.max(a.end, b.end), - file: a.file, - }; -} + public static eof(file: LoadedFile): Span { + return new Span(Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER, file); + } -export const DUMMY_SPAN: Span = { start: 0, end: 0, file: { content: "" } }; -export const eofSpan = (file: LoadedFile): Span => ({ - start: Number.MAX_SAFE_INTEGER, - end: Number.MAX_SAFE_INTEGER, - file, -}); + public static startOfFile(file: LoadedFile): Span { + return new Span(0, 1, file); + } + + public static DUMMY: Span = new Span(0, 0, { content: "" }); +} export class CompilerError extends Error { msg: string; @@ -98,11 +103,11 @@ function spanToSnippet(input: string, span: Span): string { } export function lines(file: LoadedFile): Span[] { - const lines: Span[] = [{ start: 0, end: 0, file }]; + const lines: Span[] = [new Span(0, 0, file)]; for (let i = 0; i < file.content.length; i++) { if (file.content[i] === "\n") { - lines.push({ start: i + 1, end: i + 1, file }); + lines.push(new Span(i + 1, i + 1, file)); } else { lines[lines.length - 1].end++; } diff --git a/src/index.ts b/src/index.ts index 605897b..26d6247 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import { LoadedFile, withErrorPrinter } from "./error"; +import { LoadedFile, Span, withErrorPrinter } from "./error"; import { isValidIdent, tokenize } from "./lexer"; import { lower as lowerToWasm } from "./lower"; import { ParseState, parse } from "./parser"; @@ -13,8 +13,6 @@ import { GlobalContext, parseArgs } from "./context"; import { loadCrate } from "./loader"; const INPUT = ` -extern mod std; - type A = { a: Int }; function main() = ( @@ -49,6 +47,8 @@ function main() { const gcx = new GlobalContext(opts, loadCrate); const mainCrate = gcx.crateId.next(); + gcx.crateLoader(gcx, "std", Span.startOfFile(file)); + withErrorPrinter( () => { const start = Date.now(); @@ -93,7 +93,9 @@ function main() { if (debug.has("wat")) { console.log("-----wasm--------------"); } - const wasmModule = lowerToWasm([typecked, ...gcx.depCrates]); + + gcx.finalizedCrates.push(typecked); + const wasmModule = lowerToWasm(gcx); const moduleStringColor = writeModuleWatToString(wasmModule, true); const moduleString = writeModuleWatToString(wasmModule); diff --git a/src/lexer.test.ts b/src/lexer.test.ts index 69c62e4..390d164 100644 --- a/src/lexer.test.ts +++ b/src/lexer.test.ts @@ -3,7 +3,7 @@ import { tokenize } from "./lexer"; it("should tokenize an emtpy function", () => { const input = `function hello() = ;`; - const tokens = tokenize(input); + const tokens = tokenize({ content: input }); expect(tokens).toMatchSnapshot(); }); @@ -11,7 +11,7 @@ it("should tokenize an emtpy function", () => { it("should tokenize hello world", () => { const input = `print("hello world")`; - const tokens = tokenize(input); + const tokens = tokenize({ content: input }); expect(tokens).toMatchSnapshot(); }); diff --git a/src/lexer.ts b/src/lexer.ts index 5586092..b850b9b 100644 --- a/src/lexer.ts +++ b/src/lexer.ts @@ -92,7 +92,7 @@ export function tokenize(file: LoadedFile): Token[] { finish: while (i < input.length) { const next = input[i]; - const span: Span = { start: i, end: i + 1, file }; + const span: Span = new Span(i, i + 1, file); if (next === "/" && input[i + 1] === "/") { while (input[i] !== "\n") { @@ -206,7 +206,7 @@ export function tokenize(file: LoadedFile): Token[] { default: throw new CompilerError( `invalid escape character: ${input[i]}`, - { start: span.end - 1, end: span.end, file } + new Span(span.end - 1, span.end, file) ); } continue; diff --git a/src/loader.ts b/src/loader.ts index 526382c..362da1b 100644 --- a/src/loader.ts +++ b/src/loader.ts @@ -45,7 +45,7 @@ export const loadCrate: CrateLoader = ( // We really, really want a good algorithm for finding crates. // But right now we just look for files in the CWD. - const existing = gcx.depCrates.find((crate) => crate.packageName === name); + const existing = gcx.finalizedCrates.find((crate) => crate.packageName === name); if (existing) { return existing; } @@ -64,7 +64,7 @@ export const loadCrate: CrateLoader = ( const typecked = typeck(gcx, resolved); - gcx.depCrates.push(typecked); + gcx.finalizedCrates.push(typecked); return typecked; }, () => { diff --git a/src/lower.ts b/src/lower.ts index 0c3a952..4f6d0a8 100644 --- a/src/lower.ts +++ b/src/lower.ts @@ -2,7 +2,6 @@ import { Crate, Expr, ExprBlock, - Final, Folder, FunctionDef, GlobalItem, @@ -16,12 +15,12 @@ import { TyStruct, TyTuple, Typecked, - findCrateItem, mkDefaultFolder, superFoldExpr, superFoldItem, varUnreachable, } from "./ast"; +import { GlobalContext } from "./context"; import { printTy } from "./printer"; import { ComplexMap, encodeUtf8, unwrap } from "./utils"; import * as wasm from "./wasm/defs"; @@ -61,7 +60,7 @@ export type Context = { reservedHeapMemoryStart: number; funcIndices: ComplexMap; globalIndices: ComplexMap; - crates: Crate[]; + gcx: GlobalContext; relocations: Relocation[]; knownDefPaths: ComplexMap; }; @@ -112,13 +111,6 @@ function appendData(cx: Context, newData: Uint8Array): number { } } -function findItem(cx: Context, id: ItemId): Item { - return findCrateItem( - unwrap(cx.crates.find((crate) => crate.id === id.crateId)), - id - ); -} - const KNOWN_DEF_PATHS = [ALLOCATE_ITEM]; function getKnownDefPaths( @@ -155,8 +147,8 @@ function getKnownDefPaths( return knows; } -export function lower(crates: Crate[]): wasm.Module { - const knownDefPaths = getKnownDefPaths(crates); +export function lower(gcx: GlobalContext): wasm.Module { + const knownDefPaths = getKnownDefPaths(gcx.finalizedCrates); const mod: wasm.Module = { types: [], @@ -183,12 +175,12 @@ export function lower(crates: Crate[]): wasm.Module { }); const cx: Context = { + gcx, mod, funcTypes: new ComplexMap(), funcIndices: new ComplexMap(), globalIndices: new ComplexMap(), reservedHeapMemoryStart: 0, - crates, relocations: [], knownDefPaths, }; @@ -221,7 +213,7 @@ export function lower(crates: Crate[]): wasm.Module { } }); } - crates.forEach((ast) => lowerMod(ast.rootItems)); + gcx.finalizedCrates.forEach((ast) => lowerMod(ast.rootItems)); const HEAP_ALIGN = 0x08; cx.reservedHeapMemoryStart = @@ -229,7 +221,7 @@ export function lower(crates: Crate[]): wasm.Module { ? (mod.datas[0].init.length + (HEAP_ALIGN - 1)) & ~(HEAP_ALIGN - 1) : 0; - addRt(cx, crates); + addRt(cx, gcx.finalizedCrates); // THE LINKER const offset = cx.mod.imports.length; @@ -447,7 +439,7 @@ function lowerExpr( break; } case "item": { - const item = findItem(fcx.cx, res.id); + const item = fcx.cx.gcx.findItem(res.id); if (item.kind !== "global") { throw new Error("cannot store to non-global item"); } @@ -529,7 +521,7 @@ function lowerExpr( break; } case "item": { - const item = findItem(fcx.cx, res.id); + const item = fcx.cx.gcx.findItem(res.id); switch (item.kind) { case "global": { const instr: wasm.Instr = { kind: "global.get", imm: DUMMY_IDX }; diff --git a/src/parser.ts b/src/parser.ts index 8897031..3aa8f83 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -32,7 +32,7 @@ import { GlobalItem, StructLiteralField, } from "./ast"; -import { CompilerError, eofSpan, LoadedFile, Span, spanMerge } from "./error"; +import { CompilerError, LoadedFile, Span } from "./error"; import { BaseToken, Token, TokenIdent, TokenLitString } from "./lexer"; import { ComplexMap, ComplexSet, Ids } from "./utils"; @@ -297,7 +297,7 @@ function mkParserExprBinary( [t, tok] = next(t); let rhs; [t, rhs] = parser(t); - const span = spanMerge(lhs.span, rhs.span); + const span = lhs.span.merge(rhs.span); return [t, mkExpr(lhs, rhs, span, tok.kind)]; } @@ -380,7 +380,7 @@ function parseExprCall(t: State): [State, Expr] { kind: "fieldAccess", lhs, field: { span: access.span, value }, - span: spanMerge(lhs.span, access.span), + span: lhs.span.merge(access.span), }; } } @@ -676,7 +676,7 @@ function expectNext( if (!tok) { throw new CompilerError( `expected \`${kind}\`, found end of file`, - eofSpan(t.file) + Span.eof(t.file) ); } if (tok.kind !== kind) { @@ -691,7 +691,7 @@ function expectNext( function next(t: State): [State, Token] { const [rest, next] = maybeNextT(t); if (!next) { - throw new CompilerError("unexpected end of file", eofSpan(t.file)); + throw new CompilerError("unexpected end of file", Span.eof(t.file)); } return [rest, next]; } diff --git a/src/resolve.ts b/src/resolve.ts index 5ae1547..0129816 100644 --- a/src/resolve.ts +++ b/src/resolve.ts @@ -19,7 +19,7 @@ import { ExternItem, } from "./ast"; import { GlobalContext } from "./context"; -import { CompilerError, spanMerge } from "./error"; +import { CompilerError } from "./error"; import { ComplexMap } from "./utils"; const BUILTIN_SET = new Set(BUILTINS); @@ -128,11 +128,14 @@ function resolveModule( }; } - if (ident.name === cx.ast.packageName) { - return { - kind: "item", - id: new ItemId(cx.ast.id, 0), - }; + // All loaded crates are in scope. + for (const crate of [cx.ast, ...cx.gcx.finalizedCrates]) { + if (ident.name === crate.packageName) { + return { + kind: "item", + id: ItemId.crateRoot(crate.id), + }; + } } if (BUILTIN_SET.has(ident.name)) { @@ -278,7 +281,7 @@ function resolveModule( kind: "path", segments: [...segments, expr.field.value], res: pathRes, - span: spanMerge(lhs.span, expr.field.span), + span: lhs.span.merge(expr.field.span), }; } } diff --git a/src/typeck.test.ts b/src/typeck.test.ts index bf79aee..64d5c9a 100644 --- a/src/typeck.test.ts +++ b/src/typeck.test.ts @@ -1,7 +1,9 @@ import { TY_INT, TY_STRING, TY_UNIT } from "./ast"; -import { DUMMY_SPAN as SPAN } from "./error"; +import { Span } from "./error"; import { InferContext } from "./typeck"; +const SPAN: Span = Span.startOfFile({content: ""}); + it("should infer types across assignments", () => { const infcx = new InferContext(); diff --git a/src/typeck.ts b/src/typeck.ts index 9a1999a..0081aec 100644 --- a/src/typeck.ts +++ b/src/typeck.ts @@ -28,7 +28,6 @@ import { Typecked, TyStruct, Item, - findCrateItem, StructLiteralField, } from "./ast"; import { GlobalContext } from "./context"; @@ -154,7 +153,7 @@ export function typeck( } } - const item = findCrateItem(ast, itemId); + const item = gcx.findItem(itemId, ast); const ty = itemTys.get(itemId); if (ty) { return ty; @@ -420,11 +419,10 @@ export function typeck( if (ast.id === 0) { // Only the final id=0 crate needs and cares about main. if (!main) { - throw new CompilerError(`\`main\` function not found`, { - start: 0, - end: 1, - file: ast.rootFile, - }); + throw new CompilerError( + `\`main\` function not found`, + Span.startOfFile(ast.rootFile) + ); } typecked.typeckResults = {