From 0eaa8fc60d19a4893ec6ed72818c3640d58b4e3b Mon Sep 17 00:00:00 2001 From: sfja Date: Mon, 23 Dec 2024 03:15:43 +0100 Subject: [PATCH] compiler: more work along the lines of generics --- compiler/ast.ts | 37 +++++--- compiler/ast_visitor.ts | 49 +++++++++++ compiler/checker.ts | 91 ++++++++++++++++---- compiler/desugar/special_loop.ts | 14 +-- compiler/lexer.ts | 9 +- compiler/lowerer.ts | 4 +- compiler/parser.ts | 143 ++++++++++++++++++------------- compiler/resolver.ts | 39 ++++++--- compiler/vtype.ts | 20 ++++- 9 files changed, 291 insertions(+), 115 deletions(-) diff --git a/compiler/ast.ts b/compiler/ast.ts index 0728cbf..8a9c7b0 100644 --- a/compiler/ast.ts +++ b/compiler/ast.ts @@ -15,7 +15,7 @@ export type StmtKind = | { type: "fn"; ident: string; - etypeParams?: ETypeParam[]; + genericParams?: GenericParam[]; params: Param[]; returnType?: EType; body: Expr; @@ -39,13 +39,18 @@ export type ExprKind = | { type: "error" } | { type: "int"; value: number } | { type: "string"; value: string } - | { type: "ident"; value: string } + | { type: "ident"; ident: string } + | { + type: "sym"; + ident: string; + sym: Sym; + } | { type: "group"; expr: Expr } - | { type: "field"; subject: Expr; value: string } + | { type: "field"; subject: Expr; ident: string } | { type: "index"; subject: Expr; value: Expr } - | { type: "call"; subject: Expr; etypeArgs?: EType[]; args: Expr[] } - | { type: "path"; subject: Expr; value: string } - | { type: "etype_args"; subject: Expr; etypeArgs?: EType[] } + | { type: "call"; subject: Expr; args: Expr[] } + | { type: "path"; subject: Expr; ident: string } + | { type: "etype_args"; subject: Expr; etypeArgs: EType[] } | { type: "unary"; unaryType: UnaryType; subject: Expr } | { type: "binary"; binaryType: BinaryType; left: Expr; right: Expr } | { type: "if"; cond: Expr; truthy: Expr; falsy?: Expr; elsePos?: Pos } @@ -53,11 +58,6 @@ export type ExprKind = | { type: "null" } | { type: "loop"; body: Expr } | { type: "block"; stmts: Stmt[]; expr?: Expr } - | { - type: "sym"; - ident: string; - sym: Sym; - } | { type: "while"; cond: Expr; body: Expr } | { type: "for_in"; param: Param; value: Expr; body: Expr } | { @@ -101,7 +101,7 @@ export type SymKind = | { type: "fn"; stmt: Stmt } | { type: "fn_param"; param: Param } | { type: "closure"; inner: Sym } - | { type: "generic"; etypeParam: ETypeParam }; + | { type: "generic"; genericParam: GenericParam }; export type EType = { kind: ETypeKind; @@ -111,11 +111,20 @@ export type EType = { export type ETypeKind = | { type: "error" } - | { type: "ident"; value: string } + | { type: "null" } + | { type: "int" } + | { type: "bool" } + | { type: "string" } + | { type: "ident"; ident: string } + | { + type: "sym"; + ident: string; + sym: Sym; + } | { type: "array"; inner: EType } | { type: "struct"; fields: Param[] }; -export type ETypeParam = { +export type GenericParam = { ident: string; pos: Pos; vtype?: VType; diff --git a/compiler/ast_visitor.ts b/compiler/ast_visitor.ts index b63d1e3..3e87c52 100644 --- a/compiler/ast_visitor.ts +++ b/compiler/ast_visitor.ts @@ -22,6 +22,8 @@ export interface AstVisitor { visitFieldExpr?(expr: Expr, ...args: Args): VisitRes; visitIndexExpr?(expr: Expr, ...args: Args): VisitRes; visitCallExpr?(expr: Expr, ...args: Args): VisitRes; + visitPathExpr?(expr: Expr, ...args: Args): VisitRes; + visitETypeArgsExpr?(expr: Expr, ...args: Args): VisitRes; visitUnaryExpr?(expr: Expr, ...args: Args): VisitRes; visitBinaryExpr?(expr: Expr, ...args: Args): VisitRes; visitIfExpr?(expr: Expr, ...args: Args): VisitRes; @@ -36,7 +38,12 @@ export interface AstVisitor { visitParam?(param: Param, ...args: Args): VisitRes; visitEType?(etype: EType, ...args: Args): VisitRes; visitErrorEType?(etype: EType, ...args: Args): VisitRes; + visitNullEType?(etype: EType, ...args: Args): VisitRes; + visitIntEType?(etype: EType, ...args: Args): VisitRes; + visitBoolEType?(etype: EType, ...args: Args): VisitRes; + visitStringEType?(etype: EType, ...args: Args): VisitRes; visitIdentEType?(etype: EType, ...args: Args): VisitRes; + visitSymEType?(etype: EType, ...args: Args): VisitRes; visitArrayEType?(etype: EType, ...args: Args): VisitRes; visitStructEType?(etype: EType, ...args: Args): VisitRes; visitAnno?(etype: EType, ...args: Args): VisitRes; @@ -95,6 +102,12 @@ export function visitStmt( if (v.visitExprStmt?.(stmt, ...args) == "stop") return; visitExpr(stmt.kind.expr, v, ...args); break; + default: + throw new Error( + `statement '${ + (stmt.kind as { type: string }).type + }' not implemented`, + ); } } @@ -135,6 +148,15 @@ export function visitExpr( visitExpr(expr.kind.subject, v, ...args); expr.kind.args.map((arg) => visitExpr(arg, v, ...args)); break; + case "path": + if (v.visitPathExpr?.(expr, ...args) == "stop") return; + visitExpr(expr.kind.subject, v, ...args); + break; + case "etype_args": + if (v.visitETypeArgsExpr?.(expr, ...args) == "stop") return; + visitExpr(expr.kind.subject, v, ...args); + expr.kind.etypeArgs.map((arg) => visitEType(arg, v, ...args)); + break; case "unary": if (v.visitUnaryExpr?.(expr, ...args) == "stop") return; visitExpr(expr.kind.subject, v, ...args); @@ -186,6 +208,12 @@ export function visitExpr( case "sym": if (v.visitSymExpr?.(expr, ...args) == "stop") return; break; + default: + throw new Error( + `expression '${ + (expr.kind as { type: string }).type + }' not implemented`, + ); } } @@ -208,9 +236,24 @@ export function visitEType( case "error": if (v.visitErrorEType?.(etype, ...args) == "stop") return; break; + case "string": + if (v.visitStringEType?.(etype, ...args) == "stop") return; + break; + case "null": + if (v.visitNullEType?.(etype, ...args) == "stop") return; + break; + case "int": + if (v.visitIntEType?.(etype, ...args) == "stop") return; + break; + case "bool": + if (v.visitBoolEType?.(etype, ...args) == "stop") return; + break; case "ident": if (v.visitIdentEType?.(etype, ...args) == "stop") return; break; + case "sym": + if (v.visitSymEType?.(etype, ...args) == "stop") return; + break; case "array": if (v.visitArrayEType?.(etype, ...args) == "stop") return; if (etype.kind.inner) visitEType(etype.kind.inner, v, ...args); @@ -219,6 +262,12 @@ export function visitEType( if (v.visitStructEType?.(etype, ...args) == "stop") return; etype.kind.fields.map((field) => visitParam(field, v, ...args)); break; + default: + throw new Error( + `etype '${ + (etype.kind as { type: string }).type + }' not implemented`, + ); } } diff --git a/compiler/checker.ts b/compiler/checker.ts index f491b0d..d300d9e 100644 --- a/compiler/checker.ts +++ b/compiler/checker.ts @@ -1,7 +1,13 @@ import { EType, Expr, Stmt } from "./ast.ts"; import { printStackTrace, Reporter } from "./info.ts"; import { Pos } from "./token.ts"; -import { VType, VTypeParam, vtypesEqual, vtypeToString } from "./vtype.ts"; +import { + VType, + VTypeGenericParam, + VTypeParam, + vtypesEqual, + vtypeToString, +} from "./vtype.ts"; export class Checker { private fnReturnStack: VType[] = []; @@ -24,6 +30,13 @@ export class Checker { const returnType: VType = stmt.kind.returnType ? this.checkEType(stmt.kind.returnType) : { type: "null" }; + let genericParams: VTypeGenericParam[] | undefined; + if (stmt.kind.genericParams !== undefined) { + genericParams = []; + for (const etypeParam of stmt.kind.genericParams) { + genericParams.push({ ident: etypeParam.ident }); + } + } const params: VTypeParam[] = []; for (const param of stmt.kind.params) { if (param.etype === undefined) { @@ -34,7 +47,7 @@ export class Checker { param.vtype = vtype; params.push({ ident: param.ident, vtype }); } - stmt.kind.vtype = { type: "fn", params, returnType }; + stmt.kind.vtype = { type: "fn", genericParams, params, returnType }; } } @@ -178,13 +191,13 @@ export class Checker { this.report("cannot use field on non-struct", pos); return { type: "error" }; } - const fieldValue = stmt.kind.subject.kind.value; + const fieldValue = stmt.kind.subject.kind.ident; const found = subject.fields.find((param) => param.ident === fieldValue ); if (!found) { this.report( - `no field named '${stmt.kind.subject.kind.value}' on struct`, + `no field named '${stmt.kind.subject.kind.ident}' on struct`, pos, ); return { type: "error" }; @@ -281,6 +294,10 @@ export class Checker { return this.checkIndexExpr(expr); case "call": return this.checkCallExpr(expr); + case "path": + return this.checkPathExpr(expr); + case "etype_args": + return this.checkETypeArgsExpr(expr); case "unary": return this.checkUnaryExpr(expr); case "binary": @@ -324,9 +341,9 @@ export class Checker { } case "fn_param": return expr.kind.sym.param.vtype!; - case "builtin": case "let_static": case "closure": + case "generic": throw new Error( `not implemented, sym type '${expr.kind.sym.type}'`, ); @@ -343,11 +360,11 @@ export class Checker { this.report("cannot use field on non-struct", pos); return { type: "error" }; } - const value = expr.kind.value; + const value = expr.kind.ident; const found = subject.fields.find((param) => param.ident === value); if (!found) { this.report( - `no field named '${expr.kind.value}' on struct`, + `no field named '${expr.kind.ident}' on struct`, pos, ); return { type: "error" }; @@ -408,6 +425,43 @@ export class Checker { return subject.returnType; } + public checkPathExpr(expr: Expr): VType { + if (expr.kind.type !== "path") { + throw new Error(); + } + throw new Error("not implemented"); + } + + public checkETypeArgsExpr(expr: Expr): VType { + if (expr.kind.type !== "etype_args") { + throw new Error(); + } + const pos = expr.pos; + const subject = this.checkExpr(expr.kind.subject); + if (subject.type !== "fn" || subject.genericParams === undefined) { + this.report( + "etype arguments must only be applied to generic functions", + expr.pos, + ); + return { type: "error" }; + } + const genericParams = expr.kind.etypeArgs.map((arg) => + this.checkEType(arg) + ); + if (genericParams.length !== subject.params.length) { + this.report( + `incorrect number of arguments` + + `, expected ${subject.params.length}`, + pos, + ); + } + return { + type: "generic_spec", + subject, + genericParams, + }; + } + public checkUnaryExpr(expr: Expr): VType { if (expr.kind.type !== "unary") { throw new Error(); @@ -556,20 +610,25 @@ export class Checker { public checkEType(etype: EType): VType { const pos = etype.pos; - if (etype.kind.type === "ident") { - if (etype.kind.value === "null") { + switch (etype.kind.type) { + case "null": return { type: "null" }; - } - if (etype.kind.value === "int") { + case "int": return { type: "int" }; - } - if (etype.kind.value === "bool") { + case "bool": return { type: "bool" }; - } - if (etype.kind.value === "string") { + case "string": return { type: "string" }; + } + if (etype.kind.type === "ident") { + this.report(`undefined type '${etype.kind.ident}'`, pos); + return { type: "error" }; + } + if (etype.kind.type === "sym") { + if (etype.kind.sym.type === "generic") { + return { type: "generic" }; } - this.report(`undefined type '${etype.kind.value}'`, pos); + this.report(`sym type '${etype.kind.sym.type}' used as type`, pos); return { type: "error" }; } if (etype.kind.type === "array") { diff --git a/compiler/desugar/special_loop.ts b/compiler/desugar/special_loop.ts index cb5c5d3..4323ed9 100644 --- a/compiler/desugar/special_loop.ts +++ b/compiler/desugar/special_loop.ts @@ -80,12 +80,12 @@ export class SpecialLoopDesugarer implements AstVisitor { type: "call", subject: Expr({ type: "ident", - value: "int_array_length", + ident: "int_array_length", }), args: [ Expr({ type: "ident", - value: "::values", + ident: "::values", }), ], }), @@ -114,11 +114,11 @@ export class SpecialLoopDesugarer implements AstVisitor { binaryType: "<", left: Expr({ type: "ident", - value: "::index", + ident: "::index", }), right: Expr({ type: "ident", - value: "::length", + ident: "::length", }), }), }), @@ -139,11 +139,11 @@ export class SpecialLoopDesugarer implements AstVisitor { type: "index", subject: Expr({ type: "ident", - value: "::values", + ident: "::values", }), value: Expr({ type: "ident", - value: "::index", + ident: "::index", }), }), }, expr.pos), @@ -156,7 +156,7 @@ export class SpecialLoopDesugarer implements AstVisitor { assignType: "+=", subject: Expr({ type: "ident", - value: "::index", + ident: "::index", }), value: Expr({ type: "int", diff --git a/compiler/lexer.ts b/compiler/lexer.ts index 4774ae9..d19a884 100644 --- a/compiler/lexer.ts +++ b/compiler/lexer.ts @@ -36,15 +36,18 @@ export class Lexer { "else", "struct", "import", - "false", - "true", - "null", "or", "and", "not", "while", "for", "in", + "false", + "true", + "null", + "int", + "bool", + "string", ]; if (keywords.includes(value)) { return this.token(value, pos); diff --git a/compiler/lowerer.ts b/compiler/lowerer.ts index aa54231..64beadc 100644 --- a/compiler/lowerer.ts +++ b/compiler/lowerer.ts @@ -107,7 +107,7 @@ export class Lowerer { switch (stmt.kind.subject.kind.type) { case "field": { this.lowerExpr(stmt.kind.subject.kind.subject); - this.program.add(Ops.PushString, stmt.kind.subject.kind.value); + this.program.add(Ops.PushString, stmt.kind.subject.kind.ident); this.program.add(Ops.Builtin, Builtins.StructSet); return; } @@ -209,7 +209,7 @@ export class Lowerer { `unexpected argument type '${anno.kind.type}' expected 'ident'`, ); } - const value = anno.kind.value; + const value = anno.kind.ident; const builtin = Object.entries(Builtins).find((entry) => entry[0] === value )?.[1]; diff --git a/compiler/parser.ts b/compiler/parser.ts index 831b314..c0c7ebe 100644 --- a/compiler/parser.ts +++ b/compiler/parser.ts @@ -5,9 +5,9 @@ import { BinaryType, EType, ETypeKind, - ETypeParam, Expr, ExprKind, + GenericParam, Param, Stmt, StmtKind, @@ -175,9 +175,9 @@ export class Parser { } const ident = this.current().identValue!; this.step(); - let etypeParams: ETypeParam[] | undefined; + let genericParams: GenericParam[] | undefined; if (this.test("<")) { - etypeParams = this.parseFnETypeParams(); + genericParams = this.parseFnETypeParams(); } if (!this.test("(")) { this.report("expected '('"); @@ -207,7 +207,7 @@ export class Parser { { type: "fn", ident, - etypeParams, + genericParams, params, returnType, body, @@ -265,11 +265,11 @@ export class Parser { return { ok: true, value: { ident, pos, values } }; } - private parseFnETypeParams(): ETypeParam[] { + private parseFnETypeParams(): GenericParam[] { return this.parseDelimitedList(this.parseETypeParam, ">", ","); } - private parseETypeParam(): Res { + private parseETypeParam(): Res { const pos = this.pos(); if (this.test("ident")) { const ident = this.current().identValue!; @@ -627,71 +627,24 @@ export class Parser { private parsePostfix(): Expr { let subject = this.parseOperand(); while (true) { - const pos = this.pos(); if (this.test(".")) { - this.step(); - if (!this.test("ident")) { - this.report("expected ident"); - return this.expr({ type: "error" }, pos); - } - const value = this.current().identValue!; - this.step(); - subject = this.expr({ type: "field", subject, value }, pos); + subject = this.parseFieldTail(subject); continue; } if (this.test("[")) { - this.step(); - const value = this.parseExpr(); - if (!this.test("]")) { - this.report("expected ']'"); - return this.expr({ type: "error" }, pos); - } - this.step(); - subject = this.expr({ type: "index", subject, value }, pos); + subject = this.parseIndexTail(subject); continue; } if (this.test("(")) { - const args = this.parseDelimitedList( - this.parseExprArg, - ")", - ",", - ); - subject = this.expr({ type: "call", subject, args }, pos); + subject = this.parseCallTail(subject); continue; } if (this.test("::")) { - this.step(); - if (!this.test("ident")) { - this.report("expected ident"); - return this.expr({ type: "error" }, pos); - } - const value = this.current().identValue!; - this.step(); - subject = this.expr({ type: "path", subject, value }, pos); + subject = this.parsePathTail(subject); continue; } if (this.test("::<")) { - const etypeArgs = this.parseDelimitedList( - this.parseETypeArg, - ">", - ",", - ); - if (this.test("(")) { - subject = this.expr( - { type: "etype_args", subject, etypeArgs }, - pos, - ); - continue; - } - const args = this.parseDelimitedList( - this.parseExprArg, - ")", - ",", - ); - subject = this.expr( - { type: "call", subject, etypeArgs, args }, - pos, - ); + subject = this.parseETypeArgsTail(subject); continue; } break; @@ -699,6 +652,65 @@ export class Parser { return subject; } + private parseETypeArgsTail(subject: Expr): Expr { + const pos = this.pos(); + const etypeArgs = this.parseDelimitedList( + this.parseETypeArg, + ">", + ",", + ); + return this.expr( + { type: "etype_args", subject, etypeArgs }, + pos, + ); + } + + private parseFieldTail(subject: Expr): Expr { + const pos = this.pos(); + this.step(); + if (!this.test("ident")) { + this.report("expected ident"); + return this.expr({ type: "error" }, pos); + } + const ident = this.current().identValue!; + this.step(); + return this.expr({ type: "field", subject, ident }, pos); + } + + private parseIndexTail(subject: Expr): Expr { + const pos = this.pos(); + this.step(); + const value = this.parseExpr(); + if (!this.test("]")) { + this.report("expected ']'"); + return this.expr({ type: "error" }, pos); + } + this.step(); + return this.expr({ type: "index", subject, value }, pos); + } + + private parseCallTail(subject: Expr): Expr { + const pos = this.pos(); + const args = this.parseDelimitedList( + this.parseExprArg, + ")", + ",", + ); + return this.expr({ type: "call", subject, args }, pos); + } + + private parsePathTail(subject: Expr): Expr { + const pos = this.pos(); + this.step(); + if (!this.test("ident")) { + this.report("expected ident"); + return this.expr({ type: "error" }, pos); + } + const ident = this.current().identValue!; + this.step(); + return this.expr({ type: "path", subject, ident }, pos); + } + private parseExprArg(): Res { return { ok: true, value: this.parseExpr() }; } @@ -710,9 +722,9 @@ export class Parser { private parseOperand(): Expr { const pos = this.pos(); if (this.test("ident")) { - const value = this.current().identValue!; + const ident = this.current().identValue!; this.step(); - return this.expr({ type: "ident", value }, pos); + return this.expr({ type: "ident", ident }, pos); } if (this.test("int")) { const value = this.current().intValue!; @@ -763,10 +775,19 @@ export class Parser { private parseEType(): EType { const pos = this.pos(); + if (["null", "int", "bool", "string"].includes(this.current().type)) { + const type = this.current().type as + | "null" + | "int" + | "bool" + | "string"; + this.step(); + return this.etype({ type }, pos); + } if (this.test("ident")) { const ident = this.current().identValue!; this.step(); - return this.etype({ type: "ident", value: ident }, pos); + return this.etype({ type: "ident", ident: ident }, pos); } if (this.test("[")) { this.step(); diff --git a/compiler/resolver.ts b/compiler/resolver.ts index 577d57d..b6b067c 100644 --- a/compiler/resolver.ts +++ b/compiler/resolver.ts @@ -1,7 +1,9 @@ -import { Expr, Stmt } from "./ast.ts"; +import { EType, Expr, Stmt } from "./ast.ts"; import { AstVisitor, + visitEType, visitExpr, + visitParam, VisitRes, visitStmt, visitStmts, @@ -73,7 +75,7 @@ export class Resolver implements AstVisitor<[Syms]> { throw new Error("expected fn statement"); } const fnScopeSyms = new FnSyms(syms); - for (const param of stmt.kind.etypeParams ?? []) { + for (const param of stmt.kind.genericParams ?? []) { if (fnScopeSyms.definedLocally(param.ident)) { this.reportAlreadyDefined(param.ident, param.pos, syms); continue; @@ -82,7 +84,7 @@ export class Resolver implements AstVisitor<[Syms]> { ident: param.ident, type: "generic", pos: param.pos, - etypeParam: param, + genericParam: param, }); } for (const param of stmt.kind.params) { @@ -90,6 +92,7 @@ export class Resolver implements AstVisitor<[Syms]> { this.reportAlreadyDefined(param.ident, param.pos, syms); continue; } + visitParam(param, this, fnScopeSyms); fnScopeSyms.define(param.ident, { ident: param.ident, type: "fn_param", @@ -97,6 +100,9 @@ export class Resolver implements AstVisitor<[Syms]> { param, }); } + if (stmt.kind.returnType) { + visitEType(stmt.kind.returnType, this, fnScopeSyms); + } visitExpr(stmt.kind.body, this, fnScopeSyms); return "stop"; } @@ -105,18 +111,14 @@ export class Resolver implements AstVisitor<[Syms]> { if (expr.kind.type !== "ident") { throw new Error("expected ident"); } - const ident = expr.kind; - const symResult = syms.get(ident.value); + const ident = expr.kind.ident; + const symResult = syms.get(ident); if (!symResult.ok) { - this.reportUseOfUndefined(ident.value, expr.pos, syms); + this.reportUseOfUndefined(ident, expr.pos, syms); return; } const sym = symResult.sym; - expr.kind = { - type: "sym", - ident: ident.value, - sym, - }; + expr.kind = { type: "sym", ident, sym }; return "stop"; } @@ -146,6 +148,21 @@ export class Resolver implements AstVisitor<[Syms]> { return "stop"; } + visitIdentEType(etype: EType, syms: Syms): VisitRes { + if (etype.kind.type !== "ident") { + throw new Error(); + } + const ident = etype.kind.ident; + const symResult = syms.get(ident); + if (!symResult.ok) { + this.reportUseOfUndefined(ident, etype.pos, syms); + return; + } + const sym = symResult.sym; + etype.kind = { type: "sym", ident, sym }; + return "stop"; + } + private reportUseOfUndefined(ident: string, pos: Pos, _syms: Syms) { this.reporter.reportError({ reporter: "Resolver", diff --git a/compiler/vtype.ts b/compiler/vtype.ts index cb92482..7363697 100644 --- a/compiler/vtype.ts +++ b/compiler/vtype.ts @@ -7,13 +7,28 @@ export type VType = | { type: "bool" } | { type: "array"; inner: VType } | { type: "struct"; fields: VTypeParam[] } - | { type: "fn"; params: VTypeParam[]; returnType: VType }; + | { + type: "fn"; + genericParams?: VTypeGenericParam[]; + params: VTypeParam[]; + returnType: VType; + } + | { type: "generic" } + | { + type: "generic_spec"; + subject: VType; + genericParams: VType[]; + }; export type VTypeParam = { ident: string; vtype: VType; }; +export type VTypeGenericParam = { + ident: string; +}; + export function vtypesEqual(a: VType, b: VType): boolean { if (a.type !== b.type) { return false; @@ -58,5 +73,8 @@ export function vtypeToString(vtype: VType): string { .join(", "); return `fn (${paramString}) -> ${vtypeToString(vtype.returnType)}`; } + if (vtype.type === "generic") { + return `generic`; + } throw new Error(`unhandled vtype '${vtype.type}'`); }