compiler: more work along the lines of generics

This commit is contained in:
sfja 2024-12-23 03:15:43 +01:00
parent bc82124601
commit cd923450f5
9 changed files with 291 additions and 115 deletions

View File

@ -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;

View File

@ -22,6 +22,8 @@ export interface AstVisitor<Args extends unknown[] = []> {
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<Args extends unknown[] = []> {
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<Args extends unknown[] = []>(
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<Args extends unknown[] = []>(
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<Args extends unknown[] = []>(
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<Args extends unknown[] = []>(
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<Args extends unknown[] = []>(
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`,
);
}
}

View File

@ -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") {

View File

@ -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",

View File

@ -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);

View File

@ -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];

View File

@ -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<ETypeParam> {
private parseETypeParam(): Res<GenericParam> {
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<Expr> {
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();

View File

@ -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",

View File

@ -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}'`);
}