diff --git a/compiler/ast/ast.ts b/compiler/ast/ast.ts index e78bddc..25cf66d 100644 --- a/compiler/ast/ast.ts +++ b/compiler/ast/ast.ts @@ -23,7 +23,7 @@ export type ItemStmt = { item: Item }; export type LetStmt = { pat: Pat; - ty: Ty; + ty?: Ty; expr?: Expr; }; @@ -57,7 +57,7 @@ export type ItemKind = | { tag: "use" } & UseItem | { tag: "type_alias" } & TypeAliasItem; -export type ModBlockItem = { stmts: Stmt[] }; +export type ModBlockItem = { block: Block }; export type ModFileItem = { filePath: string }; export type EnumItem = { variants: Variant[] }; export type StructItem = { data: VariantData }; @@ -122,7 +122,7 @@ export type Expr = { export type ExprKind = | { tag: "error" } - | { tag: "path" } & Path + | { tag: "path" } & PathExpr | { tag: "null" } | { tag: "int" } & IntExpr | { tag: "bool" } & BoolExpr @@ -139,13 +139,14 @@ export type ExprKind = | { tag: "call" } & CallExpr | { tag: "unary" } & UnaryExpr | { tag: "binary" } & BinaryExpr - | { tag: "block" } & Block + | { 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 }; @@ -161,6 +162,7 @@ export type IndexExpr = { expr: Expr; index: Expr }; export type CallExpr = { expr: Expr; args: Expr[] }; export type UnaryExpr = { unaryType: UnaryType; expr: Expr }; export type BinaryExpr = { binaryType: BinaryType; left: Expr; right: Expr }; +export type BlockExpr = { block: Block }; export type IfExpr = { cond: Expr; truthy: Block; falsy?: Expr }; export type LoopExpr = { body: Block }; export type WhileExpr = { cond: Expr; body: Block }; @@ -216,13 +218,18 @@ export type Ty = { export type TyKind = | { tag: "error" } - | { tag: "path" } & Path + | { tag: "null" } + | { tag: "int" } + | { tag: "bool" } + | { tag: "string" } + | { 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: Ty; mut: boolean }; export type PtrTy = { ty: Ty; mut: boolean }; export type SliceTy = { ty: Ty }; diff --git a/compiler/ast/visitor.ts b/compiler/ast/visitor.ts index ba63080..a0b7f11 100644 --- a/compiler/ast/visitor.ts +++ b/compiler/ast/visitor.ts @@ -1,5 +1,5 @@ import { exhausted } from "../util.ts"; -import { Block } from "./ast.ts"; +import { Block, BlockExpr, PathExpr, PathTy } from "./ast.ts"; import { AnonStructTy, ArrayExpr, @@ -83,7 +83,7 @@ export interface Visitor< visitExpr?(expr: Expr, ...p: P): R; visitErrorExpr?(expr: Expr, ...p: P): R; - visitPathExpr?(expr: Expr, kind: Path, ...p: P): R; + visitPathExpr?(expr: Expr, kind: PathExpr, ...p: P): R; visitNullExpr?(expr: Expr, ...p: P): R; visitIntExpr?(expr: Expr, kind: IntExpr, ...p: P): R; visitBoolExpr?(expr: Expr, kind: BoolExpr, ...p: P): R; @@ -100,7 +100,7 @@ export interface Visitor< visitCallExpr?(expr: Expr, kind: CallExpr, ...p: P): R; visitUnaryExpr?(expr: Expr, kind: UnaryExpr, ...p: P): R; visitBinaryExpr?(expr: Expr, kind: BinaryExpr, ...p: P): R; - visitBlockExpr?(expr: Expr, kind: Block, ...p: P): R; + visitBlockExpr?(expr: Expr, kind: BlockExpr, ...p: P): R; visitIfExpr?(expr: Expr, kind: IfExpr, ...p: P): R; visitLoopExpr?(expr: Expr, kind: LoopExpr, ...p: P): R; visitWhileExpr?(expr: Expr, kind: WhileExpr, ...p: P): R; @@ -113,7 +113,11 @@ export interface Visitor< visitTy?(ty: Ty, ...p: P): R; visitErrorTy?(ty: Ty, ...p: P): R; - visitPathTy?(ty: Ty, kind: Path, ...p: P): R; + visitNullTy?(ty: Ty, ...p: P): R; + visitIntTy?(ty: Ty, ...p: P): R; + visitBoolTy?(ty: Ty, ...p: P): R; + visitStringTy?(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; visitSliceTy?(ty: Ty, kind: SliceTy, ...p: P): R; @@ -163,24 +167,41 @@ export function visitStmt< return; case "item": if (v.visitItemStmt?.(stmt, kind, ...p) === "stop") return; + visitItem(v, kind.item, ...p); return; case "let": if (v.visitLetStmt?.(stmt, kind, ...p) === "stop") return; + visitPat(v, kind.pat, ...p); + if (kind.ty) { + visitTy(v, kind.ty, ...p); + } + if (kind.expr) { + visitExpr(v, kind.expr, ...p); + } return; case "return": if (v.visitReturnStmt?.(stmt, kind, ...p) === "stop") return; + if (kind.expr) { + visitExpr(v, kind.expr, ...p); + } return; case "break": if (v.visitBreakStmt?.(stmt, kind, ...p) === "stop") return; + if (kind.expr) { + visitExpr(v, kind.expr, ...p); + } return; case "continue": if (v.visitContinueStmt?.(stmt, ...p) === "stop") return; return; case "assign": if (v.visitAssignStmt?.(stmt, kind, ...p) === "stop") return; + visitExpr(v, kind.subject, ...p); + visitExpr(v, kind.value, ...p); return; case "expr": if (v.visitExprStmt?.(stmt, kind, ...p) === "stop") return; + visitExpr(v, kind.expr, ...p); return; } exhausted(kind); @@ -193,6 +214,7 @@ export function visitItem< item: Item, ...p: P ) { + visitIdent(v, item.ident, ...p); const kind = item.kind; switch (kind.tag) { case "error": @@ -237,7 +259,7 @@ export function visitExpr< return; case "path": if (v.visitPathExpr?.(expr, kind, ...p) === "stop") return; - visitPath(v, kind, ...p); + visitPath(v, kind.path, ...p); return; case "null": if (v.visitNullExpr?.(expr, ...p) === "stop") return; @@ -271,7 +293,7 @@ export function visitExpr< if (kind.path) { visitPath(v, kind.path, ...p); } - for (const field of kind.field) { + for (const field of kind.fields) { visitIdent(v, field.ident, ...p); visitExpr(v, field.expr, ...p); } @@ -316,14 +338,14 @@ export function visitExpr< return; case "block": if (v.visitBlockExpr?.(expr, kind, ...p) === "stop") return; - visitBlock(v, kind, ...p); + visitBlock(v, kind.block, ...p); return; case "if": if (v.visitIfExpr?.(expr, kind, ...p) === "stop") return; visitExpr(v, kind.cond, ...p); visitBlock(v, kind.truthy, ...p); if (kind.falsy) { - visitBlock(v, kind.falsy, ...p); + visitExpr(v, kind.falsy, ...p); } return; case "loop": @@ -388,9 +410,21 @@ export function visitTy< case "error": if (v.visitErrorTy?.(ty, ...p) === "stop") return; return; + case "null": + if (v.visitNullTy?.(ty, ...p) === "stop") return; + return; + case "int": + if (v.visitIntTy?.(ty, ...p) === "stop") return; + return; + case "bool": + if (v.visitBoolTy?.(ty, ...p) === "stop") return; + return; + case "string": + if (v.visitStringTy?.(ty, ...p) === "stop") return; + return; case "path": if (v.visitPathTy?.(ty, kind, ...p) === "stop") return; - v.visitPath?.(kind, ...p); + v.visitPath?.(kind.path, ...p); return; case "ref": if (v.visitRefTy?.(ty, kind, ...p) === "stop") return; diff --git a/compiler/ctx.ts b/compiler/ctx.ts index 74d9311..68246a1 100644 --- a/compiler/ctx.ts +++ b/compiler/ctx.ts @@ -1,5 +1,11 @@ import * as ast from "./ast/mod.ts"; -import { Pos, prettyPrintReport, printStackTrace, Report, Span } from "./diagnostics.ts"; +import { + Pos, + prettyPrintReport, + printStackTrace, + Report, + Span, +} from "./diagnostics.ts"; export class Ctx { private fileIds = new Ids(); @@ -44,23 +50,23 @@ export class Ctx { } public filePosLineText(file: File, pos: Pos): string { - const fileTextLines = this.fileInfo(file).text.split("\n") - return fileTextLines[pos.line-1] + const fileTextLines = this.fileInfo(file).text.split("\n"); + return fileTextLines[pos.line - 1]; } public fileSpanText(file: File, span: Span): string { - let result = "" - const fileTextLines = this.fileInfo(file).text.split("\n") + let result = ""; + const fileTextLines = this.fileInfo(file).text.split("\n"); - for(let i = 0; i < fileTextLines.length; i++) { - if (i > span.end.line-1) { + for (let i = 0; i < fileTextLines.length; i++) { + if (i > span.end.line - 1) { break; } - if (i >= span.begin.line-1) { + if (i >= span.begin.line - 1) { result += fileTextLines[i] + "\n"; } } - return result + return result; } public report(rep: Report) { @@ -78,6 +84,13 @@ export class Ctx { } } } + + public printAsts() { + for (const [_, info] of this.files) { + console.log(`${info.absPath}:`); + console.log(JSON.stringify(info.ast!, null, 2)); + } + } } export type File = IdBase; diff --git a/compiler/main.ts b/compiler/main.ts index 46b48a8..c0e0c2a 100644 --- a/compiler/main.ts +++ b/compiler/main.ts @@ -4,6 +4,13 @@ import * as ast from "./ast/mod.ts"; import { Ctx } from "./ctx.ts"; import { File } from "./ctx.ts"; +async function main() { + const filePath = Deno.args[0]; + const compiler = new PackCompiler(filePath, new NullEmitter()); + compiler.enableDebug(); + await compiler.compile(); +} + export type Pack = { rootMod: Mod; }; @@ -14,6 +21,11 @@ export interface PackEmitter { emit(pack: Pack): void; } +export class NullEmitter implements PackEmitter { + emit(pack: Pack): void { + } +} + export class PackCompiler { private ctx = new Ctx(); @@ -22,10 +34,16 @@ export class PackCompiler { private emitter: PackEmitter, ) {} - public compile() { - FileTreeAstCollector + public async compile() { + await FileTreeAstCollector .fromEntryFile(this.ctx, this.entryFilePath) .collect(); + this.ctx.printAsts(); + } + + public enableDebug() { + this.ctx.enableReportImmediately = true; + this.ctx.enableStacktrace = true; } } @@ -74,7 +92,8 @@ export class FileTreeAstCollector implements ast.Visitor<[_P]> { kind: ast.ModFileItem, { file }: _P, ): ast.VisitRes { - const { ident: { text: ident }, filePath: relPath } = kind; + const { ident: { text: ident } } = item; + const { filePath: relPath } = kind; const absPath = path.join(path.dirname(this.absPath), relPath); this.subFilePromise = this.subFilePromise .then(() => { @@ -99,3 +118,5 @@ export class FileTreeAstCollector implements ast.Visitor<[_P]> { return "stop"; } } + +main(); diff --git a/compiler/parser/parser.ts b/compiler/parser/parser.ts index 5b67df6..e188ab9 100644 --- a/compiler/parser/parser.ts +++ b/compiler/parser/parser.ts @@ -1,4 +1,5 @@ import { + AnonFieldDef, BinaryType, ExprField, PathSegment, @@ -28,6 +29,8 @@ import { Ctx, File as CtxFile } from "../ctx.ts"; import { Pos, Span } from "../diagnostics.ts"; import { Res, todo } from "../util.ts"; import { Lexer } from "./lexer.ts"; +import { TokenIter } from "./token.ts"; +import { SigFilter } from "./token.ts"; import { Token } from "./token.ts"; type ParseRes = Res; @@ -38,14 +41,14 @@ type StmtDetails = { }; export class Parser { - private lexer: Lexer; + private lexer: TokenIter; private currentToken: Token | null; public constructor( private ctx: Ctx, private file: CtxFile, ) { - this.lexer = new Lexer(this.ctx, this.file); + this.lexer = new SigFilter(new Lexer(this.ctx, this.file)); this.currentToken = this.lexer.next(); } @@ -203,7 +206,7 @@ export class Parser { private parseBlockExpr(): Expr { const block = this.parseBlock(); return block.ok - ? this.expr({ tag: "block", ...block.val }, this.span()) + ? this.expr({ tag: "block", block: block.val }, this.span()) : this.expr({ tag: "error" }, this.span()); } @@ -312,7 +315,13 @@ export class Parser { return this.stmt({ tag: "item", item: this.item( - { tag: "mod_block", stmts }, + { + tag: "mod_block", + block: { + stmts, + span: Span.fromto(pos, stmts.at(-1)?.span ?? pos), + }, + }, pos, ident, details.pub, @@ -443,18 +452,15 @@ export class Parser { }); } - private parsePat(): Pat { - return todo(); - } - private parseLet(): Stmt { const pos = this.span(); this.step(); - const paramResult = this.parseParam(); - if (!paramResult.ok) { - return this.stmt({ tag: "error" }, pos); + const pat = this.parsePat(); + let ty: Ty | undefined = undefined; + if (this.test(":")) { + this.step(); + ty = this.parseTy(); } - const { pat, ty } = paramResult.val; if (!this.test("=")) { this.report("expected '='"); return this.stmt({ tag: "error" }, pos); @@ -936,9 +942,23 @@ export class Parser { private parseOperand(): Expr { const pos = this.span(); if (this.test("ident")) { - const ident = this.current().identValue!; - this.step(); - return this.expr({ tag: "ident", ident }, pos); + const pathRes = this.parsePath(); + if (!pathRes.ok) { + return this.expr({ tag: "error" }, pos); + } + if (this.test("{")) { + this.step(); + const fields = this.parseDelimitedList( + this.parseExprField, + "}", + ",", + ); + return this.expr( + { tag: "struct", path: pathRes.val, fields }, + pathRes.val.span, + ); + } + return this.expr({ tag: "path", path: pathRes.val }, pos); } if (this.test("int")) { const value = this.current().intValue!; @@ -993,6 +1013,45 @@ export class Parser { return this.expr({ tag: "error" }, pos); } + private parseExprField(): ParseRes { + if (!this.test("ident")) { + this.report("expected 'ident'"); + return Res.Err(undefined); + } + const ident = this.parseIdent(); + if (!this.test(":")) { + this.report("expected ':'"); + return Res.Err(undefined); + } + this.step(); + const expr = this.parseExpr(); + return Res.Ok({ + ident, + expr, + span: Span.fromto(ident.span, expr.span), + }); + } + + private parsePat(): Pat { + const pos = this.span(); + if (this.test("ident")) { + const ident = this.parseIdent(); + return this.pat({ tag: "bind", ident, mut: false }, ident.span); + } + if (this.test("mut")) { + this.step(); + if (!this.test("ident")) { + this.report("expected 'ident'"); + return this.pat({ tag: "error" }, pos); + } + const ident = this.parseIdent(); + return this.pat({ tag: "bind", ident, mut: false }, pos); + } + this.report(`expected pattern, got '${this.current().type}'`, pos); + this.step(); + return this.pat({ tag: "error" }, pos); + } + private parseTy(): Ty { const pos = this.span(); if (["null", "int", "bool", "string"].includes(this.current().type)) { @@ -1005,19 +1064,31 @@ export class Parser { return this.ty({ tag }, pos); } if (this.test("ident")) { - const ident = this.current().identValue!; - this.step(); - return this.ty({ tag: "ident", ident: ident }, pos); + const pathRes = this.parsePath(); + if (!pathRes.ok) { + return this.ty({ tag: "error" }, pos); + } + return this.ty({ tag: "path", path: pathRes.val }, pos); } if (this.test("[")) { this.step(); - const subject = this.parseTy(); + const ty = this.parseTy(); + if (this.test(";")) { + this.step(); + const length = this.parseExpr(); + if (!this.test("]")) { + this.report("expected ']'", pos); + return this.ty({ tag: "error" }, pos); + } + this.step(); + return this.ty({ tag: "array", ty, length }, pos); + } if (!this.test("]")) { - this.report("expected ']'", pos); + this.report("expected ']' or ';'", pos); return this.ty({ tag: "error" }, pos); } this.step(); - return this.ty({ tag: "array", subject }, pos); + return this.ty({ tag: "slice", ty }, pos); } if (this.test("struct")) { this.step(); @@ -1025,55 +1096,55 @@ export class Parser { this.report("expected '{'"); return this.ty({ tag: "error" }, pos); } - const fields = this.parseTyStructFields(); - return this.ty({ tag: "struct", fields }, pos); + const fields = this.parseAnonFieldDefs(); + return this.ty({ tag: "anon_struct", fields }, pos); } if (this.test("&")) { this.step(); - let tag: "ref" | "ref_mut" = "ref"; + let mut = false; if (this.test("mut")) { this.step(); - tag = "ref_mut"; + mut = true; } - const subject = this.parseTy(); - return this.ty({ type, subject }, pos); + const ty = this.parseTy(); + return this.ty({ tag: "ref", ty, mut }, pos); } if (this.test("*")) { this.step(); - let tag: "ptr" | "ptr_mut" = "ptr"; + let mut = false; if (this.test("mut")) { this.step(); - tag = "ptr_mut"; + mut = true; } - const subject = this.parseTy(); - return this.ty({ type, subject }, pos); + const ty = this.parseTy(); + return this.ty({ tag: "ptr", ty, mut }, pos); } this.report("expected type"); return this.ty({ tag: "error" }, pos); } - private parseTyStructFields(): Param[] { + private parseAnonFieldDefs(): AnonFieldDef[] { this.step(); if (this.test("}")) { this.step(); return []; } - const params: Param[] = []; - const paramResult = this.parseParam(); + const params: AnonFieldDef[] = []; + const paramResult = this.parseAnonFieldDef(); if (!paramResult.ok) { return []; } - params.push(paramResult.value); + params.push(paramResult.val); while (this.test(",")) { this.step(); if (this.test("}")) { break; } - const paramResult = this.parseParam(); + const paramResult = this.parseAnonFieldDef(); if (!paramResult.ok) { return []; } - params.push(paramResult.value); + params.push(paramResult.val); } if (!this.test("}")) { this.report("expected '}'"); @@ -1083,10 +1154,72 @@ export class Parser { return params; } - private parsePath(): Path { + private parseAnonFieldDef(): ParseRes { + const begin = this.span(); + const identRes = this.eatIdent(); + if (!identRes.ok) return Res.Err(undefined); + const ident = identRes.val; + if (!this.test(":")) { + this.report("expected ':'"); + return Res.Err(undefined); + } + this.step(); + const ty = this.parseTy(); + return Res.Ok({ + ident, + ty, + span: Span.fromto(begin, ty.span), + }); + } + + private parsePath(): ParseRes { const begin = this.span(); let end = begin; const segments: PathSegment[] = []; + const identRes = this.eatIdent(); + if (!identRes.ok) return Res.Err(undefined); + const ident = identRes.val; + segments.push({ ident, span: Span.fromto(begin, end) }); + while (this.test("::")) { + this.step(); + if (!this.test("ident")) { + this.report("expected 'ident'"); + return Res.Err(undefined); + } + end = this.span(); + const ident = this.parseIdent(); + let genericArgs: Ty[] | undefined = undefined; + if (this.test("::")) { + this.step(); + if (!this.test("<")) { + this.report("expected '<'"); + return Res.Err(undefined); + } + genericArgs = this.parseDelimitedList( + this.parseTyRes, + ">", + ",", + ); + } + segments.push({ + ident, + genericArgs, + span: Span.fromto(begin, end), + }); + } + return Res.Ok({ segments, span: Span.fromto(begin, end) }); + } + + private parseTyRes(): ParseRes { + return Res.Ok(this.parseTy()); + } + + private eatIdent(): ParseRes { + if (!this.test("ident")) { + this.report("expected 'ident'"); + return Res.Err(undefined); + } + return Res.Ok(this.parseIdent()); } private parseIdent(): Ident { @@ -1107,11 +1240,12 @@ export class Parser { return this.currentToken!; } + private lastSpan?: Span; private span(): Span { if (this.done()) { - throw new Error(); + return this.lastSpan!; } - return this.current().span; + return this.lastSpan = this.current().span; } private test(type: string): boolean {