diff --git a/compiler/ast/ast.ts b/compiler/ast/ast.ts
new file mode 100644
index 0000000..4040f6e
--- /dev/null
+++ b/compiler/ast/ast.ts
@@ -0,0 +1,23 @@
+export type File = {
+ stmts: Stmt[];
+};
+
+export type Stmt = {
+ id: number;
+ kind: StmtKind;
+};
+
+export type StmtKind =
+ | { tag: "error" }
+ | { tag: "mod_block" } & ModBlockStmt
+ | { tag: "mod_file" } & ModFileStmt;
+
+export type ModBlockStmt = {
+ ident: string;
+ stmts: Stmt[];
+};
+
+export type ModFileStmt = {
+ ident: string;
+ filePath: string;
+};
diff --git a/compiler/ast/mod.ts b/compiler/ast/mod.ts
new file mode 100644
index 0000000..7627d46
--- /dev/null
+++ b/compiler/ast/mod.ts
@@ -0,0 +1,2 @@
+export * from "./ast.ts";
+export * from "./visitor.ts";
diff --git a/compiler/ast/visitor.ts b/compiler/ast/visitor.ts
new file mode 100644
index 0000000..7c4ed88
--- /dev/null
+++ b/compiler/ast/visitor.ts
@@ -0,0 +1,62 @@
+import { exhausted } from "../util.ts";
+import { File, ModBlockStmt, ModFileStmt, Stmt } from "./ast.ts";
+
+export type VisitRes = "stop" | void;
+
+type R = VisitRes;
+type PM = unknown[];
+
+export interface Visitor<
+ P extends PM = [],
+> {
+ visitFile?(file: File, ...p: P): R;
+ visitStmt?(stmt: Stmt, ...p: P): R;
+ visitErrorStmt?(stmt: Stmt, ...p: P): R;
+ visitModBlockStmt?(stmt: Stmt, kind: ModBlockStmt, ...p: P): R;
+ visitModFileStmt?(stmt: Stmt, kind: ModFileStmt, ...p: P): R;
+}
+
+export function visitFile<
+ P extends PM = [],
+>(
+ v: Visitor
,
+ file: File,
+ ...p: P
+) {
+ if (v.visitFile?.(file, ...p) === "stop") return;
+ visitStmts(v, file.stmts, ...p);
+}
+
+export function visitStmts<
+ P extends PM = [],
+>(
+ v: Visitor
,
+ stmts: Stmt[],
+ ...p: P
+) {
+ for (const stmt of stmts) {
+ visitStmt(v, stmt, ...p);
+ }
+}
+
+export function visitStmt<
+ P extends PM = [],
+>(
+ v: Visitor
,
+ stmt: Stmt,
+ ...p: P
+) {
+ switch (stmt.kind.tag) {
+ case "error":
+ if (v.visitErrorStmt?.(stmt, ...p) === "stop") return;
+ return;
+ case "mod_block":
+ if (v.visitModBlockStmt?.(stmt, stmt.kind, ...p) === "stop") return;
+ visitStmts(v, stmt.kind.stmts, ...p);
+ return;
+ case "mod_file":
+ if (v.visitModFileStmt?.(stmt, stmt.kind, ...p) === "stop") return;
+ return;
+ }
+ exhausted(stmt.kind);
+}
diff --git a/compiler/ctx.ts b/compiler/ctx.ts
new file mode 100644
index 0000000..5d0267b
--- /dev/null
+++ b/compiler/ctx.ts
@@ -0,0 +1,107 @@
+import * as ast from "./ast/mod.ts";
+import { Diag } from "./diagnostics.ts";
+
+export class Ctx {
+ private fileIds = new Ids();
+ private files = new Map, FileInfo>();
+
+ private diags: Diag[] = [];
+
+ public fileHasChildWithIdent(file: File, childIdent: string): boolean {
+ return this.files.get(id(file))!
+ .subFiles.has(childIdent);
+ }
+
+ public addFile(
+ ident: string,
+ absPath: string,
+ relPath: string,
+ superFile: File | undefined,
+ text: string,
+ ): File {
+ const file = this.fileIds.nextThenStep();
+ this.files.set(id(file), {
+ ident,
+ absPath,
+ relPath,
+ superFile,
+ subFiles: new Map(),
+ text,
+ });
+ if (superFile) {
+ this.files.get(id(superFile))!
+ .subFiles.set(ident, file);
+ }
+ return file;
+ }
+
+ public addFileAst(file: File, ast: ast.File) {
+ this.files.get(id(file))!.ast = ast;
+ }
+
+ public fileInfo(file: File): FileInfo {
+ this.files.get(id(file))!;
+ }
+
+ public reportFatal(file: File, msg: string) {
+ console.error(`fatal: ${msg}`);
+ this.reportImmediately(file, msg);
+ }
+
+ public enableReportImmediately = false;
+ public enableStacktrace = false;
+ private reportImmediately(file: File, msg: string) {
+ if (!this.enableReportImmediately) {
+ return;
+ }
+
+ if (!this.enableStacktrace) {
+ return;
+ }
+ class StackTracer extends Error {
+ constructor() {
+ super("StackTracer");
+ }
+ }
+ try {
+ //throw new ReportNotAnError();
+ } catch (error) {
+ if (!(error instanceof StackTracer)) {
+ throw error;
+ }
+ console.log(
+ error.stack?.replace(
+ "Error: StackTracer",
+ "Stack trace:",
+ ) ??
+ error,
+ );
+ }
+ }
+}
+
+export type File = IdBase;
+
+export type FileInfo = {
+ ident: string;
+ absPath: string;
+ relPath: string;
+ superFile?: File;
+ subFiles: Map;
+ text: string;
+ ast?: ast.File;
+};
+
+export type IdBase = { id: number };
+
+export type Id = IdType["id"];
+export const id = (id: IdType): Id => id.id;
+
+export class Ids {
+ private next = 0;
+ public nextThenStep(): IdType {
+ const id = this.next;
+ this.next += 1;
+ return { id } as IdType;
+ }
+}
diff --git a/compiler/diagnostics.ts b/compiler/diagnostics.ts
new file mode 100644
index 0000000..080c030
--- /dev/null
+++ b/compiler/diagnostics.ts
@@ -0,0 +1,43 @@
+import { Ctx, File } from "./ctx.ts";
+
+export type Span = {
+ begin: Pos;
+ end: Pos;
+};
+
+export type Pos = {
+ idx: number;
+ line: number;
+ col: number;
+};
+
+export type Diag = {
+ severity: "fatal" | "error" | "warning" | "info";
+ origin?: string;
+ msg: string;
+ file?: File;
+ span?: Span;
+ pos?: Pos;
+};
+
+export function prettyPrintDiag(ctx: Ctx, diag: Diag) {
+ const { severity, msg } = diag;
+ const origin = diag.origin ? `${diag.origin}: ` : "";
+ console.error(`${origin}${severity}: ${msg}`);
+ if (diag.file && (diag.span || diag.pos)) {
+ const { absPath: path, text } = ctx.fileInfo(diag.file);
+ const { line, col } = diag.span?.begin ?? diag.pos!;
+ console.error(` at ./${path}:${line}:${col}`);
+ if (diag.span) {
+ let begin = diag.span.begin.idx;
+ while (begin >= 0 && text[begin - 1] != "\n") {
+ begin -= 1;
+ }
+ let end = diag.span.end.idx;
+ while (end < text.length && text[end + 1] != "\n") {
+ end += 1;
+ }
+ } else if (diag.pos) {
+ }
+ }
+}
diff --git a/compiler/main.ts b/compiler/main.ts
index c87de99..c0b7153 100644
--- a/compiler/main.ts
+++ b/compiler/main.ts
@@ -1,14 +1,20 @@
-type Pack = {
+import * as path from "jsr:@std/path";
+import { Parser } from "./parser/parser.ts";
+import * as ast from "./ast/mod.ts";
+import { Ctx } from "./ctx.ts";
+import { File } from "./ctx.ts";
+
+export type Pack = {
rootMod: Mod;
};
-type Mod = null;
+export type Mod = null;
-interface PackEmitter {
+export interface PackEmitter {
emit(pack: Pack): void;
}
-class PackCompiler {
+export class PackCompiler {
public constructor(
private entryFilePath: string,
private emitter: PackEmitter,
@@ -18,5 +24,53 @@ class PackCompiler {
}
}
-class ModResolver {
+type _P = { file: File };
+export class FileTreeCollector implements ast.Visitor<[_P]> {
+ private subFilePromise = Promise.resolve();
+
+ public constructor(
+ private ctx: Ctx,
+ private superFile: File | undefined,
+ private ident: string,
+ private absPath: string,
+ private relPath: string,
+ ) {}
+
+ public async collect(): Promise {
+ const text = await Deno.readTextFile(this.absPath);
+ const file = this.ctx.addFile(
+ this.ident,
+ this.absPath,
+ this.relPath,
+ this.superFile,
+ text,
+ );
+ const fileAst = new Parser(file, text).parse();
+ this.ctx.addFileAst(file, fileAst);
+ ast.visitFile(this, fileAst, { file });
+ await this.subFilePromise;
+ }
+
+ visitModFileStmt(
+ _stmt: ast.Stmt,
+ kind: ast.ModFileStmt,
+ { file }: _P,
+ ): ast.VisitRes {
+ const { ident, filePath: relPath } = kind;
+ const absPath = path.join(path.dirname(this.absPath), relPath);
+ this.subFilePromise = this.subFilePromise
+ .then(() => {
+ if (this.ctx.fileHasChildWithIdent(file, ident)) {
+ }
+ return new FileTreeCollector(
+ this.ctx,
+ file,
+ ident,
+ absPath,
+ relPath,
+ )
+ .collect();
+ });
+ return "stop";
+ }
}
diff --git a/compiler/parser/parser.ts b/compiler/parser/parser.ts
new file mode 100644
index 0000000..15186af
--- /dev/null
+++ b/compiler/parser/parser.ts
@@ -0,0 +1,14 @@
+import { File } from "../ast/ast.ts";
+import { File as CtxFile } from "../ctx.ts";
+import { todo } from "../util.ts";
+
+export class Parser {
+ public constructor(
+ private file: CtxFile,
+ private text: string,
+ ) {}
+
+ public parse(): File {
+ return todo();
+ }
+}
diff --git a/compiler/util.ts b/compiler/util.ts
new file mode 100644
index 0000000..7b8b9bf
--- /dev/null
+++ b/compiler/util.ts
@@ -0,0 +1,9 @@
+export function todo(msg?: string): T {
+ class NotImplemented extends Error {}
+ throw new NotImplemented(msg);
+}
+
+export function exhausted(_: never) {
+ class Unexhausted extends Error {}
+ throw new Unexhausted();
+}