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"; type: "fn";
ident: string; ident: string;
etypeParams?: ETypeParam[]; genericParams?: GenericParam[];
params: Param[]; params: Param[];
returnType?: EType; returnType?: EType;
body: Expr; body: Expr;
@ -39,13 +39,18 @@ export type ExprKind =
| { type: "error" } | { type: "error" }
| { type: "int"; value: number } | { type: "int"; value: number }
| { type: "string"; value: string } | { type: "string"; value: string }
| { type: "ident"; value: string } | { type: "ident"; ident: string }
| {
type: "sym";
ident: string;
sym: Sym;
}
| { type: "group"; expr: Expr } | { type: "group"; expr: Expr }
| { type: "field"; subject: Expr; value: string } | { type: "field"; subject: Expr; ident: string }
| { type: "index"; subject: Expr; value: Expr } | { type: "index"; subject: Expr; value: Expr }
| { type: "call"; subject: Expr; etypeArgs?: EType[]; args: Expr[] } | { type: "call"; subject: Expr; args: Expr[] }
| { type: "path"; subject: Expr; value: string } | { type: "path"; subject: Expr; ident: string }
| { type: "etype_args"; subject: Expr; etypeArgs?: EType[] } | { type: "etype_args"; subject: Expr; etypeArgs: EType[] }
| { type: "unary"; unaryType: UnaryType; subject: Expr } | { type: "unary"; unaryType: UnaryType; subject: Expr }
| { type: "binary"; binaryType: BinaryType; left: Expr; right: Expr } | { type: "binary"; binaryType: BinaryType; left: Expr; right: Expr }
| { type: "if"; cond: Expr; truthy: Expr; falsy?: Expr; elsePos?: Pos } | { type: "if"; cond: Expr; truthy: Expr; falsy?: Expr; elsePos?: Pos }
@ -53,11 +58,6 @@ export type ExprKind =
| { type: "null" } | { type: "null" }
| { type: "loop"; body: Expr } | { type: "loop"; body: Expr }
| { type: "block"; stmts: Stmt[]; expr?: Expr } | { type: "block"; stmts: Stmt[]; expr?: Expr }
| {
type: "sym";
ident: string;
sym: Sym;
}
| { type: "while"; cond: Expr; body: Expr } | { type: "while"; cond: Expr; body: Expr }
| { type: "for_in"; param: Param; value: 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"; stmt: Stmt }
| { type: "fn_param"; param: Param } | { type: "fn_param"; param: Param }
| { type: "closure"; inner: Sym } | { type: "closure"; inner: Sym }
| { type: "generic"; etypeParam: ETypeParam }; | { type: "generic"; genericParam: GenericParam };
export type EType = { export type EType = {
kind: ETypeKind; kind: ETypeKind;
@ -111,11 +111,20 @@ export type EType = {
export type ETypeKind = export type ETypeKind =
| { type: "error" } | { 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: "array"; inner: EType }
| { type: "struct"; fields: Param[] }; | { type: "struct"; fields: Param[] };
export type ETypeParam = { export type GenericParam = {
ident: string; ident: string;
pos: Pos; pos: Pos;
vtype?: VType; vtype?: VType;

View File

@ -22,6 +22,8 @@ export interface AstVisitor<Args extends unknown[] = []> {
visitFieldExpr?(expr: Expr, ...args: Args): VisitRes; visitFieldExpr?(expr: Expr, ...args: Args): VisitRes;
visitIndexExpr?(expr: Expr, ...args: Args): VisitRes; visitIndexExpr?(expr: Expr, ...args: Args): VisitRes;
visitCallExpr?(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; visitUnaryExpr?(expr: Expr, ...args: Args): VisitRes;
visitBinaryExpr?(expr: Expr, ...args: Args): VisitRes; visitBinaryExpr?(expr: Expr, ...args: Args): VisitRes;
visitIfExpr?(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; visitParam?(param: Param, ...args: Args): VisitRes;
visitEType?(etype: EType, ...args: Args): VisitRes; visitEType?(etype: EType, ...args: Args): VisitRes;
visitErrorEType?(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; visitIdentEType?(etype: EType, ...args: Args): VisitRes;
visitSymEType?(etype: EType, ...args: Args): VisitRes;
visitArrayEType?(etype: EType, ...args: Args): VisitRes; visitArrayEType?(etype: EType, ...args: Args): VisitRes;
visitStructEType?(etype: EType, ...args: Args): VisitRes; visitStructEType?(etype: EType, ...args: Args): VisitRes;
visitAnno?(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; if (v.visitExprStmt?.(stmt, ...args) == "stop") return;
visitExpr(stmt.kind.expr, v, ...args); visitExpr(stmt.kind.expr, v, ...args);
break; 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); visitExpr(expr.kind.subject, v, ...args);
expr.kind.args.map((arg) => visitExpr(arg, v, ...args)); expr.kind.args.map((arg) => visitExpr(arg, v, ...args));
break; 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": case "unary":
if (v.visitUnaryExpr?.(expr, ...args) == "stop") return; if (v.visitUnaryExpr?.(expr, ...args) == "stop") return;
visitExpr(expr.kind.subject, v, ...args); visitExpr(expr.kind.subject, v, ...args);
@ -186,6 +208,12 @@ export function visitExpr<Args extends unknown[] = []>(
case "sym": case "sym":
if (v.visitSymExpr?.(expr, ...args) == "stop") return; if (v.visitSymExpr?.(expr, ...args) == "stop") return;
break; 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": case "error":
if (v.visitErrorEType?.(etype, ...args) == "stop") return; if (v.visitErrorEType?.(etype, ...args) == "stop") return;
break; 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": case "ident":
if (v.visitIdentEType?.(etype, ...args) == "stop") return; if (v.visitIdentEType?.(etype, ...args) == "stop") return;
break; break;
case "sym":
if (v.visitSymEType?.(etype, ...args) == "stop") return;
break;
case "array": case "array":
if (v.visitArrayEType?.(etype, ...args) == "stop") return; if (v.visitArrayEType?.(etype, ...args) == "stop") return;
if (etype.kind.inner) visitEType(etype.kind.inner, v, ...args); 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; if (v.visitStructEType?.(etype, ...args) == "stop") return;
etype.kind.fields.map((field) => visitParam(field, v, ...args)); etype.kind.fields.map((field) => visitParam(field, v, ...args));
break; 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 { EType, Expr, Stmt } from "./ast.ts";
import { printStackTrace, Reporter } from "./info.ts"; import { printStackTrace, Reporter } from "./info.ts";
import { Pos } from "./token.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 { export class Checker {
private fnReturnStack: VType[] = []; private fnReturnStack: VType[] = [];
@ -24,6 +30,13 @@ export class Checker {
const returnType: VType = stmt.kind.returnType const returnType: VType = stmt.kind.returnType
? this.checkEType(stmt.kind.returnType) ? this.checkEType(stmt.kind.returnType)
: { type: "null" }; : { 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[] = []; const params: VTypeParam[] = [];
for (const param of stmt.kind.params) { for (const param of stmt.kind.params) {
if (param.etype === undefined) { if (param.etype === undefined) {
@ -34,7 +47,7 @@ export class Checker {
param.vtype = vtype; param.vtype = vtype;
params.push({ ident: param.ident, 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); this.report("cannot use field on non-struct", pos);
return { type: "error" }; return { type: "error" };
} }
const fieldValue = stmt.kind.subject.kind.value; const fieldValue = stmt.kind.subject.kind.ident;
const found = subject.fields.find((param) => const found = subject.fields.find((param) =>
param.ident === fieldValue param.ident === fieldValue
); );
if (!found) { if (!found) {
this.report( this.report(
`no field named '${stmt.kind.subject.kind.value}' on struct`, `no field named '${stmt.kind.subject.kind.ident}' on struct`,
pos, pos,
); );
return { type: "error" }; return { type: "error" };
@ -281,6 +294,10 @@ export class Checker {
return this.checkIndexExpr(expr); return this.checkIndexExpr(expr);
case "call": case "call":
return this.checkCallExpr(expr); return this.checkCallExpr(expr);
case "path":
return this.checkPathExpr(expr);
case "etype_args":
return this.checkETypeArgsExpr(expr);
case "unary": case "unary":
return this.checkUnaryExpr(expr); return this.checkUnaryExpr(expr);
case "binary": case "binary":
@ -324,9 +341,9 @@ export class Checker {
} }
case "fn_param": case "fn_param":
return expr.kind.sym.param.vtype!; return expr.kind.sym.param.vtype!;
case "builtin":
case "let_static": case "let_static":
case "closure": case "closure":
case "generic":
throw new Error( throw new Error(
`not implemented, sym type '${expr.kind.sym.type}'`, `not implemented, sym type '${expr.kind.sym.type}'`,
); );
@ -343,11 +360,11 @@ export class Checker {
this.report("cannot use field on non-struct", pos); this.report("cannot use field on non-struct", pos);
return { type: "error" }; return { type: "error" };
} }
const value = expr.kind.value; const value = expr.kind.ident;
const found = subject.fields.find((param) => param.ident === value); const found = subject.fields.find((param) => param.ident === value);
if (!found) { if (!found) {
this.report( this.report(
`no field named '${expr.kind.value}' on struct`, `no field named '${expr.kind.ident}' on struct`,
pos, pos,
); );
return { type: "error" }; return { type: "error" };
@ -408,6 +425,43 @@ export class Checker {
return subject.returnType; 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 { public checkUnaryExpr(expr: Expr): VType {
if (expr.kind.type !== "unary") { if (expr.kind.type !== "unary") {
throw new Error(); throw new Error();
@ -556,20 +610,25 @@ export class Checker {
public checkEType(etype: EType): VType { public checkEType(etype: EType): VType {
const pos = etype.pos; const pos = etype.pos;
if (etype.kind.type === "ident") { switch (etype.kind.type) {
if (etype.kind.value === "null") { case "null":
return { type: "null" }; return { type: "null" };
} case "int":
if (etype.kind.value === "int") {
return { type: "int" }; return { type: "int" };
} case "bool":
if (etype.kind.value === "bool") {
return { type: "bool" }; return { type: "bool" };
} case "string":
if (etype.kind.value === "string") {
return { type: "string" }; return { type: "string" };
} }
this.report(`undefined type '${etype.kind.value}'`, pos); 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(`sym type '${etype.kind.sym.type}' used as type`, pos);
return { type: "error" }; return { type: "error" };
} }
if (etype.kind.type === "array") { if (etype.kind.type === "array") {

View File

@ -80,12 +80,12 @@ export class SpecialLoopDesugarer implements AstVisitor {
type: "call", type: "call",
subject: Expr({ subject: Expr({
type: "ident", type: "ident",
value: "int_array_length", ident: "int_array_length",
}), }),
args: [ args: [
Expr({ Expr({
type: "ident", type: "ident",
value: "::values", ident: "::values",
}), }),
], ],
}), }),
@ -114,11 +114,11 @@ export class SpecialLoopDesugarer implements AstVisitor {
binaryType: "<", binaryType: "<",
left: Expr({ left: Expr({
type: "ident", type: "ident",
value: "::index", ident: "::index",
}), }),
right: Expr({ right: Expr({
type: "ident", type: "ident",
value: "::length", ident: "::length",
}), }),
}), }),
}), }),
@ -139,11 +139,11 @@ export class SpecialLoopDesugarer implements AstVisitor {
type: "index", type: "index",
subject: Expr({ subject: Expr({
type: "ident", type: "ident",
value: "::values", ident: "::values",
}), }),
value: Expr({ value: Expr({
type: "ident", type: "ident",
value: "::index", ident: "::index",
}), }),
}), }),
}, expr.pos), }, expr.pos),
@ -156,7 +156,7 @@ export class SpecialLoopDesugarer implements AstVisitor {
assignType: "+=", assignType: "+=",
subject: Expr({ subject: Expr({
type: "ident", type: "ident",
value: "::index", ident: "::index",
}), }),
value: Expr({ value: Expr({
type: "int", type: "int",

View File

@ -36,15 +36,18 @@ export class Lexer {
"else", "else",
"struct", "struct",
"import", "import",
"false",
"true",
"null",
"or", "or",
"and", "and",
"not", "not",
"while", "while",
"for", "for",
"in", "in",
"false",
"true",
"null",
"int",
"bool",
"string",
]; ];
if (keywords.includes(value)) { if (keywords.includes(value)) {
return this.token(value, pos); return this.token(value, pos);

View File

@ -107,7 +107,7 @@ export class Lowerer {
switch (stmt.kind.subject.kind.type) { switch (stmt.kind.subject.kind.type) {
case "field": { case "field": {
this.lowerExpr(stmt.kind.subject.kind.subject); 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); this.program.add(Ops.Builtin, Builtins.StructSet);
return; return;
} }
@ -209,7 +209,7 @@ export class Lowerer {
`unexpected argument type '${anno.kind.type}' expected 'ident'`, `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) => const builtin = Object.entries(Builtins).find((entry) =>
entry[0] === value entry[0] === value
)?.[1]; )?.[1];

View File

@ -5,9 +5,9 @@ import {
BinaryType, BinaryType,
EType, EType,
ETypeKind, ETypeKind,
ETypeParam,
Expr, Expr,
ExprKind, ExprKind,
GenericParam,
Param, Param,
Stmt, Stmt,
StmtKind, StmtKind,
@ -175,9 +175,9 @@ export class Parser {
} }
const ident = this.current().identValue!; const ident = this.current().identValue!;
this.step(); this.step();
let etypeParams: ETypeParam[] | undefined; let genericParams: GenericParam[] | undefined;
if (this.test("<")) { if (this.test("<")) {
etypeParams = this.parseFnETypeParams(); genericParams = this.parseFnETypeParams();
} }
if (!this.test("(")) { if (!this.test("(")) {
this.report("expected '('"); this.report("expected '('");
@ -207,7 +207,7 @@ export class Parser {
{ {
type: "fn", type: "fn",
ident, ident,
etypeParams, genericParams,
params, params,
returnType, returnType,
body, body,
@ -265,11 +265,11 @@ export class Parser {
return { ok: true, value: { ident, pos, values } }; return { ok: true, value: { ident, pos, values } };
} }
private parseFnETypeParams(): ETypeParam[] { private parseFnETypeParams(): GenericParam[] {
return this.parseDelimitedList(this.parseETypeParam, ">", ","); return this.parseDelimitedList(this.parseETypeParam, ">", ",");
} }
private parseETypeParam(): Res<ETypeParam> { private parseETypeParam(): Res<GenericParam> {
const pos = this.pos(); const pos = this.pos();
if (this.test("ident")) { if (this.test("ident")) {
const ident = this.current().identValue!; const ident = this.current().identValue!;
@ -627,19 +627,58 @@ export class Parser {
private parsePostfix(): Expr { private parsePostfix(): Expr {
let subject = this.parseOperand(); let subject = this.parseOperand();
while (true) { while (true) {
const pos = this.pos();
if (this.test(".")) { if (this.test(".")) {
subject = this.parseFieldTail(subject);
continue;
}
if (this.test("[")) {
subject = this.parseIndexTail(subject);
continue;
}
if (this.test("(")) {
subject = this.parseCallTail(subject);
continue;
}
if (this.test("::")) {
subject = this.parsePathTail(subject);
continue;
}
if (this.test("::<")) {
subject = this.parseETypeArgsTail(subject);
continue;
}
break;
}
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(); this.step();
if (!this.test("ident")) { if (!this.test("ident")) {
this.report("expected ident"); this.report("expected ident");
return this.expr({ type: "error" }, pos); return this.expr({ type: "error" }, pos);
} }
const value = this.current().identValue!; const ident = this.current().identValue!;
this.step(); this.step();
subject = this.expr({ type: "field", subject, value }, pos); return this.expr({ type: "field", subject, ident }, pos);
continue;
} }
if (this.test("[")) {
private parseIndexTail(subject: Expr): Expr {
const pos = this.pos();
this.step(); this.step();
const value = this.parseExpr(); const value = this.parseExpr();
if (!this.test("]")) { if (!this.test("]")) {
@ -647,56 +686,29 @@ export class Parser {
return this.expr({ type: "error" }, pos); return this.expr({ type: "error" }, pos);
} }
this.step(); this.step();
subject = this.expr({ type: "index", subject, value }, pos); return this.expr({ type: "index", subject, value }, pos);
continue;
} }
if (this.test("(")) {
private parseCallTail(subject: Expr): Expr {
const pos = this.pos();
const args = this.parseDelimitedList( const args = this.parseDelimitedList(
this.parseExprArg, this.parseExprArg,
")", ")",
",", ",",
); );
subject = this.expr({ type: "call", subject, args }, pos); return this.expr({ type: "call", subject, args }, pos);
continue;
} }
if (this.test("::")) {
private parsePathTail(subject: Expr): Expr {
const pos = this.pos();
this.step(); this.step();
if (!this.test("ident")) { if (!this.test("ident")) {
this.report("expected ident"); this.report("expected ident");
return this.expr({ type: "error" }, pos); return this.expr({ type: "error" }, pos);
} }
const value = this.current().identValue!; const ident = this.current().identValue!;
this.step(); this.step();
subject = this.expr({ type: "path", subject, value }, pos); return this.expr({ type: "path", subject, ident }, pos);
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,
);
continue;
}
break;
}
return subject;
} }
private parseExprArg(): Res<Expr> { private parseExprArg(): Res<Expr> {
@ -710,9 +722,9 @@ export class Parser {
private parseOperand(): Expr { private parseOperand(): Expr {
const pos = this.pos(); const pos = this.pos();
if (this.test("ident")) { if (this.test("ident")) {
const value = this.current().identValue!; const ident = this.current().identValue!;
this.step(); this.step();
return this.expr({ type: "ident", value }, pos); return this.expr({ type: "ident", ident }, pos);
} }
if (this.test("int")) { if (this.test("int")) {
const value = this.current().intValue!; const value = this.current().intValue!;
@ -763,10 +775,19 @@ export class Parser {
private parseEType(): EType { private parseEType(): EType {
const pos = this.pos(); 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")) { if (this.test("ident")) {
const ident = this.current().identValue!; const ident = this.current().identValue!;
this.step(); this.step();
return this.etype({ type: "ident", value: ident }, pos); return this.etype({ type: "ident", ident: ident }, pos);
} }
if (this.test("[")) { if (this.test("[")) {
this.step(); this.step();

View File

@ -1,7 +1,9 @@
import { Expr, Stmt } from "./ast.ts"; import { EType, Expr, Stmt } from "./ast.ts";
import { import {
AstVisitor, AstVisitor,
visitEType,
visitExpr, visitExpr,
visitParam,
VisitRes, VisitRes,
visitStmt, visitStmt,
visitStmts, visitStmts,
@ -73,7 +75,7 @@ export class Resolver implements AstVisitor<[Syms]> {
throw new Error("expected fn statement"); throw new Error("expected fn statement");
} }
const fnScopeSyms = new FnSyms(syms); const fnScopeSyms = new FnSyms(syms);
for (const param of stmt.kind.etypeParams ?? []) { for (const param of stmt.kind.genericParams ?? []) {
if (fnScopeSyms.definedLocally(param.ident)) { if (fnScopeSyms.definedLocally(param.ident)) {
this.reportAlreadyDefined(param.ident, param.pos, syms); this.reportAlreadyDefined(param.ident, param.pos, syms);
continue; continue;
@ -82,7 +84,7 @@ export class Resolver implements AstVisitor<[Syms]> {
ident: param.ident, ident: param.ident,
type: "generic", type: "generic",
pos: param.pos, pos: param.pos,
etypeParam: param, genericParam: param,
}); });
} }
for (const param of stmt.kind.params) { for (const param of stmt.kind.params) {
@ -90,6 +92,7 @@ export class Resolver implements AstVisitor<[Syms]> {
this.reportAlreadyDefined(param.ident, param.pos, syms); this.reportAlreadyDefined(param.ident, param.pos, syms);
continue; continue;
} }
visitParam(param, this, fnScopeSyms);
fnScopeSyms.define(param.ident, { fnScopeSyms.define(param.ident, {
ident: param.ident, ident: param.ident,
type: "fn_param", type: "fn_param",
@ -97,6 +100,9 @@ export class Resolver implements AstVisitor<[Syms]> {
param, param,
}); });
} }
if (stmt.kind.returnType) {
visitEType(stmt.kind.returnType, this, fnScopeSyms);
}
visitExpr(stmt.kind.body, this, fnScopeSyms); visitExpr(stmt.kind.body, this, fnScopeSyms);
return "stop"; return "stop";
} }
@ -105,18 +111,14 @@ export class Resolver implements AstVisitor<[Syms]> {
if (expr.kind.type !== "ident") { if (expr.kind.type !== "ident") {
throw new Error("expected ident"); throw new Error("expected ident");
} }
const ident = expr.kind; const ident = expr.kind.ident;
const symResult = syms.get(ident.value); const symResult = syms.get(ident);
if (!symResult.ok) { if (!symResult.ok) {
this.reportUseOfUndefined(ident.value, expr.pos, syms); this.reportUseOfUndefined(ident, expr.pos, syms);
return; return;
} }
const sym = symResult.sym; const sym = symResult.sym;
expr.kind = { expr.kind = { type: "sym", ident, sym };
type: "sym",
ident: ident.value,
sym,
};
return "stop"; return "stop";
} }
@ -146,6 +148,21 @@ export class Resolver implements AstVisitor<[Syms]> {
return "stop"; 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) { private reportUseOfUndefined(ident: string, pos: Pos, _syms: Syms) {
this.reporter.reportError({ this.reporter.reportError({
reporter: "Resolver", reporter: "Resolver",

View File

@ -7,13 +7,28 @@ export type VType =
| { type: "bool" } | { type: "bool" }
| { type: "array"; inner: VType } | { type: "array"; inner: VType }
| { type: "struct"; fields: VTypeParam[] } | { 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 = { export type VTypeParam = {
ident: string; ident: string;
vtype: VType; vtype: VType;
}; };
export type VTypeGenericParam = {
ident: string;
};
export function vtypesEqual(a: VType, b: VType): boolean { export function vtypesEqual(a: VType, b: VType): boolean {
if (a.type !== b.type) { if (a.type !== b.type) {
return false; return false;
@ -58,5 +73,8 @@ export function vtypeToString(vtype: VType): string {
.join(", "); .join(", ");
return `fn (${paramString}) -> ${vtypeToString(vtype.returnType)}`; return `fn (${paramString}) -> ${vtypeToString(vtype.returnType)}`;
} }
if (vtype.type === "generic") {
return `generic`;
}
throw new Error(`unhandled vtype '${vtype.type}'`); throw new Error(`unhandled vtype '${vtype.type}'`);
} }