diff --git a/compiler/ast/ast.ts b/compiler/ast/ast.ts index 25cf66d..45a2a11 100644 --- a/compiler/ast/ast.ts +++ b/compiler/ast/ast.ts @@ -1,3 +1,4 @@ +import { IdentId } from "../ctx.ts"; import { Span } from "../diagnostics.ts"; export type File = { @@ -126,7 +127,7 @@ export type ExprKind = | { tag: "null" } | { tag: "int" } & IntExpr | { tag: "bool" } & BoolExpr - | { tag: "string" } & StringExpr + | { tag: "str" } & StringExpr | { tag: "group" } & GroupExpr | { tag: "array" } & ArrayExpr | { tag: "repeat" } & RepeatExpr @@ -221,7 +222,7 @@ export type TyKind = | { tag: "null" } | { tag: "int" } | { tag: "bool" } - | { tag: "string" } + | { tag: "str" } | { tag: "path" } & PathTy | { tag: "ref" } & RefTy | { tag: "ptr" } & PtrTy @@ -255,6 +256,6 @@ export type PathSegment = { }; export type Ident = { - text: string; + id: IdentId; span: Span; }; diff --git a/compiler/ast/visitor.ts b/compiler/ast/visitor.ts index a0b7f11..9237cb0 100644 --- a/compiler/ast/visitor.ts +++ b/compiler/ast/visitor.ts @@ -116,7 +116,7 @@ export interface Visitor< visitNullTy?(ty: Ty, ...p: P): R; visitIntTy?(ty: Ty, ...p: P): R; visitBoolTy?(ty: Ty, ...p: P): R; - visitStringTy?(ty: Ty, ...p: P): R; + visitStrTy?(ty: Ty, ...p: P): R; visitPathTy?(ty: Ty, kind: PathTy, ...p: P): R; visitRefTy?(ty: Ty, kind: RefTy, ...p: P): R; visitPtrTy?(ty: Ty, kind: PtrTy, ...p: P): R; @@ -267,7 +267,7 @@ export function visitExpr< case "int": if (v.visitIntExpr?.(expr, kind, ...p) === "stop") return; return; - case "string": + case "str": if (v.visitStringExpr?.(expr, kind, ...p) === "stop") return; return; case "bool": @@ -419,8 +419,8 @@ export function visitTy< case "bool": if (v.visitBoolTy?.(ty, ...p) === "stop") return; return; - case "string": - if (v.visitStringTy?.(ty, ...p) === "stop") return; + case "str": + if (v.visitStrTy?.(ty, ...p) === "stop") return; return; case "path": if (v.visitPathTy?.(ty, kind, ...p) === "stop") return; diff --git a/compiler/ctx.ts b/compiler/ctx.ts index 68246a1..eabdaac 100644 --- a/compiler/ctx.ts +++ b/compiler/ctx.ts @@ -6,15 +6,16 @@ import { Report, Span, } from "./diagnostics.ts"; +import * as hir from "./middle/hir.ts"; export class Ctx { - private fileIds = new Ids(); - private files = new Map, FileInfo>(); + private fileIds = new Ids(); + private files = new Map, FileInfo>(); private reports: Report[] = []; public fileHasChildWithIdent(file: File, childIdent: string): boolean { - return this.files.get(id(file))! + return this.files.get(idKey(file))! .subFiles.has(childIdent); } @@ -26,7 +27,7 @@ export class Ctx { text: string, ): File { const file = this.fileIds.nextThenStep(); - this.files.set(id(file), { + this.files.set(idKey(file), { ident, absPath, relPath, @@ -35,18 +36,54 @@ export class Ctx { text, }); if (superFile) { - this.files.get(id(superFile))! + this.files.get(idKey(superFile))! .subFiles.set(ident, file); } return file; } public addFileAst(file: File, ast: ast.File) { - this.files.get(id(file))!.ast = ast; + this.files.get(idKey(file))!.ast = ast; } public fileInfo(file: File): FileInfo { - return this.files.get(id(file))!; + return this.files.get(idKey(file))!; + } + + public entryFile(): File { + return keyId(0); + } + + public iterFiles(): Iterator { + return this.files.keys() + .map((key) => keyId(key)); + } + + private identIds = new Ids(); + private identStringToId = new Map(); + private identIdToString = new Map, string>(); + + public internIdent(ident: string): IdentId { + if (this.identStringToId.has(ident)) { + return this.identStringToId.get(ident)!; + } + const id = this.identIds.nextThenStep(); + this.identStringToId.set(ident, id); + this.identIdToString.set(idKey(id), ident); + return id; + } + + public identText(ident: IdentId): string { + return this.identIdToString.get(idKey(ident))!; + } + + private stmtIds = new Ids(); + private stmts = new Map, hir.Stmt>(); + + public internStmt(kind: hir.StmtKind, span: Span): hir.StmtId { + const id = this.stmtIds.nextThenStep(); + this.stmts.set(idKey(id), { kind, span }); + return id; } public filePosLineText(file: File, pos: Pos): string { @@ -93,7 +130,7 @@ export class Ctx { } } -export type File = IdBase; +export type File = IdBase & { readonly _: unique symbol }; export type FileInfo = { ident: string; @@ -105,16 +142,22 @@ export type FileInfo = { ast?: ast.File; }; -export type IdBase = { id: number }; +export type IdentId = IdBase & { readonly _: unique symbol }; +export type DefId = IdBase & { readonly _: unique symbol }; -export type Id = IdType["id"]; -export const id = (id: IdType): Id => id.id; +export type IdBase = { key: number }; + +export type Key = IdType["key"]; +export const idKey = (id: IdType): Key => id.key; +export const keyId = ( + key: Key, +): IdType => ({ key } as IdType); export class Ids { private next = 0; public nextThenStep(): IdType { - const id = this.next; + const key = this.next; this.next += 1; - return { id } as IdType; + return { key } as IdType; } } diff --git a/compiler/main.ts b/compiler/main.ts index c0e0c2a..ef1121b 100644 --- a/compiler/main.ts +++ b/compiler/main.ts @@ -92,7 +92,7 @@ export class FileTreeAstCollector implements ast.Visitor<[_P]> { kind: ast.ModFileItem, { file }: _P, ): ast.VisitRes { - const { ident: { text: ident } } = item; + const ident = this.ctx.identText(item.ident.id); const { filePath: relPath } = kind; const absPath = path.join(path.dirname(this.absPath), relPath); this.subFilePromise = this.subFilePromise diff --git a/compiler/middle/hir.ts b/compiler/middle/hir.ts new file mode 100644 index 0000000..1127d1e --- /dev/null +++ b/compiler/middle/hir.ts @@ -0,0 +1,278 @@ +import { Span } from "../diagnostics.ts"; + +export type StmtId = { key: number; readonly unique: unique symbol }; + +export type Stmt = { + kind: StmtKind; + span: Span; +}; + +export type StmtKind = + | { tag: "error" } + | { tag: "item" } & ItemStmt + | { tag: "let" } & LetStmt + | { tag: "return" } & ReturnStmt + | { tag: "break" } & BreakStmt + | { tag: "continue" } + | { tag: "assign" } & AssignStmt + | { tag: "expr" } & ExprStmt; + +export type ItemStmt = { item: ItemId }; + +export type LetStmt = { + pat: PatId; + ty?: TyId; + expr?: ExprId; +}; + +export type ReturnStmt = { expr?: ExprId }; +export type BreakStmt = { expr?: ExprId }; + +export type AssignStmt = { + assignType: AssignType; + subject: ExprId; + value: ExprId; +}; + +export type AssignType = "=" | "+=" | "-="; + +export type ExprStmt = { expr: ExprId }; + +export type ItemId = { key: number; readonly unique: unique symbol }; + +export type Item = { + kind: ItemKind; + span: Span; + ident: Ident; + pub: boolean; +}; + +export type ItemKind = + | { tag: "error" } + | { tag: "mod_block" } & ModBlockItem + | { tag: "mod_file" } & ModFileItem + | { tag: "enum" } & EnumItem + | { tag: "struct" } & StructItem + | { tag: "fn" } & FnItem + | { tag: "use" } & UseItem + | { tag: "type_alias" } & TypeAliasItem; + +export type ModBlockItem = { block: BlockId }; +export type ModFileItem = { filePath: string }; +export type EnumItem = { variants: Variant[] }; +export type StructItem = { data: VariantData }; + +export type FnItem = { + generics?: Generics; + params: Param[]; + returnTy?: TyId; + body: BlockId; +}; + +export type UseItem = { _: 0 }; +export type TypeAliasItem = { ty: TyId }; + +export type Variant = { + ident: Ident; + data: VariantData; + pub: boolean; + span: Span; +}; + +export type VariantData = { + kind: VariantDataKind; + span: Span; +}; + +export type VariantDataKind = + | { tag: "error" } + | { tag: "unit" } + | { tag: "tuple" } & TupleVariantData + | { tag: "struct" } & StructVariantData; + +export type TupleVariantData = { elems: VariantData[] }; +export type StructVariantData = { fields: FieldDef[] }; + +export type FieldDef = { + ident: Ident; + ty: TyId; + pub: boolean; + span: Span; +}; + +export type Param = { + pat: PatId; + ty: TyId; + span: Span; +}; + +export type Generics = { + params: GenericParam[]; +}; + +export type GenericParam = { + ident: Ident; + span: Span; +}; + +export type ExprId = { key: number; readonly unique: unique symbol }; + +export type Expr = { + kind: ExprKind; + span: Span; +}; + +export type ExprKind = + | { tag: "error" } + | { tag: "path" } & PathExpr + | { tag: "null" } + | { tag: "int" } & IntExpr + | { tag: "bool" } & BoolExpr + | { tag: "str" } & StringExpr + | { tag: "group" } & GroupExpr + | { tag: "array" } & ArrayExpr + | { tag: "repeat" } & RepeatExpr + | { tag: "struct" } & StructExpr + | { tag: "ref" } & RefExpr + | { tag: "deref" } & DerefExpr + | { tag: "elem" } & ElemExpr + | { tag: "field" } & FieldExpr + | { tag: "index" } & IndexExpr + | { tag: "call" } & CallExpr + | { tag: "unary" } & UnaryExpr + | { tag: "binary" } & BinaryExpr + | { tag: "block" } & BlockExpr + | { tag: "if" } & IfExpr + | { tag: "loop" } & LoopExpr + | { tag: "while" } & WhileExpr + | { tag: "for" } & ForExpr + | { tag: "c_for" } & CForExpr; + +export type PathExpr = { path: Path }; +export type IntExpr = { value: number }; +export type BoolExpr = { value: boolean }; +export type StringExpr = { value: string }; +export type GroupExpr = { expr: ExprId }; +export type ArrayExpr = { exprs: ExprId[] }; +export type RepeatExpr = { expr: ExprId; length: ExprId }; +export type StructExpr = { path?: Path; fields: ExprField[] }; +export type RefExpr = { expr: ExprId; refType: RefType; mut: boolean }; +export type DerefExpr = { expr: ExprId }; +export type ElemExpr = { expr: ExprId; elem: number }; +export type FieldExpr = { expr: ExprId; ident: Ident }; +export type IndexExpr = { expr: ExprId; index: ExprId }; +export type CallExpr = { expr: ExprId; args: ExprId[] }; +export type UnaryExpr = { unaryType: UnaryType; expr: ExprId }; +export type BinaryExpr = { + binaryType: BinaryType; + left: ExprId; + right: ExprId; +}; +export type BlockExpr = { block: BlockId }; +export type IfExpr = { cond: ExprId; truthy: BlockId; falsy?: ExprId }; +export type LoopExpr = { body: BlockId }; +export type WhileExpr = { cond: ExprId; body: BlockId }; +export type ForExpr = { pat: PatId; expr: ExprId; body: BlockId }; +export type CForExpr = { + decl?: StmtId; + cond?: ExprId; + incr?: StmtId; + body: BlockId; +}; + +export type RefType = "ref" | "ptr"; +export type UnaryType = "not" | "-"; +export type BinaryType = + | "+" + | "*" + | "==" + | "-" + | "/" + | "!=" + | "<" + | ">" + | "<=" + | ">=" + | "or" + | "and"; + +export type ExprField = { + ident: Ident; + expr: ExprId; + span: Span; +}; + +export type BlockId = { key: number; readonly unique: unique symbol }; + +export type Block = { + stmts: StmtId[]; + expr?: ExprId; + span: Span; +}; + +export type PatId = { key: number; readonly unique: unique symbol }; + +export type Pat = { + kind: PatKind; + span: Span; +}; + +export type PatKind = + | { tag: "error" } + | { tag: "bind" } & BindPat; + +export type BindPat = { + ident: Ident; + mut: boolean; +}; + +export type TyId = { key: number; readonly unique: unique symbol }; + +export type Ty = { + kind: TyKind; + span: Span; +}; + +export type TyKind = + | { tag: "error" } + | { tag: "null" } + | { tag: "int" } + | { tag: "bool" } + | { tag: "str" } + | { tag: "path" } & PathTy + | { tag: "ref" } & RefTy + | { tag: "ptr" } & PtrTy + | { tag: "slice" } & SliceTy + | { tag: "array" } & ArrayTy + | { tag: "anon_struct" } & AnonStructTy; + +export type PathTy = { path: Path }; +export type RefTy = { ty: TyId; mut: boolean }; +export type PtrTy = { ty: TyId; mut: boolean }; +export type SliceTy = { ty: TyId }; +export type ArrayTy = { ty: TyId; length: ExprId }; +export type TupleTy = { elems: TyId[] }; +export type AnonStructTy = { fields: AnonFieldDef[] }; + +export type AnonFieldDef = { + ident: Ident; + ty: TyId; + span: Span; +}; + +export type Path = { + segments: PathSegment[]; + span: Span; +}; + +export type PathSegment = { + ident: Ident; + genericArgs?: TyId[]; + span: Span; +}; + +export type Ident = { + internId: number; + span: Span; +}; + diff --git a/compiler/middle/lower_ast.ts b/compiler/middle/lower_ast.ts new file mode 100644 index 0000000..a87cfb9 --- /dev/null +++ b/compiler/middle/lower_ast.ts @@ -0,0 +1,125 @@ +import { Ctx, DefId, IdentId } from "../ctx.ts"; +import * as ast from "../ast/ast.ts"; +import { ExprId, Ident, ItemId, PatId, StmtId, TyId } from "./hir.ts"; +import { exhausted, Res as Result, todo } from "../util.ts"; + +export class AstLowerer { + private ribs: Rib[] = []; + + public constructor( + private ctx: Ctx, + ) {} + + public lower() { + const file = this.ctx.entryFile(); + const ast = this.ctx.fileInfo(file).ast!; + this.lowerFile(ast); + } + + private lowerFile(file: ast.File) { + this.lowerStmts(file.stmts); + } + + private lowerStmts(stmts: ast.Stmt[]): StmtId[] { + return stmts.map((stmt) => this.lowerStmt(stmt)); + } + + private lowerStmt(stmt: ast.Stmt): StmtId { + const kind = stmt.kind; + switch (kind.tag) { + case "error": + return this.ctx.internStmt(kind, stmt.span); + case "item": + return this.ctx.internStmt({ + tag: "item", + item: this.lowerItem(kind.item), + }, stmt.span); + case "let": + return this.lowerLetStmt(stmt, kind); + case "return": + return this.ctx.internStmt({ + tag: "return", + expr: kind.expr && this.lowerExpr(kind.expr), + }, stmt.span); + case "break": + return this.ctx.internStmt({ + tag: "break", + expr: kind.expr && this.lowerExpr(kind.expr), + }, stmt.span); + case "continue": + return this.ctx.internStmt({ + tag: "continue", + }, stmt.span); + case "assign": + return this.ctx.internStmt({ + tag: "assign", + assignType: kind.assignType, + subject: this.lowerExpr(kind.subject), + value: this.lowerExpr(kind.value), + }, stmt.span); + case "expr": + return this.ctx.internStmt({ + tag: "expr", + expr: this.lowerExpr(kind.expr), + }, stmt.span); + } + exhausted(kind); + } + + private lowerLetStmt(stmt: ast.Stmt, kind: ast.LetStmt): StmtId { + return this.ctx.internStmt({ + tag: "let", + pat: this.lowerPat(kind.pat), + ty: kind.ty && this.lowerTy(kind.ty), + expr: kind.expr && this.lowerExpr(kind.expr), + }, stmt.span); + } + + private lowerItem(item: ast.Item): ItemId { + return todo(); + } + + private lowerPat(pat: ast.Pat): PatId { + return todo(); + } + + private lowerTy(ty: ast.Ty): TyId { + return todo(); + } + + private lowerExpr(expr: ast.Expr): ExprId { + return todo(); + } +} + +// https://doc.rust-lang.org/nightly/nightly-rustc/rustc_resolve/late/struct.Rib.html +type Rib = { + kind: RibKind; + bindings: Map; +}; + +// https://doc.rust-lang.org/nightly/nightly-rustc/rustc_resolve/late/enum.RibKind.html +type RibKind = + | { tag: "normal" } + | { tag: "fn" } + | { tag: "item"; defKind: DefKind } + | { tag: "mod" }; + +// https://doc.rust-lang.org/nightly/nightly-rustc/rustc_resolve/late/enum.RibKind.html +type Res = + | { tag: "error" } + | { tag: "def"; kind: DefKind; id: DefId } + | { tag: "local"; id: DefId }; + +// https://doc.rust-lang.org/nightly/nightly-rustc/rustc_hir/def/enum.DefKind.html +type DefKind = + | { type: "mod" } + | { type: "struct" } + | { type: "enum" } + | { type: "variant" } + | { type: "ty_alias" } + | { type: "ty_param" } + | { type: "fn" } + | { type: "ctor" } + | { type: "use" } + | { type: "field" }; diff --git a/compiler/old/lexer.ts b/compiler/old/lexer.ts index f5dd6fd..9203284 100644 --- a/compiler/old/lexer.ts +++ b/compiler/old/lexer.ts @@ -56,7 +56,7 @@ export class Lexer { if (keywords.includes(value)) { return this.token(value, pos); } else { - return { ...this.token("ident", pos), identValue: value }; + return { ...this.token("ident", pos), identId: value }; } } if (this.test(/[1-9]/)) { diff --git a/compiler/old/parser.ts b/compiler/old/parser.ts index 4c02eb4..89d7def 100644 --- a/compiler/old/parser.ts +++ b/compiler/old/parser.ts @@ -199,7 +199,7 @@ export class Parser { this.report("expected 'ident'"); return this.stmt({ type: "error" }, spos); } - const ident = this.current().identValue!; + const ident = this.current().identId!; this.step(); const args: Expr[] = []; if (this.test("(")) { @@ -250,7 +250,7 @@ export class Parser { this.report("expected 'ident'"); return this.stmt({ type: "error" }, pos); } - const ident = this.current().identValue!; + const ident = this.current().identId!; this.step(); if (this.test(";")) { this.eatSemicolon(); @@ -290,7 +290,7 @@ export class Parser { this.report("expected ident"); return this.stmt({ type: "error" }, pos); } - const ident = this.current().identValue!; + const ident = this.current().identId!; this.step(); let genericParams: GenericParam[] | undefined; if (this.test("<")) { @@ -333,7 +333,7 @@ export class Parser { private parseETypeParam(index: number): Res { const pos = this.pos(); if (this.test("ident")) { - const ident = this.current().identValue!; + const ident = this.current().identId!; this.step(); return { ok: true, @@ -394,7 +394,7 @@ export class Parser { mut = true; this.step(); } - const ident = this.current().identValue!; + const ident = this.current().identId!; this.step(); if (this.test(":")) { this.step(); @@ -637,7 +637,7 @@ export class Parser { this.report("expected 'ident'"); return { ok: false, pos }; } - const ident = this.current().identValue!; + const ident = this.current().identId!; this.step(); if (!this.test(":")) { this.report("expected ':'"); @@ -856,7 +856,7 @@ export class Parser { this.report("expected ident"); return this.expr({ type: "error" }, pos); } - const ident = this.current().identValue!; + const ident = this.current().identId!; this.step(); return this.expr({ type: "field", subject, ident }, pos); } @@ -890,7 +890,7 @@ export class Parser { this.report("expected ident"); return this.expr({ type: "error" }, pos); } - const ident = this.current().identValue!; + const ident = this.current().identId!; this.step(); return this.expr({ type: "path", subject, ident }, pos); } @@ -906,7 +906,7 @@ export class Parser { private parseOperand(): Expr { const pos = this.pos(); if (this.test("ident")) { - const ident = this.current().identValue!; + const ident = this.current().identId!; this.step(); return this.expr({ type: "ident", ident }, pos); } @@ -975,7 +975,7 @@ export class Parser { return this.etype({ type }, pos); } if (this.test("ident")) { - const ident = this.current().identValue!; + const ident = this.current().identId!; this.step(); return this.etype({ type: "ident", ident: ident }, pos); } diff --git a/compiler/parser/lexer.ts b/compiler/parser/lexer.ts index 7b6325c..3c7e390 100644 --- a/compiler/parser/lexer.ts +++ b/compiler/parser/lexer.ts @@ -37,7 +37,7 @@ export class Lexer implements TokenIter { ? this.token(val, span) : this.token("ident", span, { type: "ident", - identValue: val, + identId: this.ctx.internIdent(val), }); }, /[a-zA-Z_]/, @@ -127,8 +127,8 @@ export class Lexer implements TokenIter { return this.token("error", { begin, end }); } this.step(); - return this.token("string", { begin, end }, { - type: "string", + return this.token("str", { begin, end }, { + type: "str", stringValue: value, }); } @@ -275,7 +275,7 @@ const keywords = new Set([ "null", "int", "bool", - "string", + "str", "return", "break", "continue", diff --git a/compiler/parser/parser.ts b/compiler/parser/parser.ts index e188ab9..7895177 100644 --- a/compiler/parser/parser.ts +++ b/compiler/parser/parser.ts @@ -280,7 +280,7 @@ export class Parser { return this.stmt({ tag: "error" }, pos); } const ident = this.parseIdent(); - if (this.test("string")) { + if (this.test("str")) { const filePath = this.current().stringValue!; this.step(); this.eatSemicolon(); @@ -965,10 +965,10 @@ export class Parser { this.step(); return this.expr({ tag: "int", value }, pos); } - if (this.test("string")) { + if (this.test("str")) { const value = this.current().stringValue!; this.step(); - return this.expr({ tag: "string", value }, pos); + return this.expr({ tag: "str", value }, pos); } if (this.test("false")) { this.step(); @@ -1054,12 +1054,12 @@ export class Parser { private parseTy(): Ty { const pos = this.span(); - if (["null", "int", "bool", "string"].includes(this.current().type)) { + if (["null", "int", "bool", "str"].includes(this.current().type)) { const tag = this.current().type as | "null" | "int" | "bool" - | "string"; + | "str"; this.step(); return this.ty({ tag }, pos); } @@ -1225,7 +1225,7 @@ export class Parser { private parseIdent(): Ident { const tok = this.current(); this.step(); - return { text: tok.identValue!, span: tok.span }; + return { id: tok.identId!, span: tok.span }; } private step() { diff --git a/compiler/parser/token.ts b/compiler/parser/token.ts index 7f9a82e..f7a26a5 100644 --- a/compiler/parser/token.ts +++ b/compiler/parser/token.ts @@ -1,10 +1,11 @@ +import { IdentId } from "../ctx.ts"; import { Span } from "../diagnostics.ts"; export type Token = { type: string; span: Span; length: number; - identValue?: string; + identId?: IdentId; intValue?: number; stringValue?: string; }; diff --git a/compiler/program.slg b/compiler/program.slg new file mode 100644 index 0000000..ccad4ac --- /dev/null +++ b/compiler/program.slg @@ -0,0 +1,5 @@ + +fn main() -> int { + let a = 5; +} +