Compare commits
27 Commits
teknikfags
...
main
Author | SHA1 | Date | |
---|---|---|---|
5d6b1abefc | |||
a7349890d0 | |||
3babacd58d | |||
01c80000ed | |||
f3da09d9c2 | |||
82a2f259e1 | |||
ac36353609 | |||
26acdc10ca | |||
f56df189c4 | |||
d981e60f8f | |||
9f17396571 | |||
e56725dd4f | |||
170e153947 | |||
f2b1323337 | |||
94a57029c0 | |||
7ffd2879d1 | |||
5642e3fc5a | |||
c65ab5329f | |||
a4c1b60a61 | |||
5150090d2d | |||
cab2c9baa3 | |||
f712b0f3a5 | |||
7b5fee745d | |||
cd923450f5 | |||
bc82124601 | |||
7944c76a6a | |||
c2ae0b2a2e |
@ -40,7 +40,8 @@ export const Ops = {
|
||||
|
||||
export type Builtins = typeof Builtins;
|
||||
export const Builtins = {
|
||||
IntToString: 0x00,
|
||||
Exit: 0x00,
|
||||
IntToString: 0x01,
|
||||
StringConcat: 0x10,
|
||||
StringEqual: 0x11,
|
||||
StringCharAt: 0x12,
|
||||
@ -52,7 +53,9 @@ export const Builtins = {
|
||||
ArrayPush: 0x22,
|
||||
ArrayAt: 0x23,
|
||||
ArrayLength: 0x24,
|
||||
StructSet: 0x30,
|
||||
StructNew: 0x30,
|
||||
StructSet: 0x31,
|
||||
StructAt: 0x32,
|
||||
Print: 0x40,
|
||||
FileOpen: 0x41,
|
||||
FileClose: 0x42,
|
||||
|
162
compiler/ast.ts
162
compiler/ast.ts
@ -1,32 +1,50 @@
|
||||
import type { Syms } from "./resolver_syms.ts";
|
||||
import { Pos } from "./token.ts";
|
||||
import { VType } from "./vtype.ts";
|
||||
import { GenericArgsMap, VType } from "./vtype.ts";
|
||||
|
||||
export type Mod = {
|
||||
filePath: string;
|
||||
ast: Stmt[];
|
||||
};
|
||||
|
||||
export type Stmt = {
|
||||
kind: StmtKind;
|
||||
pos: Pos;
|
||||
details?: StmtDetails;
|
||||
id: number;
|
||||
};
|
||||
|
||||
export type StmtKind =
|
||||
| { type: "error" }
|
||||
| { type: "import"; path: Expr }
|
||||
| { type: "mod_block"; ident: string; stmts: Stmt[] }
|
||||
| { type: "mod_file"; ident: string; filePath: string }
|
||||
| { type: "mod"; ident: string; mod: Mod }
|
||||
| { type: "break"; expr?: Expr }
|
||||
| { type: "return"; expr?: Expr }
|
||||
| {
|
||||
type: "fn";
|
||||
ident: string;
|
||||
params: Param[];
|
||||
returnType?: EType;
|
||||
body: Expr;
|
||||
anno?: Anno;
|
||||
vtype?: VType;
|
||||
}
|
||||
| FnStmtKind
|
||||
| { type: "let"; param: Param; value: Expr }
|
||||
| { type: "type_alias"; param: Param }
|
||||
| { type: "assign"; assignType: AssignType; subject: Expr; value: Expr }
|
||||
| { type: "expr"; expr: Expr };
|
||||
|
||||
export type FnStmtKind = {
|
||||
type: "fn";
|
||||
ident: string;
|
||||
genericParams?: GenericParam[];
|
||||
params: Param[];
|
||||
returnType?: EType;
|
||||
body: Expr;
|
||||
sym?: Sym;
|
||||
vtype?: VType;
|
||||
};
|
||||
|
||||
export type AssignType = "=" | "+=" | "-=";
|
||||
|
||||
export type StmtDetails = {
|
||||
pub: boolean;
|
||||
annos: Anno[];
|
||||
};
|
||||
|
||||
export type Expr = {
|
||||
kind: ExprKind;
|
||||
pos: Pos;
|
||||
@ -38,11 +56,28 @@ 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: "ref"; subject: Expr }
|
||||
| { type: "ref_mut"; subject: Expr }
|
||||
| { type: "deref"; subject: Expr }
|
||||
| { type: "array"; exprs: Expr[] }
|
||||
| { type: "struct"; fields: Field[] }
|
||||
| { type: "field"; subject: Expr; ident: string }
|
||||
| { type: "index"; subject: Expr; value: Expr }
|
||||
| { type: "call"; subject: Expr; args: Expr[] }
|
||||
| {
|
||||
type: "call";
|
||||
subject: Expr;
|
||||
args: Expr[];
|
||||
genericArgs?: GenericArgsMap;
|
||||
}
|
||||
| { 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 }
|
||||
@ -50,11 +85,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 }
|
||||
| {
|
||||
@ -80,25 +110,38 @@ export type BinaryType =
|
||||
| "or"
|
||||
| "and";
|
||||
|
||||
export type Param = {
|
||||
export type Field = {
|
||||
ident: string;
|
||||
expr: Expr;
|
||||
pos: Pos;
|
||||
};
|
||||
|
||||
export type Param = {
|
||||
id: number;
|
||||
index?: number;
|
||||
ident: string;
|
||||
mut: boolean;
|
||||
etype?: EType;
|
||||
pos: Pos;
|
||||
sym?: Sym;
|
||||
vtype?: VType;
|
||||
};
|
||||
|
||||
export type Sym = {
|
||||
ident: string;
|
||||
fullPath: string;
|
||||
pos?: Pos;
|
||||
} & SymKind;
|
||||
|
||||
export type SymKind =
|
||||
| { type: "let"; stmt: Stmt; param: Param }
|
||||
| { type: "let_static"; stmt: Stmt; param: Param }
|
||||
| { type: "type_alias"; stmt: Stmt; param: Param }
|
||||
| { type: "fn"; stmt: Stmt }
|
||||
| { type: "fn_param"; param: Param }
|
||||
| { type: "closure"; inner: Sym }
|
||||
| { type: "builtin"; builtinId: number };
|
||||
| { type: "generic"; stmt: Stmt; genericParam: GenericParam }
|
||||
| { type: "mod"; syms: Syms };
|
||||
|
||||
export type EType = {
|
||||
kind: ETypeKind;
|
||||
@ -108,11 +151,27 @@ export type EType = {
|
||||
|
||||
export type ETypeKind =
|
||||
| { type: "error" }
|
||||
| { type: "ident"; value: string }
|
||||
| { type: "array"; inner: EType }
|
||||
| { type: "struct"; fields: Param[] };
|
||||
| { type: "null" }
|
||||
| { type: "int" }
|
||||
| { type: "bool" }
|
||||
| { type: "string" }
|
||||
| { type: "ident"; ident: string }
|
||||
| {
|
||||
type: "sym";
|
||||
ident: string;
|
||||
sym: Sym;
|
||||
}
|
||||
| { type: "ref"; subject: EType }
|
||||
| { type: "ref_mut"; subject: EType }
|
||||
| { type: "ptr"; subject: EType }
|
||||
| { type: "ptr_mut"; subject: EType }
|
||||
| { type: "array"; subject: EType }
|
||||
| { type: "struct"; fields: Param[] }
|
||||
| { type: "type_of"; expr: Expr };
|
||||
|
||||
export type ETypeParam = {
|
||||
export type GenericParam = {
|
||||
id: number;
|
||||
index: number;
|
||||
ident: string;
|
||||
pos: Pos;
|
||||
vtype?: VType;
|
||||
@ -120,28 +179,63 @@ export type ETypeParam = {
|
||||
|
||||
export type Anno = {
|
||||
ident: string;
|
||||
values: Expr[];
|
||||
args: Expr[];
|
||||
pos: Pos;
|
||||
};
|
||||
|
||||
export class AstCreator {
|
||||
private nextNodeId = 0;
|
||||
|
||||
public stmt(kind: StmtKind, pos: Pos): Stmt {
|
||||
const id = this.nextNodeId;
|
||||
this.nextNodeId += 1;
|
||||
return { kind, pos, id };
|
||||
public stmt(kind: StmtKind, pos: Pos, details?: StmtDetails): Stmt {
|
||||
const id = this.genId();
|
||||
return { kind, pos, details, id };
|
||||
}
|
||||
|
||||
public expr(kind: ExprKind, pos: Pos): Expr {
|
||||
const id = this.nextNodeId;
|
||||
this.nextNodeId += 1;
|
||||
const id = this.genId();
|
||||
return { kind, pos, id };
|
||||
}
|
||||
|
||||
public etype(kind: ETypeKind, pos: Pos): EType {
|
||||
const id = this.nextNodeId;
|
||||
this.nextNodeId += 1;
|
||||
const id = this.genId();
|
||||
return { kind, pos, id };
|
||||
}
|
||||
|
||||
public param(val: Omit<Param, "id">): Param {
|
||||
const id = this.genId();
|
||||
return { ...val, id };
|
||||
}
|
||||
|
||||
public genericParam(val: Omit<GenericParam, "id">): GenericParam {
|
||||
const id = this.genId();
|
||||
return { ...val, id };
|
||||
}
|
||||
|
||||
private genId(): number {
|
||||
const id = this.nextNodeId;
|
||||
this.nextNodeId += 1;
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
export class AnnoView {
|
||||
public constructor(private details?: StmtDetails) {}
|
||||
|
||||
public has(...idents: string[]): boolean {
|
||||
return this.details?.annos.some((anno) =>
|
||||
idents.some((ident) => anno.ident === ident)
|
||||
) ?? false;
|
||||
}
|
||||
|
||||
public get(ident: string): Anno {
|
||||
const anno = this.details?.annos.find((anno) => anno.ident === ident);
|
||||
if (!anno) {
|
||||
throw new Error();
|
||||
}
|
||||
return anno;
|
||||
}
|
||||
}
|
||||
|
||||
export function forceType(v: unknown): { type: string } {
|
||||
return v as { type: string };
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { EType, Expr, Param, Stmt } from "./ast.ts";
|
||||
import { EType, Expr, Field, Param, Stmt } from "./ast.ts";
|
||||
|
||||
export type VisitRes = "stop" | void;
|
||||
|
||||
@ -6,11 +6,14 @@ export interface AstVisitor<Args extends unknown[] = []> {
|
||||
visitStmts?(stmts: Stmt[], ...args: Args): VisitRes;
|
||||
visitStmt?(stmt: Stmt, ...args: Args): VisitRes;
|
||||
visitErrorStmt?(stmt: Stmt, ...args: Args): VisitRes;
|
||||
visitImportStmt?(stmt: Stmt, ...args: Args): VisitRes;
|
||||
visitModFileStmt?(stmt: Stmt, ...args: Args): VisitRes;
|
||||
visitModBlockStmt?(stmt: Stmt, ...args: Args): VisitRes;
|
||||
visitModStmt?(stmt: Stmt, ...args: Args): VisitRes;
|
||||
visitBreakStmt?(stmt: Stmt, ...args: Args): VisitRes;
|
||||
visitReturnStmt?(stmt: Stmt, ...args: Args): VisitRes;
|
||||
visitFnStmt?(stmt: Stmt, ...args: Args): VisitRes;
|
||||
visitLetStmt?(stmt: Stmt, ...args: Args): VisitRes;
|
||||
visitTypeAliasStmt?(stmt: Stmt, ...args: Args): VisitRes;
|
||||
visitAssignStmt?(stmt: Stmt, ...args: Args): VisitRes;
|
||||
visitExprStmt?(stmt: Stmt, ...args: Args): VisitRes;
|
||||
visitExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||
@ -19,9 +22,16 @@ export interface AstVisitor<Args extends unknown[] = []> {
|
||||
visitStringExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||
visitIdentExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||
visitGroupExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||
visitRefExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||
visitRefMutExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||
visitDerefExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||
visitArrayExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||
visitStructExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||
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;
|
||||
@ -34,11 +44,22 @@ export interface AstVisitor<Args extends unknown[] = []> {
|
||||
visitBlockExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||
visitSymExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||
visitParam?(param: Param, ...args: Args): VisitRes;
|
||||
visitField?(field: Field, ...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;
|
||||
visitRefEType?(etype: EType, ...args: Args): VisitRes;
|
||||
visitRefMutEType?(etype: EType, ...args: Args): VisitRes;
|
||||
visitPtrEType?(etype: EType, ...args: Args): VisitRes;
|
||||
visitPtrMutEType?(etype: EType, ...args: Args): VisitRes;
|
||||
visitArrayEType?(etype: EType, ...args: Args): VisitRes;
|
||||
visitStructEType?(etype: EType, ...args: Args): VisitRes;
|
||||
visitTypeOfEType?(etype: EType, ...args: Args): VisitRes;
|
||||
visitAnno?(etype: EType, ...args: Args): VisitRes;
|
||||
}
|
||||
|
||||
@ -61,9 +82,16 @@ export function visitStmt<Args extends unknown[] = []>(
|
||||
case "error":
|
||||
if (v.visitErrorStmt?.(stmt, ...args) == "stop") return;
|
||||
break;
|
||||
case "import":
|
||||
if (v.visitImportStmt?.(stmt, ...args) == "stop") return;
|
||||
visitExpr(stmt.kind.path, v, ...args);
|
||||
case "mod_file":
|
||||
if (v.visitModFileStmt?.(stmt, ...args) == "stop") return;
|
||||
break;
|
||||
case "mod_block":
|
||||
if (v.visitModBlockStmt?.(stmt, ...args) == "stop") return;
|
||||
visitStmts(stmt.kind.stmts, v, ...args);
|
||||
break;
|
||||
case "mod":
|
||||
if (v.visitModStmt?.(stmt, ...args) == "stop") return;
|
||||
visitStmts(stmt.kind.mod.ast, v, ...args);
|
||||
break;
|
||||
case "break":
|
||||
if (v.visitBreakStmt?.(stmt, ...args) == "stop") return;
|
||||
@ -86,6 +114,10 @@ export function visitStmt<Args extends unknown[] = []>(
|
||||
visitParam(stmt.kind.param, v, ...args);
|
||||
visitExpr(stmt.kind.value, v, ...args);
|
||||
break;
|
||||
case "type_alias":
|
||||
if (v.visitTypeAliasStmt?.(stmt, ...args) == "stop") return;
|
||||
visitParam(stmt.kind.param, v, ...args);
|
||||
break;
|
||||
case "assign":
|
||||
if (v.visitAssignStmt?.(stmt, ...args) == "stop") return;
|
||||
visitExpr(stmt.kind.subject, v, ...args);
|
||||
@ -95,6 +127,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`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,6 +159,18 @@ export function visitExpr<Args extends unknown[] = []>(
|
||||
if (v.visitGroupExpr?.(expr, ...args) == "stop") return;
|
||||
visitExpr(expr.kind.expr, v, ...args);
|
||||
break;
|
||||
case "ref":
|
||||
if (v.visitRefExpr?.(expr, ...args) == "stop") return;
|
||||
visitExpr(expr.kind.subject, v, ...args);
|
||||
break;
|
||||
case "ref_mut":
|
||||
if (v.visitRefMutExpr?.(expr, ...args) == "stop") return;
|
||||
visitExpr(expr.kind.subject, v, ...args);
|
||||
break;
|
||||
case "deref":
|
||||
if (v.visitDerefExpr?.(expr, ...args) == "stop") return;
|
||||
visitExpr(expr.kind.subject, v, ...args);
|
||||
break;
|
||||
case "field":
|
||||
if (v.visitFieldExpr?.(expr, ...args) == "stop") return;
|
||||
visitExpr(expr.kind.subject, v, ...args);
|
||||
@ -135,6 +185,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);
|
||||
@ -144,6 +203,14 @@ export function visitExpr<Args extends unknown[] = []>(
|
||||
visitExpr(expr.kind.left, v, ...args);
|
||||
visitExpr(expr.kind.right, v, ...args);
|
||||
break;
|
||||
case "array":
|
||||
if (v.visitArrayExpr?.(expr, ...args) == "stop") return;
|
||||
expr.kind.exprs.map((expr) => visitExpr(expr, v, ...args));
|
||||
break;
|
||||
case "struct":
|
||||
if (v.visitStructExpr?.(expr, ...args) == "stop") return;
|
||||
expr.kind.fields.map((field) => visitField(field, v, ...args));
|
||||
break;
|
||||
case "if":
|
||||
if (v.visitIfExpr?.(expr, ...args) == "stop") return;
|
||||
visitExpr(expr.kind.cond, v, ...args);
|
||||
@ -186,6 +253,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`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,6 +271,15 @@ export function visitParam<Args extends unknown[] = []>(
|
||||
if (param.etype) visitEType(param.etype, v, ...args);
|
||||
}
|
||||
|
||||
export function visitField<Args extends unknown[] = []>(
|
||||
field: Field,
|
||||
v: AstVisitor<Args>,
|
||||
...args: Args
|
||||
) {
|
||||
if (v.visitField?.(field, ...args) == "stop") return;
|
||||
visitExpr(field.expr, v, ...args);
|
||||
}
|
||||
|
||||
export function visitEType<Args extends unknown[] = []>(
|
||||
etype: EType,
|
||||
v: AstVisitor<Args>,
|
||||
@ -208,17 +290,58 @@ 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 "ref":
|
||||
if (v.visitRefEType?.(etype, ...args) == "stop") return;
|
||||
visitEType(etype.kind.subject, v, ...args);
|
||||
break;
|
||||
case "ref_mut":
|
||||
if (v.visitRefMutEType?.(etype, ...args) == "stop") return;
|
||||
visitEType(etype.kind.subject, v, ...args);
|
||||
break;
|
||||
case "ptr":
|
||||
if (v.visitPtrEType?.(etype, ...args) == "stop") return;
|
||||
visitEType(etype.kind.subject, v, ...args);
|
||||
break;
|
||||
case "ptr_mut":
|
||||
if (v.visitPtrMutEType?.(etype, ...args) == "stop") return;
|
||||
visitEType(etype.kind.subject, v, ...args);
|
||||
break;
|
||||
case "array":
|
||||
if (v.visitArrayEType?.(etype, ...args) == "stop") return;
|
||||
if (etype.kind.inner) visitEType(etype.kind.inner, v, ...args);
|
||||
visitEType(etype.kind.subject, v, ...args);
|
||||
break;
|
||||
case "struct":
|
||||
if (v.visitStructEType?.(etype, ...args) == "stop") return;
|
||||
etype.kind.fields.map((field) => visitParam(field, v, ...args));
|
||||
break;
|
||||
case "type_of":
|
||||
if (v.visitTypeOfEType?.(etype, ...args) == "stop") return;
|
||||
visitExpr(etype.kind.expr, v, ...args);
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
`etype '${
|
||||
(etype.kind as { type: string }).type
|
||||
}' not implemented`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,40 +1,75 @@
|
||||
import { EType, Expr, Stmt } from "./ast.ts";
|
||||
import { AnnoView, EType, Expr, forceType, Stmt, Sym } from "./ast.ts";
|
||||
import { printStackTrace, Reporter } from "./info.ts";
|
||||
import { Pos } from "./token.ts";
|
||||
import { VType, VTypeParam, vtypesEqual, vtypeToString } from "./vtype.ts";
|
||||
import {
|
||||
extractGenericType,
|
||||
GenericArgsMap,
|
||||
VType,
|
||||
VTypeGenericParam,
|
||||
VTypeParam,
|
||||
vtypesEqual,
|
||||
vtypeToString,
|
||||
} from "./vtype.ts";
|
||||
|
||||
export class Checker {
|
||||
private fnReturnStack: VType[] = [];
|
||||
private loopBreakStack: VType[][] = [];
|
||||
|
||||
private globalIdToGenericParamMap = new Map<number, VTypeGenericParam>();
|
||||
|
||||
public constructor(private reporter: Reporter) {}
|
||||
|
||||
public check(stmts: Stmt[]) {
|
||||
this.checkFnHeaders(stmts);
|
||||
this.scout(stmts);
|
||||
for (const stmt of stmts) {
|
||||
this.checkStmt(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
private checkFnHeaders(stmts: Stmt[]) {
|
||||
private scout(stmts: Stmt[]) {
|
||||
for (const stmt of stmts) {
|
||||
if (stmt.kind.type !== "fn") {
|
||||
continue;
|
||||
}
|
||||
const returnType: VType = stmt.kind.returnType
|
||||
? this.checkEType(stmt.kind.returnType)
|
||||
: { type: "null" };
|
||||
const params: VTypeParam[] = [];
|
||||
for (const param of stmt.kind.params) {
|
||||
if (param.etype === undefined) {
|
||||
this.report("parameter types must be defined", param.pos);
|
||||
stmt.kind.vtype = { type: "error" };
|
||||
if (stmt.kind.type === "fn") {
|
||||
let genericParams: VTypeGenericParam[] | undefined;
|
||||
if (stmt.kind.genericParams !== undefined) {
|
||||
genericParams = [];
|
||||
for (const etypeParam of stmt.kind.genericParams) {
|
||||
const id = genericParams.length;
|
||||
const globalId = etypeParam.id;
|
||||
const param = { id, ident: etypeParam.ident };
|
||||
genericParams.push(param);
|
||||
this.globalIdToGenericParamMap.set(globalId, param);
|
||||
}
|
||||
}
|
||||
const vtype = this.checkEType(param.etype!);
|
||||
param.vtype = vtype;
|
||||
params.push({ ident: param.ident, vtype });
|
||||
const params: VTypeParam[] = [];
|
||||
for (const param of stmt.kind.params) {
|
||||
if (param.etype === undefined) {
|
||||
this.report(
|
||||
"parameter types must be defined",
|
||||
param.pos,
|
||||
);
|
||||
stmt.kind.vtype = { type: "error" };
|
||||
}
|
||||
const vtype = this.checkEType(param.etype!);
|
||||
param.vtype = vtype;
|
||||
params.push({ ident: param.ident, mut: true, vtype });
|
||||
}
|
||||
const returnType: VType = stmt.kind.returnType
|
||||
? this.checkEType(stmt.kind.returnType)
|
||||
: { type: "null" };
|
||||
stmt.kind.vtype = {
|
||||
type: "fn",
|
||||
genericParams,
|
||||
params,
|
||||
returnType,
|
||||
stmtId: stmt.id,
|
||||
};
|
||||
} else if (stmt.kind.type === "type_alias") {
|
||||
if (!stmt.kind.param.etype) {
|
||||
this.report("no type specified", stmt.pos);
|
||||
return;
|
||||
}
|
||||
stmt.kind.param.vtype = this.checkEType(stmt.kind.param.etype);
|
||||
}
|
||||
stmt.kind.vtype = { type: "fn", params, returnType };
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,6 +77,11 @@ export class Checker {
|
||||
switch (stmt.kind.type) {
|
||||
case "error":
|
||||
return { type: "error" };
|
||||
case "mod_block":
|
||||
case "mod_file":
|
||||
throw new Error("mod declaration in ast, should be resolved");
|
||||
case "mod":
|
||||
return this.checkModStmt(stmt);
|
||||
case "break":
|
||||
return this.checkBreakStmt(stmt);
|
||||
case "return":
|
||||
@ -50,6 +90,8 @@ export class Checker {
|
||||
return this.checkFnStmt(stmt);
|
||||
case "let":
|
||||
return this.checkLetStmt(stmt);
|
||||
case "type_alias":
|
||||
return this.checkTypeAliasStmt(stmt);
|
||||
case "assign":
|
||||
return this.checkAssignStmt(stmt);
|
||||
case "expr":
|
||||
@ -57,6 +99,17 @@ export class Checker {
|
||||
}
|
||||
}
|
||||
|
||||
public checkModStmt(stmt: Stmt) {
|
||||
if (stmt.kind.type !== "mod") {
|
||||
throw new Error();
|
||||
}
|
||||
const { ast } = stmt.kind.mod;
|
||||
this.scout(ast);
|
||||
for (const stmt of ast) {
|
||||
this.checkStmt(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
public checkBreakStmt(stmt: Stmt) {
|
||||
if (stmt.kind.type !== "break") {
|
||||
throw new Error();
|
||||
@ -103,8 +156,8 @@ export class Checker {
|
||||
if (!vtypesEqual(exprType, returnType)) {
|
||||
this.report(
|
||||
`incompatible return type` +
|
||||
`, got ${exprType}` +
|
||||
`, expected ${returnType}`,
|
||||
`, expected ${vtypeToString(returnType)}` +
|
||||
`, got ${vtypeToString(exprType)}`,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
@ -119,16 +172,17 @@ export class Checker {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
if (
|
||||
stmt.kind.anno?.ident === "remainder" ||
|
||||
stmt.kind.anno?.ident === "builtin"
|
||||
) {
|
||||
const annos = new AnnoView(stmt.details);
|
||||
if (annos.has("builtin", "remainder")) {
|
||||
// NOTE: handled in lowerer
|
||||
return;
|
||||
}
|
||||
|
||||
const { returnType } = stmt.kind.vtype!;
|
||||
if (returnType.type === "error") return returnType;
|
||||
this.fnReturnStack.push(returnType);
|
||||
const body = this.checkExpr(stmt.kind.body);
|
||||
if (body.type === "error") return body;
|
||||
this.fnReturnStack.pop();
|
||||
|
||||
if (!vtypesEqual(returnType, body)) {
|
||||
@ -147,13 +201,17 @@ export class Checker {
|
||||
}
|
||||
const pos = stmt.pos;
|
||||
const value = this.checkExpr(stmt.kind.value);
|
||||
if (value.type === "error") {
|
||||
return stmt.kind.param.vtype = value;
|
||||
}
|
||||
if (stmt.kind.param.etype) {
|
||||
const paramVtype = this.checkEType(stmt.kind.param.etype);
|
||||
if (!vtypesEqual(value, paramVtype)) {
|
||||
const paramVType = this.checkEType(stmt.kind.param.etype);
|
||||
if (paramVType.type === "error") return paramVType;
|
||||
if (!vtypesEqual(value, paramVType)) {
|
||||
this.report(
|
||||
`incompatible value type` +
|
||||
`, got '${vtypeToString(value)}'` +
|
||||
`, expected '${vtypeToString(paramVtype)}'`,
|
||||
`, expected '${vtypeToString(paramVType)}'`,
|
||||
pos,
|
||||
);
|
||||
return;
|
||||
@ -162,6 +220,18 @@ export class Checker {
|
||||
stmt.kind.param.vtype = value;
|
||||
}
|
||||
|
||||
public checkTypeAliasStmt(stmt: Stmt) {
|
||||
if (stmt.kind.type !== "type_alias") {
|
||||
throw new Error();
|
||||
}
|
||||
const pos = stmt.pos;
|
||||
if (!stmt.kind.param.etype) {
|
||||
this.report("no type specified", pos);
|
||||
return;
|
||||
}
|
||||
stmt.kind.param.vtype = this.checkEType(stmt.kind.param.etype);
|
||||
}
|
||||
|
||||
public checkAssignStmt(stmt: Stmt) {
|
||||
if (stmt.kind.type !== "assign") {
|
||||
throw new Error();
|
||||
@ -178,13 +248,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" };
|
||||
@ -216,7 +286,7 @@ export class Checker {
|
||||
}
|
||||
if (
|
||||
subject.type == "array" &&
|
||||
!vtypesEqual(subject.inner, value)
|
||||
!vtypesEqual(subject.subject, value)
|
||||
) {
|
||||
this.report(
|
||||
`cannot assign incompatible type to array ` +
|
||||
@ -262,6 +332,9 @@ export class Checker {
|
||||
case "error":
|
||||
throw new Error("error in AST");
|
||||
case "ident":
|
||||
if (this.reporter.errorOccured()) {
|
||||
return { type: "error" };
|
||||
}
|
||||
throw new Error("ident expr in AST");
|
||||
case "sym":
|
||||
return this.checkSymExpr(expr);
|
||||
@ -275,12 +348,26 @@ export class Checker {
|
||||
return { type: "string" };
|
||||
case "group":
|
||||
return this.checkExpr(expr.kind.expr);
|
||||
case "ref":
|
||||
return this.checkRefExpr(expr);
|
||||
case "ref_mut":
|
||||
return this.checkRefMutExpr(expr);
|
||||
case "deref":
|
||||
return this.checkDerefExpr(expr);
|
||||
case "array":
|
||||
throw new Error("should have been desugared");
|
||||
case "struct":
|
||||
return this.checkStructExpr(expr);
|
||||
case "field":
|
||||
return this.checkFieldExpr(expr);
|
||||
case "index":
|
||||
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":
|
||||
@ -307,11 +394,17 @@ export class Checker {
|
||||
if (expr.kind.type !== "sym") {
|
||||
throw new Error();
|
||||
}
|
||||
switch (expr.kind.sym.type) {
|
||||
return this.checkSym(expr.kind.sym);
|
||||
}
|
||||
|
||||
private checkSym(sym: Sym): VType {
|
||||
switch (sym.type) {
|
||||
case "let":
|
||||
return expr.kind.sym.param.vtype!;
|
||||
return sym.param.vtype!;
|
||||
case "type_alias":
|
||||
return sym.param.vtype!;
|
||||
case "fn": {
|
||||
const fnStmt = expr.kind.sym.stmt!;
|
||||
const fnStmt = sym.stmt!;
|
||||
if (fnStmt.kind.type !== "fn") {
|
||||
throw new Error();
|
||||
}
|
||||
@ -319,20 +412,117 @@ export class Checker {
|
||||
if (vtype.type !== "fn") {
|
||||
throw new Error();
|
||||
}
|
||||
const { params, returnType } = vtype;
|
||||
return { type: "fn", params, returnType };
|
||||
return vtype;
|
||||
}
|
||||
case "fn_param":
|
||||
return expr.kind.sym.param.vtype!;
|
||||
case "builtin":
|
||||
return sym.param.vtype!;
|
||||
case "let_static":
|
||||
case "closure":
|
||||
case "generic":
|
||||
throw new Error(
|
||||
`not implemented, sym type '${expr.kind.sym.type}'`,
|
||||
`not implemented, sym type '${sym.type}'`,
|
||||
);
|
||||
case "mod":
|
||||
throw new Error("should already be resolved");
|
||||
}
|
||||
}
|
||||
|
||||
public checkRefExpr(expr: Expr): VType {
|
||||
if (expr.kind.type !== "ref") {
|
||||
throw new Error();
|
||||
}
|
||||
const subject = this.checkExpr(expr.kind.subject);
|
||||
if (expr.kind.subject.kind.type === "sym") {
|
||||
const sym = expr.kind.subject.kind.sym;
|
||||
if (sym.type === "let" || sym.type === "fn_param") {
|
||||
return { type: "ref", subject };
|
||||
}
|
||||
this.report(
|
||||
`taking reference to symbol type '${sym.type}' not supported`,
|
||||
expr.pos,
|
||||
);
|
||||
return { type: "error" };
|
||||
}
|
||||
this.report(
|
||||
`taking reference to expression type '${
|
||||
forceType(expr.kind.subject.kind).type
|
||||
}' not supported`,
|
||||
expr.pos,
|
||||
);
|
||||
return { type: "error" };
|
||||
}
|
||||
|
||||
public checkRefMutExpr(expr: Expr): VType {
|
||||
if (expr.kind.type !== "ref_mut") {
|
||||
throw new Error();
|
||||
}
|
||||
const subject = this.checkExpr(expr.kind.subject);
|
||||
if (expr.kind.subject.kind.type === "sym") {
|
||||
const sym = expr.kind.subject.kind.sym;
|
||||
if (sym.type === "let" || sym.type === "fn_param") {
|
||||
if (!sym.param.mut) {
|
||||
this.report(
|
||||
`symbol '${sym.ident}' it not declared mutable`,
|
||||
expr.pos,
|
||||
);
|
||||
this.reporter.addNote({
|
||||
reporter: "checker",
|
||||
msg: "symbol defined here",
|
||||
pos: sym.param.pos,
|
||||
});
|
||||
return { type: "error" };
|
||||
}
|
||||
return { type: "ref_mut", subject };
|
||||
}
|
||||
this.report(
|
||||
`taking reference to symbol type '${sym.type}' not supported`,
|
||||
expr.pos,
|
||||
);
|
||||
return { type: "error" };
|
||||
}
|
||||
this.report(
|
||||
`taking mutable reference to expression type '${
|
||||
forceType(expr.kind.subject.kind).type
|
||||
}' not supported`,
|
||||
expr.pos,
|
||||
);
|
||||
return { type: "error" };
|
||||
}
|
||||
public checkDerefExpr(expr: Expr): VType {
|
||||
if (expr.kind.type !== "deref") {
|
||||
throw new Error();
|
||||
}
|
||||
const subject = this.checkExpr(expr.kind.subject);
|
||||
switch (subject.type) {
|
||||
case "ref":
|
||||
return subject.subject;
|
||||
case "ref_mut":
|
||||
return subject.subject;
|
||||
case "ptr":
|
||||
return subject.subject;
|
||||
case "ptr_mut":
|
||||
return subject.subject;
|
||||
}
|
||||
this.report(
|
||||
`dereferenced type is neither a reference nor a pointer`,
|
||||
expr.pos,
|
||||
);
|
||||
return { type: "error" };
|
||||
}
|
||||
|
||||
public checkStructExpr(expr: Expr): VType {
|
||||
if (expr.kind.type !== "struct") {
|
||||
throw new Error();
|
||||
}
|
||||
const fields: VTypeParam[] = expr.kind.fields
|
||||
.map(({ ident, expr }): VTypeParam => ({
|
||||
ident,
|
||||
mut: true,
|
||||
vtype: this.checkExpr(expr),
|
||||
}));
|
||||
return { type: "struct", fields };
|
||||
}
|
||||
|
||||
public checkFieldExpr(expr: Expr): VType {
|
||||
if (expr.kind.type !== "field") {
|
||||
throw new Error();
|
||||
@ -343,11 +533,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" };
|
||||
@ -371,7 +561,7 @@ export class Checker {
|
||||
return { type: "error" };
|
||||
}
|
||||
if (subject.type === "array") {
|
||||
return subject.inner;
|
||||
return subject.subject;
|
||||
}
|
||||
return { type: "int" };
|
||||
}
|
||||
@ -382,18 +572,62 @@ export class Checker {
|
||||
}
|
||||
const pos = expr.pos;
|
||||
const subject = this.checkExpr(expr.kind.subject);
|
||||
if (subject.type !== "fn") {
|
||||
this.report("cannot call non-fn", pos);
|
||||
return { type: "error" };
|
||||
}
|
||||
const args = expr.kind.args.map((arg) => this.checkExpr(arg));
|
||||
if (args.length !== subject.params.length) {
|
||||
this.report(
|
||||
`incorrect number of arguments` +
|
||||
`, expected ${subject.params.length}`,
|
||||
if (subject.type === "error") return subject;
|
||||
if (subject.type === "fn") {
|
||||
if (expr.kind.args.length !== subject.params.length) {
|
||||
this.report(
|
||||
`expected ${subject.params.length} arguments` +
|
||||
`, got ${expr.kind.args.length}`,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
const args = expr.kind.args.map((arg) => this.checkExpr(arg));
|
||||
if (args.some((arg) => arg.type === "error")) {
|
||||
return { type: "error" };
|
||||
}
|
||||
if (subject.genericParams === undefined) {
|
||||
return this.checkCallExprNoGenericsTail(
|
||||
expr,
|
||||
subject,
|
||||
args,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
return this.checkCallExprInferredGenericsTail(
|
||||
expr,
|
||||
subject,
|
||||
args,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
if (subject.type === "generic_spec" && subject.subject.type === "fn") {
|
||||
return this.checkCallExprExplicitGenericsTail(expr, subject);
|
||||
}
|
||||
this.report("cannot call non-fn", pos);
|
||||
return { type: "error" };
|
||||
}
|
||||
|
||||
private checkCallExprNoGenericsTail(
|
||||
expr: Expr,
|
||||
subject: VType,
|
||||
args: VType[],
|
||||
pos: Pos,
|
||||
): VType {
|
||||
if (
|
||||
expr.kind.type !== "call" || subject.type !== "fn"
|
||||
) {
|
||||
throw new Error();
|
||||
}
|
||||
for (let i = 0; i < args.length; ++i) {
|
||||
if (this.vtypeContainsGeneric(args[i])) {
|
||||
this.report(
|
||||
`amfibious generic parameter for argument ${i}, please specify generic types explicitly`,
|
||||
pos,
|
||||
);
|
||||
return { type: "error" };
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < args.length; ++i) {
|
||||
if (!vtypesEqual(args[i], subject.params[i].vtype)) {
|
||||
this.report(
|
||||
@ -405,15 +639,283 @@ export class Checker {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return subject.returnType;
|
||||
}
|
||||
|
||||
private checkCallExprInferredGenericsTail(
|
||||
expr: Expr,
|
||||
subject: VType,
|
||||
args: VType[],
|
||||
pos: Pos,
|
||||
): VType {
|
||||
if (
|
||||
expr.kind.type !== "call" || subject.type !== "fn" ||
|
||||
subject.genericParams === undefined
|
||||
) {
|
||||
throw new Error();
|
||||
}
|
||||
const genericArgsRes = this.inferGenericArgs(
|
||||
subject.genericParams,
|
||||
subject.params,
|
||||
args,
|
||||
pos,
|
||||
);
|
||||
if (!genericArgsRes.ok) {
|
||||
return { type: "error" };
|
||||
}
|
||||
const genericArgs = genericArgsRes.value;
|
||||
|
||||
for (let i = 0; i < args.length; ++i) {
|
||||
const vtypeCompatible = vtypesEqual(
|
||||
args[i],
|
||||
subject.params[i].vtype,
|
||||
genericArgs,
|
||||
);
|
||||
if (!vtypeCompatible) {
|
||||
this.report(
|
||||
`incorrect argument ${i} '${subject.params[i].ident}'` +
|
||||
`, expected ${
|
||||
vtypeToString(
|
||||
extractGenericType(
|
||||
subject.params[i].vtype,
|
||||
genericArgs,
|
||||
),
|
||||
)
|
||||
}` +
|
||||
`, got ${vtypeToString(args[i])}`,
|
||||
pos,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
expr.kind.genericArgs = genericArgs;
|
||||
return this.concretizeVType(subject.returnType, genericArgs);
|
||||
}
|
||||
|
||||
private inferGenericArgs(
|
||||
genericParams: VTypeGenericParam[],
|
||||
params: VTypeParam[],
|
||||
args: VType[],
|
||||
pos: Pos,
|
||||
): { ok: true; value: GenericArgsMap } | { ok: false } {
|
||||
const genericArgs: GenericArgsMap = {};
|
||||
|
||||
for (let i = 0; i < params.length; ++i) {
|
||||
if (!this.vtypeContainsGeneric(params[i].vtype)) {
|
||||
continue;
|
||||
}
|
||||
const {
|
||||
a: generic,
|
||||
b: concrete,
|
||||
} = this.reduceToSignificant(params[i].vtype, args[i]);
|
||||
if (generic.type !== "generic") {
|
||||
throw new Error();
|
||||
}
|
||||
const paramId = generic.param.id;
|
||||
if (
|
||||
paramId in genericArgs &&
|
||||
!vtypesEqual(genericArgs[paramId], concrete)
|
||||
) {
|
||||
this.report(
|
||||
`according to inferrence, argument ${i} has a conflicting type`,
|
||||
pos,
|
||||
);
|
||||
return { ok: false };
|
||||
}
|
||||
genericArgs[paramId] = concrete;
|
||||
}
|
||||
|
||||
for (const param of genericParams) {
|
||||
if (!(param.id in genericArgs)) {
|
||||
this.report(`could not infer generic type ${param.ident}`, pos);
|
||||
return { ok: false };
|
||||
}
|
||||
}
|
||||
|
||||
return { ok: true, value: genericArgs };
|
||||
}
|
||||
|
||||
private reduceToSignificant(a: VType, b: VType): { a: VType; b: VType } {
|
||||
if (a.type !== b.type) {
|
||||
return { a, b };
|
||||
}
|
||||
if (a.type === "array" && b.type === "array") {
|
||||
return this.reduceToSignificant(a.subject, b.subject);
|
||||
}
|
||||
if (a.type === "generic" && b.type === "generic") {
|
||||
return { a, b };
|
||||
}
|
||||
throw new Error("idk what to do here");
|
||||
}
|
||||
|
||||
private vtypeContainsGeneric(vtype: VType): boolean {
|
||||
switch (vtype.type) {
|
||||
case "error":
|
||||
case "string":
|
||||
case "unknown":
|
||||
case "null":
|
||||
case "int":
|
||||
case "bool":
|
||||
return false;
|
||||
case "ref":
|
||||
case "ref_mut":
|
||||
case "ptr":
|
||||
case "ptr_mut":
|
||||
return this.vtypeContainsGeneric(vtype.subject);
|
||||
case "array":
|
||||
return this.vtypeContainsGeneric(vtype.subject);
|
||||
case "struct":
|
||||
return vtype.fields.some((field) =>
|
||||
this.vtypeContainsGeneric(field.vtype)
|
||||
);
|
||||
case "fn":
|
||||
throw new Error("not implemented");
|
||||
case "generic":
|
||||
return true;
|
||||
case "generic_spec":
|
||||
throw new Error("listen kid, grrrrrrrr");
|
||||
}
|
||||
}
|
||||
|
||||
private checkCallExprExplicitGenericsTail(
|
||||
expr: Expr,
|
||||
subject: VType,
|
||||
): VType {
|
||||
if (
|
||||
expr.kind.type !== "call" || subject.type !== "generic_spec" ||
|
||||
subject.subject.type !== "fn"
|
||||
) {
|
||||
throw new Error();
|
||||
}
|
||||
const pos = expr.pos;
|
||||
const inner = subject.subject;
|
||||
const params = inner.params;
|
||||
const args = expr.kind.args.map((arg) => this.checkExpr(arg));
|
||||
if (args.length !== params.length) {
|
||||
this.report(
|
||||
`expected ${params.length} arguments` +
|
||||
`, got ${args.length}`,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
for (let i = 0; i < args.length; ++i) {
|
||||
const vtypeCompatible = vtypesEqual(
|
||||
args[i],
|
||||
params[i].vtype,
|
||||
subject.genericArgs,
|
||||
);
|
||||
if (!vtypeCompatible) {
|
||||
this.report(
|
||||
`incorrect argument ${i} '${inner.params[i].ident}'` +
|
||||
`, expected ${
|
||||
vtypeToString(
|
||||
extractGenericType(
|
||||
params[i].vtype,
|
||||
subject.genericArgs,
|
||||
),
|
||||
)
|
||||
}` +
|
||||
`, got ${vtypeToString(args[i])}`,
|
||||
pos,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
expr.kind.genericArgs = subject.genericArgs;
|
||||
return this.concretizeVType(
|
||||
subject.subject.returnType,
|
||||
subject.genericArgs,
|
||||
);
|
||||
}
|
||||
|
||||
private concretizeVType(
|
||||
vtype: VType,
|
||||
generics: GenericArgsMap,
|
||||
): VType {
|
||||
switch (vtype.type) {
|
||||
case "error":
|
||||
case "unknown":
|
||||
case "string":
|
||||
case "null":
|
||||
case "int":
|
||||
case "bool":
|
||||
return vtype;
|
||||
case "ref":
|
||||
case "ref_mut":
|
||||
case "ptr":
|
||||
case "ptr_mut":
|
||||
case "array":
|
||||
return {
|
||||
type: vtype.type,
|
||||
subject: this.concretizeVType(vtype.subject, generics),
|
||||
};
|
||||
case "struct":
|
||||
return {
|
||||
type: "struct",
|
||||
fields: vtype.fields.map((field) => ({
|
||||
...field,
|
||||
vtype: this.concretizeVType(field.vtype, generics),
|
||||
})),
|
||||
};
|
||||
case "fn":
|
||||
throw new Error("not implemented");
|
||||
case "generic":
|
||||
return generics[vtype.param.id];
|
||||
case "generic_spec":
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
public checkPathExpr(expr: Expr): VType {
|
||||
if (expr.kind.type !== "path") {
|
||||
throw new Error();
|
||||
}
|
||||
throw new Error("should already be resolved");
|
||||
}
|
||||
|
||||
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 args = expr.kind.etypeArgs;
|
||||
if (args.length !== subject.genericParams.length) {
|
||||
this.report(
|
||||
`incorrect number of arguments` +
|
||||
`, expected ${subject.params.length}`,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
const genericArgs: GenericArgsMap = {};
|
||||
for (let i = 0; i < args.length; ++i) {
|
||||
const etype = this.checkEType(args[i]);
|
||||
genericArgs[subject.genericParams[i].id] = etype;
|
||||
}
|
||||
return {
|
||||
type: "generic_spec",
|
||||
subject,
|
||||
genericArgs,
|
||||
};
|
||||
}
|
||||
|
||||
public checkUnaryExpr(expr: Expr): VType {
|
||||
if (expr.kind.type !== "unary") {
|
||||
throw new Error();
|
||||
}
|
||||
const pos = expr.pos;
|
||||
const subject = this.checkExpr(expr.kind.subject);
|
||||
if (subject.type === "error") return subject;
|
||||
for (const operation of simpleUnaryOperations) {
|
||||
if (operation.unaryType !== expr.kind.unaryType) {
|
||||
continue;
|
||||
@ -437,7 +939,9 @@ export class Checker {
|
||||
}
|
||||
const pos = expr.pos;
|
||||
const left = this.checkExpr(expr.kind.left);
|
||||
if (left.type === "error") return left;
|
||||
const right = this.checkExpr(expr.kind.right);
|
||||
if (right.type === "error") return right;
|
||||
for (const operation of simpleBinaryOperations) {
|
||||
if (operation.binaryType !== expr.kind.binaryType) {
|
||||
continue;
|
||||
@ -466,10 +970,13 @@ export class Checker {
|
||||
}
|
||||
const pos = expr.pos;
|
||||
const cond = this.checkExpr(expr.kind.cond);
|
||||
if (cond.type === "error") return cond;
|
||||
const truthy = this.checkExpr(expr.kind.truthy);
|
||||
if (truthy.type === "error") return truthy;
|
||||
const falsy = expr.kind.falsy
|
||||
? this.checkExpr(expr.kind.falsy)
|
||||
: undefined;
|
||||
if (falsy?.type === "error") return falsy;
|
||||
if (cond.type !== "bool") {
|
||||
this.report(
|
||||
`if condition should be 'bool', got '${vtypeToString(cond)}'`,
|
||||
@ -545,7 +1052,7 @@ export class Checker {
|
||||
if (expr.kind.type !== "block") {
|
||||
throw new Error();
|
||||
}
|
||||
this.checkFnHeaders(expr.kind.stmts);
|
||||
this.scout(expr.kind.stmts);
|
||||
for (const stmt of expr.kind.stmts) {
|
||||
this.checkStmt(stmt);
|
||||
}
|
||||
@ -556,25 +1063,54 @@ 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" };
|
||||
}
|
||||
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 === "type_alias") {
|
||||
return etype.kind.sym.param.vtype!;
|
||||
}
|
||||
if (etype.kind.sym.type === "generic") {
|
||||
const { id: globalId, ident } = etype.kind.sym.genericParam;
|
||||
if (!this.globalIdToGenericParamMap.has(globalId)) {
|
||||
throw new Error();
|
||||
}
|
||||
const { id } = this.globalIdToGenericParamMap.get(globalId)!;
|
||||
return { type: "generic", param: { id, ident } };
|
||||
}
|
||||
this.report(`sym type '${etype.kind.sym.type}' used as type`, pos);
|
||||
return { type: "error" };
|
||||
}
|
||||
if (etype.kind.type === "ref") {
|
||||
const subject = this.checkEType(etype.kind.subject);
|
||||
return { type: "ref", subject };
|
||||
}
|
||||
if (etype.kind.type === "ref_mut") {
|
||||
const subject = this.checkEType(etype.kind.subject);
|
||||
return { type: "ref", subject };
|
||||
}
|
||||
if (etype.kind.type === "ptr") {
|
||||
const subject = this.checkEType(etype.kind.subject);
|
||||
return { type: "ptr", subject };
|
||||
}
|
||||
if (etype.kind.type === "ptr_mut") {
|
||||
const subject = this.checkEType(etype.kind.subject);
|
||||
return { type: "ptr_mut", subject };
|
||||
}
|
||||
if (etype.kind.type === "array") {
|
||||
const inner = this.checkEType(etype.kind.inner);
|
||||
return { type: "array", inner };
|
||||
const subject = this.checkEType(etype.kind.subject);
|
||||
return { type: "array", subject };
|
||||
}
|
||||
if (etype.kind.type === "struct") {
|
||||
const noTypeTest = etype.kind.fields.reduce(
|
||||
@ -608,13 +1144,19 @@ export class Checker {
|
||||
this.report(`field ${declaredTwiceTest[2]} defined twice`, pos);
|
||||
return { type: "error" };
|
||||
}
|
||||
const fields = etype.kind.fields.map((param): VTypeParam => ({
|
||||
ident: param.ident,
|
||||
vtype: this.checkEType(param.etype!),
|
||||
}));
|
||||
const fields = etype.kind.fields
|
||||
.map((param): VTypeParam => ({
|
||||
ident: param.ident,
|
||||
mut: true,
|
||||
vtype: this.checkEType(param.etype!),
|
||||
}));
|
||||
return { type: "struct", fields };
|
||||
}
|
||||
throw new Error(`unknown explicit type ${etype.kind.type}`);
|
||||
if (etype.kind.type === "type_of") {
|
||||
const exprVType = this.checkExpr(etype.kind.expr);
|
||||
return exprVType;
|
||||
}
|
||||
throw new Error(`unknown explicit type '${etype.kind.type}'`);
|
||||
}
|
||||
|
||||
private report(msg: string, pos: Pos) {
|
||||
|
@ -1,17 +1,28 @@
|
||||
import { AstCreator } from "./ast.ts";
|
||||
import { AstCreator, Mod, Stmt } from "./ast.ts";
|
||||
import { Checker } from "./checker.ts";
|
||||
import { CompoundAssignDesugarer } from "./desugar/compound_assign.ts";
|
||||
import { StructLiteralDesugarer } from "./desugar/struct_literal.ts";
|
||||
import { SpecialLoopDesugarer } from "./desugar/special_loop.ts";
|
||||
import { Reporter } from "./info.ts";
|
||||
import { Lexer } from "./lexer.ts";
|
||||
import { Monomorphizer } from "./mono.ts";
|
||||
import { FnNamesMap, Lowerer } from "./lowerer.ts";
|
||||
import { Parser } from "./parser.ts";
|
||||
import { Resolver } from "./resolver.ts";
|
||||
import { AstVisitor, VisitRes, visitStmts } from "./ast_visitor.ts";
|
||||
import { Pos } from "./token.ts";
|
||||
import { ArrayLiteralDesugarer } from "./desugar/array_literal.ts";
|
||||
import { mirOpCount, printMir } from "./middle/mir.ts";
|
||||
|
||||
export type CompiledFile = {
|
||||
filepath: string;
|
||||
program: number[];
|
||||
};
|
||||
import * as path from "jsr:@std/path";
|
||||
import { lowerAst } from "./middle/lower_ast.ts";
|
||||
import { eliminateUnusedLocals } from "./middle/elim_unused_local.ts";
|
||||
import {
|
||||
eliminateOnlyChildsBlocks,
|
||||
eliminateUnreachableBlocks,
|
||||
} from "./middle/elim_blocks.ts";
|
||||
import { checkBorrows } from "./middle/borrow_checker.ts";
|
||||
import { makeMoveCopyExplicit } from "./middle/explicit_move_copy.ts";
|
||||
|
||||
export type CompileResult = {
|
||||
program: number[];
|
||||
@ -20,19 +31,22 @@ export type CompileResult = {
|
||||
|
||||
export class Compiler {
|
||||
private astCreator = new AstCreator();
|
||||
private reporter = new Reporter();
|
||||
private reporter;
|
||||
|
||||
public constructor(private startFilePath: string) {}
|
||||
public constructor(private startFilePath: string) {
|
||||
this.reporter = new Reporter(this.startFilePath);
|
||||
}
|
||||
|
||||
public async compile(): Promise<CompileResult> {
|
||||
const text = await Deno.readTextFile(this.startFilePath);
|
||||
|
||||
const lexer = new Lexer(text, this.reporter);
|
||||
|
||||
const parser = new Parser(lexer, this.astCreator, this.reporter);
|
||||
const ast = parser.parse();
|
||||
const { ast } = new ModTree(
|
||||
this.startFilePath,
|
||||
this.astCreator,
|
||||
this.reporter,
|
||||
).resolve();
|
||||
|
||||
new SpecialLoopDesugarer(this.astCreator).desugar(ast);
|
||||
new ArrayLiteralDesugarer(this.astCreator).desugar(ast);
|
||||
new StructLiteralDesugarer(this.astCreator).desugar(ast);
|
||||
|
||||
new Resolver(this.reporter).resolve(ast);
|
||||
|
||||
@ -40,16 +54,130 @@ export class Compiler {
|
||||
|
||||
new Checker(this.reporter).check(ast);
|
||||
|
||||
//const mir = lowerAst(ast);
|
||||
//
|
||||
//console.log("Before optimizations:");
|
||||
//printMir(mir);
|
||||
|
||||
//const mirHistory = [mirOpCount(mir)];
|
||||
//for (let i = 0; i < 1; ++i) {
|
||||
// eliminateUnusedLocals(mir, this.reporter, mirHistory.length === 1);
|
||||
// eliminateOnlyChildsBlocks(mir);
|
||||
// eliminateUnreachableBlocks(mir);
|
||||
// eliminateTransientVals(mir);
|
||||
//
|
||||
// const opCount = mirOpCount(mir);
|
||||
// const histOccurence = mirHistory
|
||||
// .filter((v) => v === opCount).length;
|
||||
// if (histOccurence >= 2) {
|
||||
// break;
|
||||
// }
|
||||
// mirHistory.push(opCount);
|
||||
//}
|
||||
//
|
||||
//console.log("After optimizations:");
|
||||
//printMir(mir);
|
||||
|
||||
if (this.reporter.errorOccured()) {
|
||||
console.error("Errors occurred, stopping compilation.");
|
||||
Deno.exit(1);
|
||||
}
|
||||
|
||||
const lowerer = new Lowerer(lexer.currentPos());
|
||||
lowerer.lower(ast);
|
||||
// lowerer.printProgram();
|
||||
const { program, fnNames } = lowerer.finish();
|
||||
const mir = lowerAst(ast);
|
||||
|
||||
return { program, fnNames };
|
||||
makeMoveCopyExplicit(mir);
|
||||
checkBorrows(mir, this.reporter);
|
||||
|
||||
printMir(mir);
|
||||
|
||||
//const { monoFns, callMap } = new Monomorphizer(ast).monomorphize();
|
||||
//
|
||||
//const lastPos = await lastPosInTextFile(this.startFilePath);
|
||||
//
|
||||
//const lowerer = new Lowerer(monoFns, callMap, lastPos);
|
||||
//const { program, fnNames } = lowerer.lower();
|
||||
////lowerer.printProgram();
|
||||
//
|
||||
//return { program, fnNames };
|
||||
return { program: [], fnNames: {} };
|
||||
}
|
||||
}
|
||||
|
||||
export class ModTree implements AstVisitor<[string]> {
|
||||
constructor(
|
||||
private entryFilePath: string,
|
||||
private astCreator: AstCreator,
|
||||
private reporter: Reporter,
|
||||
) {}
|
||||
|
||||
public resolve(): Mod {
|
||||
const entryAst = this.parseFile(this.entryFilePath);
|
||||
|
||||
visitStmts(entryAst, this, this.entryFilePath);
|
||||
|
||||
return { filePath: this.entryFilePath, ast: entryAst };
|
||||
}
|
||||
|
||||
private parseFile(filePath: string): Stmt[] {
|
||||
const text = Deno.readTextFileSync(filePath);
|
||||
|
||||
const lexer = new Lexer(text, this.reporter);
|
||||
|
||||
const parser = new Parser(lexer, this.astCreator, this.reporter);
|
||||
const ast = parser.parse();
|
||||
|
||||
return ast;
|
||||
}
|
||||
|
||||
visitModBlockStmt(stmt: Stmt, filePath: string): VisitRes {
|
||||
if (stmt.kind.type !== "mod_block") {
|
||||
throw new Error();
|
||||
}
|
||||
const { ident, stmts: ast } = stmt.kind;
|
||||
stmt.kind = {
|
||||
type: "mod",
|
||||
ident,
|
||||
mod: { filePath, ast },
|
||||
};
|
||||
visitStmts(ast, this, filePath);
|
||||
return "stop";
|
||||
}
|
||||
|
||||
visitModFileStmt(stmt: Stmt, filePath: string): VisitRes {
|
||||
if (stmt.kind.type !== "mod_file") {
|
||||
throw new Error();
|
||||
}
|
||||
const { ident, filePath: modFilePath } = stmt.kind;
|
||||
const ast = this.parseFile(
|
||||
ident === "std"
|
||||
? path.join(
|
||||
path.dirname(path.fromFileUrl(Deno.mainModule)),
|
||||
"../std/lib.slg",
|
||||
)
|
||||
: path.join(path.dirname(filePath), modFilePath),
|
||||
);
|
||||
stmt.kind = { type: "mod", ident, mod: { filePath, ast } };
|
||||
visitStmts(ast, this, filePath);
|
||||
return "stop";
|
||||
}
|
||||
}
|
||||
|
||||
async function lastPosInTextFile(filePath: string): Promise<Pos> {
|
||||
const text = await Deno.readTextFile(filePath);
|
||||
|
||||
let index = 0;
|
||||
let line = 1;
|
||||
let col = 1;
|
||||
|
||||
while (index < text.length) {
|
||||
if (text[index] == "\n") {
|
||||
line += 1;
|
||||
col = 1;
|
||||
} else {
|
||||
col += 1;
|
||||
}
|
||||
index += 1;
|
||||
}
|
||||
|
||||
return { index, line, col };
|
||||
}
|
||||
|
@ -1,8 +1,14 @@
|
||||
{
|
||||
"version": "4",
|
||||
"specifiers": {
|
||||
"jsr:@std/path@*": "1.0.8",
|
||||
"npm:@types/node@*": "22.5.4"
|
||||
},
|
||||
"jsr": {
|
||||
"@std/path@1.0.8": {
|
||||
"integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be"
|
||||
}
|
||||
},
|
||||
"npm": {
|
||||
"@types/node@22.5.4": {
|
||||
"integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
|
||||
|
94
compiler/desugar/array_literal.ts
Normal file
94
compiler/desugar/array_literal.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import {
|
||||
AstCreator,
|
||||
ETypeKind,
|
||||
Expr,
|
||||
ExprKind,
|
||||
Stmt,
|
||||
StmtKind,
|
||||
} from "../ast.ts";
|
||||
import { AstVisitor, visitExpr, VisitRes, visitStmts } from "../ast_visitor.ts";
|
||||
import { Pos } from "../token.ts";
|
||||
|
||||
export class ArrayLiteralDesugarer implements AstVisitor {
|
||||
public constructor(
|
||||
private astCreator: AstCreator,
|
||||
) {}
|
||||
|
||||
public desugar(stmts: Stmt[]) {
|
||||
visitStmts(stmts, this);
|
||||
}
|
||||
|
||||
visitArrayExpr(expr: Expr): VisitRes {
|
||||
if (expr.kind.type !== "array") {
|
||||
throw new Error();
|
||||
}
|
||||
const npos: Pos = { index: 0, line: 1, col: 1 };
|
||||
const Expr = (kind: ExprKind, pos = npos) =>
|
||||
this.astCreator.expr(kind, pos);
|
||||
const Stmt = (kind: StmtKind, pos = npos) =>
|
||||
this.astCreator.stmt(kind, pos);
|
||||
const EType = (kind: ETypeKind, pos = npos) =>
|
||||
this.astCreator.etype(kind, pos);
|
||||
|
||||
const std = (ident: string): Expr =>
|
||||
Expr({
|
||||
type: "path",
|
||||
subject: Expr({
|
||||
type: "ident",
|
||||
ident: "std",
|
||||
}),
|
||||
ident,
|
||||
});
|
||||
|
||||
if (expr.kind.exprs.length < 1) {
|
||||
throw new Error("");
|
||||
}
|
||||
|
||||
expr.kind = {
|
||||
type: "block",
|
||||
stmts: [
|
||||
Stmt({
|
||||
type: "let",
|
||||
param: this.astCreator.param({
|
||||
ident: "::value",
|
||||
mut: true,
|
||||
pos: npos,
|
||||
}),
|
||||
value: Expr({
|
||||
type: "call",
|
||||
subject: Expr({
|
||||
type: "etype_args",
|
||||
subject: std("array_new"),
|
||||
etypeArgs: [
|
||||
EType({
|
||||
type: "type_of",
|
||||
expr: expr.kind.exprs[0],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
args: [],
|
||||
}),
|
||||
}),
|
||||
...expr.kind.exprs
|
||||
.map((expr) =>
|
||||
Stmt({
|
||||
type: "expr",
|
||||
expr: Expr({
|
||||
type: "call",
|
||||
subject: std("array_push"),
|
||||
args: [
|
||||
Expr({ type: "ident", ident: "::value" }),
|
||||
expr,
|
||||
],
|
||||
}),
|
||||
})
|
||||
),
|
||||
],
|
||||
expr: Expr({ type: "ident", ident: "::value" }),
|
||||
};
|
||||
|
||||
visitExpr(expr, this);
|
||||
|
||||
return "stop";
|
||||
}
|
||||
}
|
@ -70,29 +70,45 @@ export class SpecialLoopDesugarer implements AstVisitor {
|
||||
stmts: [
|
||||
Stmt({
|
||||
type: "let",
|
||||
param: { ident: "::values", pos: npos },
|
||||
param: this.astCreator.param({
|
||||
ident: "::values",
|
||||
mut: true,
|
||||
pos: npos,
|
||||
}),
|
||||
value: expr.kind.value,
|
||||
}),
|
||||
Stmt({
|
||||
type: "let",
|
||||
param: { ident: "::length", pos: npos },
|
||||
param: this.astCreator.param({
|
||||
ident: "::length",
|
||||
mut: false,
|
||||
pos: npos,
|
||||
}),
|
||||
value: Expr({
|
||||
type: "call",
|
||||
subject: Expr({
|
||||
type: "ident",
|
||||
value: "int_array_length",
|
||||
type: "path",
|
||||
subject: Expr({
|
||||
type: "ident",
|
||||
ident: "std",
|
||||
}),
|
||||
ident: "array_length",
|
||||
}),
|
||||
args: [
|
||||
Expr({
|
||||
type: "ident",
|
||||
value: "::values",
|
||||
ident: "::values",
|
||||
}),
|
||||
],
|
||||
}),
|
||||
}),
|
||||
Stmt({
|
||||
type: "let",
|
||||
param: { ident: "::index", pos: npos },
|
||||
param: this.astCreator.param({
|
||||
ident: "::index",
|
||||
mut: true,
|
||||
pos: npos,
|
||||
}),
|
||||
value: Expr({ type: "int", value: 0 }),
|
||||
}, expr.pos),
|
||||
Stmt({
|
||||
@ -114,11 +130,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 +155,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 +172,7 @@ export class SpecialLoopDesugarer implements AstVisitor {
|
||||
assignType: "+=",
|
||||
subject: Expr({
|
||||
type: "ident",
|
||||
value: "::index",
|
||||
ident: "::index",
|
||||
}),
|
||||
value: Expr({
|
||||
type: "int",
|
||||
|
101
compiler/desugar/struct_literal.ts
Normal file
101
compiler/desugar/struct_literal.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import {
|
||||
AstCreator,
|
||||
ETypeKind,
|
||||
Expr,
|
||||
ExprKind,
|
||||
Stmt,
|
||||
StmtKind,
|
||||
} from "../ast.ts";
|
||||
import {
|
||||
AstVisitor,
|
||||
visitField,
|
||||
VisitRes,
|
||||
visitStmts,
|
||||
} from "../ast_visitor.ts";
|
||||
import { Pos } from "../token.ts";
|
||||
|
||||
export class StructLiteralDesugarer implements AstVisitor {
|
||||
public constructor(
|
||||
private astCreator: AstCreator,
|
||||
) {}
|
||||
|
||||
public desugar(stmts: Stmt[]) {
|
||||
visitStmts(stmts, this);
|
||||
}
|
||||
|
||||
visitStructExpr(expr: Expr): VisitRes {
|
||||
if (expr.kind.type !== "struct") {
|
||||
throw new Error();
|
||||
}
|
||||
const npos: Pos = { index: 0, line: 1, col: 1 };
|
||||
const Expr = (kind: ExprKind, pos = npos) =>
|
||||
this.astCreator.expr(kind, pos);
|
||||
const Stmt = (kind: StmtKind, pos = npos) =>
|
||||
this.astCreator.stmt(kind, pos);
|
||||
const EType = (kind: ETypeKind, pos = npos) =>
|
||||
this.astCreator.etype(kind, pos);
|
||||
|
||||
const std = (ident: string): Expr =>
|
||||
Expr({
|
||||
type: "path",
|
||||
subject: Expr({
|
||||
type: "ident",
|
||||
ident: "std",
|
||||
}),
|
||||
ident,
|
||||
});
|
||||
|
||||
const fields = expr.kind.fields;
|
||||
|
||||
expr.kind = {
|
||||
type: "block",
|
||||
stmts: [
|
||||
Stmt({
|
||||
type: "let",
|
||||
param: this.astCreator.param({
|
||||
ident: "::value",
|
||||
mut: true,
|
||||
pos: npos,
|
||||
}),
|
||||
value: Expr({
|
||||
type: "call",
|
||||
subject: Expr({
|
||||
type: "etype_args",
|
||||
subject: std("struct_new"),
|
||||
etypeArgs: [
|
||||
EType({
|
||||
type: "type_of",
|
||||
expr: Expr({ ...expr.kind }),
|
||||
}),
|
||||
],
|
||||
}),
|
||||
args: [],
|
||||
}),
|
||||
}),
|
||||
...expr.kind.fields
|
||||
.map((field) =>
|
||||
Stmt({
|
||||
type: "assign",
|
||||
assignType: "=",
|
||||
subject: Expr({
|
||||
type: "field",
|
||||
subject: Expr({
|
||||
type: "ident",
|
||||
ident: "::value",
|
||||
}),
|
||||
ident: field.ident,
|
||||
}),
|
||||
value: field.expr,
|
||||
})
|
||||
),
|
||||
],
|
||||
expr: Expr({ type: "ident", ident: "::value" }),
|
||||
};
|
||||
|
||||
for (const field of fields) {
|
||||
visitField(field, this);
|
||||
}
|
||||
|
||||
return "stop";
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { Pos } from "./token.ts";
|
||||
|
||||
export type Report = {
|
||||
type: "error" | "note";
|
||||
type: "error" | "warning" | "note";
|
||||
reporter: string;
|
||||
pos?: Pos;
|
||||
msg: string;
|
||||
@ -11,16 +11,27 @@ export class Reporter {
|
||||
private reports: Report[] = [];
|
||||
private errorSet = false;
|
||||
|
||||
public constructor(private filePath: string) {}
|
||||
|
||||
public setFilePath(filePath: string) {
|
||||
this.filePath = filePath;
|
||||
}
|
||||
|
||||
public reportError(report: Omit<Report, "type">) {
|
||||
this.reports.push({ ...report, type: "error" });
|
||||
this.printReport({ ...report, type: "error" });
|
||||
this.errorSet = true;
|
||||
}
|
||||
|
||||
public reportWarning(report: Omit<Report, "type">) {
|
||||
this.reports.push({ ...report, type: "warning" });
|
||||
this.printReport({ ...report, type: "warning" });
|
||||
}
|
||||
|
||||
private printReport({ reporter, type, pos, msg }: Report) {
|
||||
console.error(
|
||||
`${reporter} ${type}: ${msg}${
|
||||
pos ? ` at ${pos.line}:${pos.col}` : ""
|
||||
pos ? `\n at ${this.filePath}:${pos.line}:${pos.col}` : ""
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
@ -27,24 +27,31 @@ export class Lexer {
|
||||
this.step();
|
||||
}
|
||||
const keywords = [
|
||||
"false",
|
||||
"true",
|
||||
"null",
|
||||
"int",
|
||||
"bool",
|
||||
"string",
|
||||
"break",
|
||||
"return",
|
||||
"let",
|
||||
"mut",
|
||||
"fn",
|
||||
"loop",
|
||||
"if",
|
||||
"else",
|
||||
"struct",
|
||||
"import",
|
||||
"false",
|
||||
"true",
|
||||
"null",
|
||||
"or",
|
||||
"and",
|
||||
"not",
|
||||
"while",
|
||||
"for",
|
||||
"in",
|
||||
"mod",
|
||||
"pub",
|
||||
"use",
|
||||
"type_alias",
|
||||
];
|
||||
if (keywords.includes(value)) {
|
||||
return this.token(value, pos);
|
||||
@ -70,6 +77,32 @@ export class Lexer {
|
||||
return { ...this.token("int", pos), intValue: 0 };
|
||||
}
|
||||
|
||||
if (this.test("'")) {
|
||||
this.step();
|
||||
let value: string;
|
||||
if (this.test("\\")) {
|
||||
this.step();
|
||||
if (this.done()) {
|
||||
this.report("malformed character literal", pos);
|
||||
return this.token("error", pos);
|
||||
}
|
||||
value = {
|
||||
n: "\n",
|
||||
t: "\t",
|
||||
"0": "\0",
|
||||
}[this.current()] ?? this.current();
|
||||
} else {
|
||||
value = this.current();
|
||||
}
|
||||
this.step();
|
||||
if (this.done() || !this.test("'") || value.length === 0) {
|
||||
this.report("malformed character literal", pos);
|
||||
return this.token("error", pos);
|
||||
}
|
||||
this.step();
|
||||
return { ...this.token("int", pos), intValue: value.charCodeAt(0) };
|
||||
}
|
||||
|
||||
if (this.test('"')) {
|
||||
this.step();
|
||||
let value = "";
|
||||
@ -96,7 +129,7 @@ export class Lexer {
|
||||
this.step();
|
||||
return { ...this.token("string", pos), stringValue: value };
|
||||
}
|
||||
if (this.test(/[\+\{\};=\-\*\(\)\.,:;\[\]><!0#]/)) {
|
||||
if (this.test(/[\+\{\};=\-\*\(\)\.,:;\[\]><!0#&]/)) {
|
||||
const first = this.current();
|
||||
this.step();
|
||||
if (first === "=" && !this.done() && this.test("=")) {
|
||||
|
@ -1,48 +1,65 @@
|
||||
import { Builtins, Ops } from "./arch.ts";
|
||||
import { Expr, Stmt } from "./ast.ts";
|
||||
import { LocalLeaf, Locals, LocalsFnRoot } from "./lowerer_locals.ts";
|
||||
import { Assembler, Label } from "./assembler.ts";
|
||||
import { vtypeToString } from "./vtype.ts";
|
||||
import { AnnoView, Expr, Stmt } from "./ast.ts";
|
||||
import { LocalLeaf, Locals, LocalsFnRoot } from "./lowerer_locals.ts";
|
||||
import { MonoCallNameGenMap, MonoFn, MonoFnsMap } from "./mono.ts";
|
||||
import { Pos } from "./token.ts";
|
||||
import { vtypeToString } from "./vtype.ts";
|
||||
|
||||
export type FnNamesMap = { [pc: number]: string };
|
||||
|
||||
export class Lowerer {
|
||||
private program = Assembler.newRoot();
|
||||
private locals: Locals = new LocalsFnRoot();
|
||||
private fnStmtIdLabelMap: { [stmtId: number]: string } = {};
|
||||
private fnLabelNameMap: { [name: string]: string } = {};
|
||||
private returnStack: Label[] = [];
|
||||
private breakStack: Label[] = [];
|
||||
|
||||
public constructor(private lastPos: Pos) {}
|
||||
public constructor(
|
||||
private monoFns: MonoFnsMap,
|
||||
private callMap: MonoCallNameGenMap,
|
||||
private lastPos: Pos,
|
||||
) {}
|
||||
|
||||
public lower(stmts: Stmt[]) {
|
||||
public lower(): { program: number[]; fnNames: FnNamesMap } {
|
||||
const fnLabelNameMap: FnLabelMap = {};
|
||||
for (const nameGen in this.monoFns) {
|
||||
fnLabelNameMap[nameGen] = nameGen;
|
||||
}
|
||||
|
||||
this.addPrelimiary();
|
||||
|
||||
for (const fn of Object.values(this.monoFns)) {
|
||||
const fnProgram = new MonoFnLowerer(
|
||||
fn,
|
||||
this.program.fork(),
|
||||
this.callMap,
|
||||
).lower();
|
||||
this.program.join(fnProgram);
|
||||
}
|
||||
|
||||
this.addConcluding();
|
||||
|
||||
const { program, locs } = this.program.assemble();
|
||||
const fnNames: FnNamesMap = {};
|
||||
for (const label in locs) {
|
||||
if (label in fnLabelNameMap) {
|
||||
fnNames[locs[label]] = fnLabelNameMap[label];
|
||||
}
|
||||
}
|
||||
return { program, fnNames };
|
||||
}
|
||||
|
||||
private addPrelimiary() {
|
||||
this.addClearingSourceMap();
|
||||
this.program.add(Ops.PushPtr, { label: "main" });
|
||||
this.program.add(Ops.Call, 0);
|
||||
this.program.add(Ops.PushPtr, { label: "_exit" });
|
||||
this.program.add(Ops.Jump);
|
||||
this.scoutFnHeaders(stmts);
|
||||
for (const stmt of stmts) {
|
||||
this.lowerStaticStmt(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
private addConcluding() {
|
||||
this.program.setLabel({ label: "_exit" });
|
||||
this.addSourceMap(this.lastPos);
|
||||
this.program.add(Ops.Pop);
|
||||
}
|
||||
|
||||
public finish(): { program: number[]; fnNames: FnNamesMap } {
|
||||
const { program, locs } = this.program.assemble();
|
||||
const fnNames: FnNamesMap = {};
|
||||
for (const label in locs) {
|
||||
if (label in this.fnLabelNameMap) {
|
||||
fnNames[locs[label]] = this.fnLabelNameMap[label];
|
||||
}
|
||||
}
|
||||
return { program, fnNames };
|
||||
}
|
||||
|
||||
private addSourceMap({ index, line, col }: Pos) {
|
||||
this.program.add(Ops.SourceMap, index, line, col);
|
||||
}
|
||||
@ -51,30 +68,84 @@ export class Lowerer {
|
||||
this.program.add(Ops.SourceMap, 0, 1, 1);
|
||||
}
|
||||
|
||||
private scoutFnHeaders(stmts: Stmt[]) {
|
||||
for (const stmt of stmts) {
|
||||
if (stmt.kind.type !== "fn") {
|
||||
continue;
|
||||
}
|
||||
const label = stmt.kind.ident === "main"
|
||||
? "main"
|
||||
: `${stmt.kind.ident}_${stmt.id}`;
|
||||
this.fnStmtIdLabelMap[stmt.id] = label;
|
||||
}
|
||||
public printProgram() {
|
||||
this.program.printProgram();
|
||||
}
|
||||
}
|
||||
|
||||
type FnLabelMap = { [nameGen: string]: string };
|
||||
|
||||
class MonoFnLowerer {
|
||||
private locals: Locals = new LocalsFnRoot();
|
||||
private returnStack: Label[] = [];
|
||||
private breakStack: Label[] = [];
|
||||
|
||||
public constructor(
|
||||
private fn: MonoFn,
|
||||
private program: Assembler,
|
||||
private callMap: MonoCallNameGenMap,
|
||||
) {}
|
||||
|
||||
public lower(): Assembler {
|
||||
this.lowerFnStmt(this.fn.stmt);
|
||||
return this.program;
|
||||
}
|
||||
|
||||
private lowerStaticStmt(stmt: Stmt) {
|
||||
switch (stmt.kind.type) {
|
||||
case "fn":
|
||||
return this.lowerFnStmt(stmt);
|
||||
case "error":
|
||||
case "break":
|
||||
case "return":
|
||||
case "let":
|
||||
case "assign":
|
||||
case "expr":
|
||||
private lowerFnStmt(stmt: Stmt) {
|
||||
if (stmt.kind.type !== "fn") {
|
||||
throw new Error();
|
||||
}
|
||||
throw new Error(`unhandled static statement '${stmt.kind.type}'`);
|
||||
const label = this.fn.nameGen;
|
||||
this.program.setLabel({ label });
|
||||
this.addSourceMap(stmt.pos);
|
||||
|
||||
const outerLocals = this.locals;
|
||||
const fnRoot = new LocalsFnRoot(outerLocals);
|
||||
const outerProgram = this.program;
|
||||
|
||||
const returnLabel = this.program.makeLabel();
|
||||
this.returnStack.push(returnLabel);
|
||||
|
||||
this.program = outerProgram.fork();
|
||||
this.locals = fnRoot;
|
||||
for (const { ident } of stmt.kind.params) {
|
||||
this.locals.allocSym(ident);
|
||||
}
|
||||
|
||||
const annos = new AnnoView(stmt.details);
|
||||
if (annos.has("builtin")) {
|
||||
const anno = annos.get("builtin");
|
||||
if (!anno) {
|
||||
throw new Error();
|
||||
}
|
||||
this.lowerFnBuiltinBody(anno.args);
|
||||
} else if (annos.has("remainder")) {
|
||||
this.program.add(Ops.Remainder);
|
||||
} else {
|
||||
this.lowerExpr(stmt.kind.body);
|
||||
}
|
||||
this.locals = outerLocals;
|
||||
|
||||
const localAmount = fnRoot.stackReserved() -
|
||||
stmt.kind.params.length;
|
||||
for (let i = 0; i < localAmount; ++i) {
|
||||
outerProgram.add(Ops.PushNull);
|
||||
}
|
||||
|
||||
this.returnStack.pop();
|
||||
this.program.setLabel(returnLabel);
|
||||
this.program.add(Ops.Return);
|
||||
|
||||
outerProgram.join(this.program);
|
||||
this.program = outerProgram;
|
||||
}
|
||||
|
||||
private addSourceMap({ index, line, col }: Pos) {
|
||||
this.program.add(Ops.SourceMap, index, line, col);
|
||||
}
|
||||
|
||||
private addClearingSourceMap() {
|
||||
this.program.add(Ops.SourceMap, 0, 1, 1);
|
||||
}
|
||||
|
||||
private lowerStmt(stmt: Stmt) {
|
||||
@ -107,7 +178,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;
|
||||
}
|
||||
@ -153,52 +224,6 @@ export class Lowerer {
|
||||
this.program.add(Ops.Jump);
|
||||
}
|
||||
|
||||
private lowerFnStmt(stmt: Stmt) {
|
||||
if (stmt.kind.type !== "fn") {
|
||||
throw new Error();
|
||||
}
|
||||
const label = stmt.kind.ident === "main"
|
||||
? "main"
|
||||
: `${stmt.kind.ident}_${stmt.id}`;
|
||||
this.program.setLabel({ label });
|
||||
this.fnLabelNameMap[label] = stmt.kind.ident;
|
||||
this.addSourceMap(stmt.pos);
|
||||
|
||||
const outerLocals = this.locals;
|
||||
const fnRoot = new LocalsFnRoot(outerLocals);
|
||||
const outerProgram = this.program;
|
||||
|
||||
const returnLabel = this.program.makeLabel();
|
||||
this.returnStack.push(returnLabel);
|
||||
|
||||
this.program = outerProgram.fork();
|
||||
this.locals = fnRoot;
|
||||
for (const { ident } of stmt.kind.params) {
|
||||
this.locals.allocSym(ident);
|
||||
}
|
||||
if (stmt.kind.anno?.ident === "builtin") {
|
||||
this.lowerFnBuiltinBody(stmt.kind.anno.values);
|
||||
} else if (stmt.kind.anno?.ident === "remainder") {
|
||||
this.program.add(Ops.Remainder);
|
||||
} else {
|
||||
this.lowerExpr(stmt.kind.body);
|
||||
}
|
||||
this.locals = outerLocals;
|
||||
|
||||
const localAmount = fnRoot.stackReserved() -
|
||||
stmt.kind.params.length;
|
||||
for (let i = 0; i < localAmount; ++i) {
|
||||
outerProgram.add(Ops.PushNull);
|
||||
}
|
||||
|
||||
this.returnStack.pop();
|
||||
this.program.setLabel(returnLabel);
|
||||
this.program.add(Ops.Return);
|
||||
|
||||
outerProgram.join(this.program);
|
||||
this.program = outerProgram;
|
||||
}
|
||||
|
||||
private lowerFnBuiltinBody(annoArgs: Expr[]) {
|
||||
if (annoArgs.length !== 1) {
|
||||
throw new Error("invalid # of arguments to builtin annotation");
|
||||
@ -209,7 +234,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];
|
||||
@ -252,11 +277,13 @@ export class Lowerer {
|
||||
case "group":
|
||||
return void this.lowerExpr(expr.kind.expr);
|
||||
case "field":
|
||||
break;
|
||||
return this.lowerFieldExpr(expr);
|
||||
case "index":
|
||||
return this.lowerIndexExpr(expr);
|
||||
case "call":
|
||||
return this.lowerCallExpr(expr);
|
||||
case "etype_args":
|
||||
return this.lowerETypeArgsExpr(expr);
|
||||
case "unary":
|
||||
return this.lowerUnaryExpr(expr);
|
||||
case "binary":
|
||||
@ -271,6 +298,20 @@ export class Lowerer {
|
||||
throw new Error(`unhandled expr '${expr.kind.type}'`);
|
||||
}
|
||||
|
||||
private lowerFieldExpr(expr: Expr) {
|
||||
if (expr.kind.type !== "field") {
|
||||
throw new Error();
|
||||
}
|
||||
this.lowerExpr(expr.kind.subject);
|
||||
this.program.add(Ops.PushString, expr.kind.ident);
|
||||
|
||||
if (expr.kind.subject.vtype?.type == "struct") {
|
||||
this.program.add(Ops.Builtin, Builtins.StructAt);
|
||||
return;
|
||||
}
|
||||
throw new Error(`unhandled field subject type '${expr.kind.subject}'`);
|
||||
}
|
||||
|
||||
private lowerIndexExpr(expr: Expr) {
|
||||
if (expr.kind.type !== "index") {
|
||||
throw new Error();
|
||||
@ -306,8 +347,42 @@ export class Lowerer {
|
||||
return;
|
||||
}
|
||||
if (expr.kind.sym.type === "fn") {
|
||||
const label = this.fnStmtIdLabelMap[expr.kind.sym.stmt.id];
|
||||
this.program.add(Ops.PushPtr, { label });
|
||||
// Is this smart? Well, my presumption is
|
||||
// that it isn't. The underlying problem, which
|
||||
// this solutions raison d'être is to solve, is
|
||||
// that the compiler, as it d'être's currently
|
||||
// doesn't support checking and infering generic
|
||||
// fn args all the way down to the sym. Therefore,
|
||||
// when a sym is checked in a call expr, we can't
|
||||
// really do anything useful. Instead the actual
|
||||
// function pointer pointing to the actual
|
||||
// monomorphized function is emplaced when
|
||||
// lowering the call expression itself. But what
|
||||
// should we do then, if the user decides to
|
||||
// assign a function to a local? You might ask.
|
||||
// You see, that's where the problem lies.
|
||||
// My current, very thought out solution, as
|
||||
// you can read below, is to push a null pointer,
|
||||
// for it to then be replaced later. This will
|
||||
// probably cause many hastles in the future
|
||||
// for myself in particular, when trying to
|
||||
// decipher the lowerer's output. So if you're
|
||||
// the unlucky girl, who has tried for ages to
|
||||
// decipher why a zero value is pushed and then
|
||||
// later replaced, and then you finally
|
||||
// stumbled upon this here implementation,
|
||||
// let me first say, I'm so sorry. At the time
|
||||
// of writing, I really haven't thought out
|
||||
// very well, how the generic call system should
|
||||
// work, and it's therefore a bit flaky, and the
|
||||
// implementation kinda looks like it was
|
||||
// implementated by a girl who didn't really
|
||||
// understand very well what they were
|
||||
// implementing at the time that they were
|
||||
// implementing it. Anyway, I just wanted to
|
||||
// apologize. Happy coding.
|
||||
// -Your favorite compiler girl.
|
||||
this.program.add(Ops.PushPtr, 0);
|
||||
return;
|
||||
}
|
||||
throw new Error(`unhandled sym type '${expr.kind.sym.type}'`);
|
||||
@ -430,6 +505,18 @@ export class Lowerer {
|
||||
default:
|
||||
}
|
||||
}
|
||||
if (vtype.type === "bool") {
|
||||
switch (expr.kind.binaryType) {
|
||||
case "==":
|
||||
this.program.add(Ops.And);
|
||||
return;
|
||||
case "!=":
|
||||
this.program.add(Ops.And);
|
||||
this.program.add(Ops.Not);
|
||||
return;
|
||||
default:
|
||||
}
|
||||
}
|
||||
if (vtype.type === "string") {
|
||||
if (expr.kind.binaryType === "+") {
|
||||
this.program.add(Ops.Builtin, Builtins.StringConcat);
|
||||
@ -462,9 +549,18 @@ export class Lowerer {
|
||||
this.lowerExpr(arg);
|
||||
}
|
||||
this.lowerExpr(expr.kind.subject);
|
||||
this.program.add(Ops.Pop);
|
||||
this.program.add(Ops.PushPtr, { label: this.callMap[expr.id] });
|
||||
this.program.add(Ops.Call, expr.kind.args.length);
|
||||
}
|
||||
|
||||
private lowerETypeArgsExpr(expr: Expr) {
|
||||
if (expr.kind.type !== "etype_args") {
|
||||
throw new Error();
|
||||
}
|
||||
this.lowerExpr(expr.kind.subject);
|
||||
}
|
||||
|
||||
private lowerIfExpr(expr: Expr) {
|
||||
if (expr.kind.type !== "if") {
|
||||
throw new Error();
|
||||
@ -528,7 +624,6 @@ export class Lowerer {
|
||||
}
|
||||
const outerLocals = this.locals;
|
||||
this.locals = new LocalLeaf(this.locals);
|
||||
this.scoutFnHeaders(expr.kind.stmts);
|
||||
for (const stmt of expr.kind.stmts) {
|
||||
this.addSourceMap(stmt.pos);
|
||||
this.lowerStmt(stmt);
|
||||
@ -541,8 +636,4 @@ export class Lowerer {
|
||||
}
|
||||
this.locals = outerLocals;
|
||||
}
|
||||
|
||||
public printProgram() {
|
||||
this.program.printProgram();
|
||||
}
|
||||
}
|
||||
|
240
compiler/middle/borrow_checker.ts
Normal file
240
compiler/middle/borrow_checker.ts
Normal file
@ -0,0 +1,240 @@
|
||||
import { Reporter } from "../info.ts";
|
||||
import { Pos } from "../token.ts";
|
||||
import { createCfg } from "./cfg.ts";
|
||||
import { Cfg } from "./cfg.ts";
|
||||
import { Block, BlockId, Fn, Local, LocalId, Mir, RValue } from "./mir.ts";
|
||||
|
||||
export function checkBorrows(
|
||||
mir: Mir,
|
||||
reporter: Reporter,
|
||||
) {
|
||||
for (const fn of mir.fns) {
|
||||
new BorrowCheckerFnPass(fn, reporter).pass();
|
||||
}
|
||||
}
|
||||
|
||||
class BorrowCheckerFnPass {
|
||||
private cfg: Cfg;
|
||||
|
||||
public constructor(
|
||||
private fn: Fn,
|
||||
private reporter: Reporter,
|
||||
) {
|
||||
this.cfg = createCfg(this.fn);
|
||||
}
|
||||
|
||||
public pass() {
|
||||
for (const local of this.fn.locals) {
|
||||
new LocalChecker(local, this.fn, this.cfg, this.reporter).check();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LocalChecker {
|
||||
private visitedBlocks = new Set<BlockId>();
|
||||
|
||||
private assignedTo = false;
|
||||
private moved = false;
|
||||
private borrowed = false;
|
||||
private borrowedMut = false;
|
||||
|
||||
private movedPos?: Pos;
|
||||
private borrowedPos?: Pos;
|
||||
|
||||
public constructor(
|
||||
private local: Local,
|
||||
private fn: Fn,
|
||||
private cfg: Cfg,
|
||||
private reporter: Reporter,
|
||||
) {}
|
||||
|
||||
public check() {
|
||||
this.checkBlock(this.cfg.entry());
|
||||
}
|
||||
|
||||
private checkBlock(block: Block) {
|
||||
if (this.visitedBlocks.has(block.id)) {
|
||||
return;
|
||||
}
|
||||
this.visitedBlocks.add(block.id);
|
||||
for (const op of block.ops) {
|
||||
const ok = op.kind;
|
||||
switch (ok.type) {
|
||||
case "error":
|
||||
break;
|
||||
case "assign":
|
||||
this.markDst(ok.dst);
|
||||
this.markSrc(ok.src);
|
||||
break;
|
||||
case "ref":
|
||||
case "ptr":
|
||||
this.markDst(ok.dst);
|
||||
this.markBorrow(ok.src);
|
||||
break;
|
||||
case "ref_mut":
|
||||
case "ptr_mut":
|
||||
this.markDst(ok.dst);
|
||||
this.markBorrowMut(ok.src);
|
||||
break;
|
||||
case "deref":
|
||||
this.markDst(ok.dst);
|
||||
this.markSrc(ok.src);
|
||||
break;
|
||||
case "assign_deref":
|
||||
this.markSrc(ok.subject);
|
||||
this.markSrc(ok.src);
|
||||
break;
|
||||
case "field":
|
||||
this.markDst(ok.dst);
|
||||
this.markSrc(ok.subject);
|
||||
break;
|
||||
case "assign_field":
|
||||
this.markSrc(ok.subject);
|
||||
this.markSrc(ok.src);
|
||||
break;
|
||||
case "index":
|
||||
this.markDst(ok.dst);
|
||||
this.markSrc(ok.subject);
|
||||
this.markSrc(ok.index);
|
||||
break;
|
||||
case "assign_index":
|
||||
this.markSrc(ok.subject);
|
||||
this.markSrc(ok.index);
|
||||
this.markSrc(ok.src);
|
||||
break;
|
||||
case "call_val":
|
||||
this.markDst(ok.dst);
|
||||
this.markSrc(ok.subject);
|
||||
for (const arg of ok.args) {
|
||||
this.markSrc(arg);
|
||||
}
|
||||
break;
|
||||
case "binary":
|
||||
this.markDst(ok.dst);
|
||||
this.markSrc(ok.left);
|
||||
this.markSrc(ok.right);
|
||||
break;
|
||||
}
|
||||
}
|
||||
const tk = block.ter.kind;
|
||||
switch (tk.type) {
|
||||
case "error":
|
||||
break;
|
||||
case "return":
|
||||
break;
|
||||
case "jump":
|
||||
break;
|
||||
case "if":
|
||||
this.markSrc(tk.cond);
|
||||
break;
|
||||
}
|
||||
for (const child of this.cfg.children(block)) {
|
||||
this.checkBlock(child);
|
||||
}
|
||||
}
|
||||
|
||||
private markDst(localId: LocalId) {
|
||||
if (localId !== this.local.id) {
|
||||
return;
|
||||
}
|
||||
if (!this.assignedTo) {
|
||||
this.assignedTo = true;
|
||||
return;
|
||||
}
|
||||
if (!this.local.mut) {
|
||||
this.reportReassignToNonMut();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private markBorrow(localId: LocalId) {
|
||||
if (localId !== this.local.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.assignedTo) {
|
||||
this.assignedTo = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private markBorrowMut(localId: LocalId) {
|
||||
if (localId !== this.local.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.assignedTo) {
|
||||
this.assignedTo = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private markSrc(src: RValue) {
|
||||
if (src.type === "local") {
|
||||
throw new Error("should be 'copy' or 'move'");
|
||||
}
|
||||
if (
|
||||
(src.type !== "copy" && src.type !== "move") ||
|
||||
src.id !== this.local.id
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (src.type === "move") {
|
||||
if (this.moved) {
|
||||
this.reportUseMoved();
|
||||
return;
|
||||
}
|
||||
if (this.borrowed) {
|
||||
this.reportUseBorrowed();
|
||||
return;
|
||||
}
|
||||
this.moved = true;
|
||||
}
|
||||
}
|
||||
|
||||
private reportReassignToNonMut() {
|
||||
const ident = this.local.sym!.ident;
|
||||
this.reporter.reportError({
|
||||
reporter: "borrow checker",
|
||||
msg: `cannot re-assign to '${ident}' as it was not declared mutable`,
|
||||
pos: this.local.sym!.pos!,
|
||||
});
|
||||
this.reporter.addNote({
|
||||
reporter: "borrow checker",
|
||||
msg: `declared here`,
|
||||
pos: this.local.sym!.pos!,
|
||||
});
|
||||
}
|
||||
|
||||
private reportUseMoved() {
|
||||
const ident = this.local.sym!.ident;
|
||||
this.reporter.reportError({
|
||||
reporter: "borrow checker",
|
||||
msg: `cannot use '${ident}' as it has been moved`,
|
||||
pos: this.local.sym!.pos!,
|
||||
});
|
||||
if (this.movedPos) {
|
||||
this.reporter.addNote({
|
||||
reporter: "borrow checker",
|
||||
msg: `moved here`,
|
||||
pos: this.movedPos,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private reportUseBorrowed() {
|
||||
const ident = this.local.sym!.ident;
|
||||
this.reporter.reportError({
|
||||
reporter: "borrow checker",
|
||||
msg: `cannot use '${ident}' as it has been borrowed`,
|
||||
pos: this.local.sym!.pos!,
|
||||
});
|
||||
if (this.borrowedPos) {
|
||||
this.reporter.addNote({
|
||||
reporter: "borrow checker",
|
||||
msg: `borrowed here`,
|
||||
pos: this.movedPos,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
107
compiler/middle/cfg.ts
Normal file
107
compiler/middle/cfg.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import { Block, BlockId, Fn } from "./mir.ts";
|
||||
|
||||
export function createCfg(fn: Fn): Cfg {
|
||||
return new CfgBuilder(fn).build();
|
||||
}
|
||||
|
||||
export class Cfg {
|
||||
public constructor(
|
||||
private graph: Map<BlockId, CfgNode>,
|
||||
private entry_: BlockId,
|
||||
private exit: BlockId,
|
||||
) {}
|
||||
|
||||
public entry(): Block {
|
||||
return this.graph.get(this.entry_)!.block;
|
||||
}
|
||||
|
||||
public parents(block: Block): Block[] {
|
||||
return this.graph
|
||||
.get(block.id)!.parents
|
||||
.map((id) => this.graph.get(id)!.block);
|
||||
}
|
||||
|
||||
public children(block: Block): Block[] {
|
||||
return this.graph
|
||||
.get(block.id)!.children
|
||||
.map((id) => this.graph.get(id)!.block);
|
||||
}
|
||||
|
||||
public index(block: Block): number {
|
||||
return this.graph.get(block.id)!.index;
|
||||
}
|
||||
|
||||
public print() {
|
||||
for (const [id, node] of this.graph.entries()) {
|
||||
const l = <T>(v: T[]) => v.map((v) => `${v}`).join(", ");
|
||||
|
||||
console.log(`graph[${id}] = {`);
|
||||
console.log(` id: ${node.block.id},`);
|
||||
console.log(` index: ${node.index},`);
|
||||
console.log(` parents: [${l(node.parents)}],`);
|
||||
console.log(` children: [${l(node.children)}],`);
|
||||
console.log(`}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type CfgNode = {
|
||||
block: Block;
|
||||
index: number;
|
||||
parents: BlockId[];
|
||||
children: BlockId[];
|
||||
};
|
||||
|
||||
class CfgBuilder {
|
||||
private nodes: [Block, number][] = [];
|
||||
private edges: [BlockId, BlockId][] = [];
|
||||
|
||||
public constructor(private fn: Fn) {}
|
||||
|
||||
public build(): Cfg {
|
||||
for (
|
||||
const [block, index] of this.fn.blocks
|
||||
.map((v, i) => [v, i] as const)
|
||||
) {
|
||||
this.addNode(block, index);
|
||||
|
||||
const tk = block.ter.kind;
|
||||
switch (tk.type) {
|
||||
case "error":
|
||||
break;
|
||||
case "return":
|
||||
break;
|
||||
case "jump":
|
||||
this.addEdge(block.id, tk.target);
|
||||
break;
|
||||
case "if":
|
||||
this.addEdge(block.id, tk.truthy);
|
||||
this.addEdge(block.id, tk.falsy);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const graph = new Map<BlockId, CfgNode>();
|
||||
for (const [block, index] of this.nodes) {
|
||||
const parents = this.edges
|
||||
.filter(([_from, to]) => to === block.id)
|
||||
.map(([from, _to]) => from);
|
||||
|
||||
const children = this.edges
|
||||
.filter(([from, _to]) => from === block.id)
|
||||
.map(([_from, to]) => to);
|
||||
|
||||
graph.set(block.id, { block, index, parents, children });
|
||||
}
|
||||
|
||||
return new Cfg(graph, this.fn.entry, this.fn.exit);
|
||||
}
|
||||
|
||||
private addNode(block: Block, index: number) {
|
||||
this.nodes.push([block, index]);
|
||||
}
|
||||
|
||||
private addEdge(from: BlockId, to: BlockId) {
|
||||
this.edges.push([from, to]);
|
||||
}
|
||||
}
|
59
compiler/middle/elim_blocks.ts
Normal file
59
compiler/middle/elim_blocks.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { createCfg } from "./cfg.ts";
|
||||
import { Block, Mir } from "./mir.ts";
|
||||
|
||||
export function eliminateOnlyChildsBlocks(mir: Mir) {
|
||||
for (const fn of mir.fns) {
|
||||
const cfg = createCfg(fn);
|
||||
|
||||
const candidates: { parent: Block; child: Block }[] = [];
|
||||
|
||||
for (const block of fn.blocks) {
|
||||
const children = cfg.children(block);
|
||||
if (children.length !== 1) {
|
||||
continue;
|
||||
}
|
||||
if (cfg.parents(children[0]).length !== 1) {
|
||||
continue;
|
||||
}
|
||||
candidates.push({ parent: block, child: children[0] });
|
||||
}
|
||||
|
||||
const elimIndices: number[] = [];
|
||||
|
||||
for (const { parent, child } of candidates) {
|
||||
parent.ops.push(...child.ops);
|
||||
parent.ter = child.ter;
|
||||
elimIndices.push(cfg.index(child));
|
||||
}
|
||||
|
||||
for (const i of elimIndices.toReversed()) {
|
||||
fn.blocks.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function eliminateUnreachableBlocks(mir: Mir) {
|
||||
for (const fn of mir.fns) {
|
||||
const cfg = createCfg(fn);
|
||||
|
||||
const candidates: Block[] = [];
|
||||
|
||||
for (const block of fn.blocks) {
|
||||
if (block.id === fn.entry) {
|
||||
continue;
|
||||
}
|
||||
if (cfg.parents(block).length !== 0) {
|
||||
continue;
|
||||
}
|
||||
candidates.push(block);
|
||||
}
|
||||
|
||||
for (
|
||||
const i of candidates
|
||||
.map((block) => cfg.index(block))
|
||||
.toReversed()
|
||||
) {
|
||||
fn.blocks.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
81
compiler/middle/elim_unused_local.ts
Normal file
81
compiler/middle/elim_unused_local.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { FnStmtKind } from "../ast.ts";
|
||||
import { Reporter } from "../info.ts";
|
||||
import {
|
||||
Block,
|
||||
Fn,
|
||||
LocalId,
|
||||
Mir,
|
||||
RValue,
|
||||
visitBlockDsts,
|
||||
visitBlockSrcs,
|
||||
} from "./mir.ts";
|
||||
|
||||
export function eliminateUnusedLocals(
|
||||
mir: Mir,
|
||||
reporter: Reporter,
|
||||
isPassOne: boolean,
|
||||
) {
|
||||
for (const fn of mir.fns) {
|
||||
new EliminateUnusedLocalsFnPass(fn, reporter, isPassOne).pass();
|
||||
}
|
||||
}
|
||||
|
||||
class EliminateUnusedLocalsFnPass {
|
||||
private locals: LocalId[];
|
||||
|
||||
public constructor(
|
||||
private fn: Fn,
|
||||
private reporter: Reporter,
|
||||
private isPassOne: boolean,
|
||||
) {
|
||||
this.locals = this.fn.locals
|
||||
.slice(1 + (fn.stmt.kind as FnStmtKind).params.length)
|
||||
.map((local) => local.id);
|
||||
}
|
||||
|
||||
public pass() {
|
||||
for (const block of this.fn.blocks) {
|
||||
this.markLocalsInBlock(block);
|
||||
}
|
||||
for (const local of this.locals) {
|
||||
for (const block of this.fn.blocks) {
|
||||
this.eliminateLocalInBlock(block, local);
|
||||
}
|
||||
}
|
||||
for (const id of this.locals) {
|
||||
const local = this.fn.locals.find((local) => local.id === id)!;
|
||||
if (local.sym?.type === "let" && this.isPassOne) {
|
||||
this.reporter.reportWarning({
|
||||
reporter: "analysis mf'er",
|
||||
msg: `unused let symbol '${local.sym.ident}'`,
|
||||
pos: local.sym.pos,
|
||||
});
|
||||
}
|
||||
}
|
||||
this.fn.locals = this.fn.locals
|
||||
.filter((local) => !this.locals.includes(local.id));
|
||||
}
|
||||
|
||||
private eliminateLocalInBlock(block: Block, local: LocalId) {
|
||||
const elimIndices: number[] = [];
|
||||
visitBlockDsts(block, (dst, i) => {
|
||||
if (dst === local) {
|
||||
elimIndices.push(i);
|
||||
}
|
||||
});
|
||||
for (const i of elimIndices.toReversed()) {
|
||||
block.ops.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private markLocalsInBlock(block: Block) {
|
||||
visitBlockSrcs(block, (src) => this.markUsed(src));
|
||||
}
|
||||
|
||||
private markUsed(local: RValue) {
|
||||
if (local.type !== "local") {
|
||||
return;
|
||||
}
|
||||
this.locals = this.locals.filter((lid) => lid !== local.id);
|
||||
}
|
||||
}
|
58
compiler/middle/explicit_move_copy.ts
Normal file
58
compiler/middle/explicit_move_copy.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { VType } from "../vtype.ts";
|
||||
import { Fn, Local, Mir, replaceBlockSrcs, RValue } from "./mir.ts";
|
||||
|
||||
export function makeMoveCopyExplicit(mir: Mir) {
|
||||
for (const fn of mir.fns) {
|
||||
for (const local of fn.locals) {
|
||||
new LocalExpliciter(fn, local).pass();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LocalExpliciter {
|
||||
private copyable: boolean;
|
||||
|
||||
public constructor(private fn: Fn, private local: Local) {
|
||||
this.copyable = copyableIsType(local.vtype);
|
||||
}
|
||||
|
||||
public pass() {
|
||||
for (const block of this.fn.blocks) {
|
||||
replaceBlockSrcs(block, (src) => this.explicitSrc(src));
|
||||
}
|
||||
}
|
||||
|
||||
private explicitSrc(src: RValue): RValue {
|
||||
if (src.type !== "local") {
|
||||
return src;
|
||||
}
|
||||
return this.copyable
|
||||
? { type: "copy", id: src.id }
|
||||
: { type: "move", id: src.id };
|
||||
}
|
||||
}
|
||||
|
||||
function copyableIsType(vtype: VType): boolean {
|
||||
switch (vtype.type) {
|
||||
case "error":
|
||||
case "unknown":
|
||||
throw new Error();
|
||||
case "null":
|
||||
case "int":
|
||||
case "bool":
|
||||
case "string":
|
||||
case "ref":
|
||||
case "ref_mut":
|
||||
case "ptr":
|
||||
case "ptr_mut":
|
||||
return true;
|
||||
case "array":
|
||||
case "struct":
|
||||
case "fn":
|
||||
return false;
|
||||
case "generic":
|
||||
return false;
|
||||
case "generic_spec":
|
||||
throw new Error();
|
||||
}
|
||||
}
|
513
compiler/middle/lower_ast.ts
Normal file
513
compiler/middle/lower_ast.ts
Normal file
@ -0,0 +1,513 @@
|
||||
import * as Ast from "../ast.ts";
|
||||
import { AllFnsCollector } from "../mono.ts";
|
||||
import { VType, vtypesEqual } from "../vtype.ts";
|
||||
import {
|
||||
Block,
|
||||
BlockId,
|
||||
Fn,
|
||||
Local,
|
||||
LocalId,
|
||||
Mir,
|
||||
OpKind,
|
||||
RValue,
|
||||
Ter,
|
||||
TerKind,
|
||||
} from "./mir.ts";
|
||||
|
||||
export function lowerAst(ast: Ast.Stmt[]): Mir {
|
||||
return new AstLowerer(ast).lower();
|
||||
}
|
||||
|
||||
class AstLowerer {
|
||||
public constructor(private ast: Ast.Stmt[]) {}
|
||||
|
||||
public lower(): Mir {
|
||||
const fnAsts = new AllFnsCollector().collect(this.ast).values();
|
||||
const fns = fnAsts
|
||||
.map((fnAst) => new FnAstLowerer(fnAst).lower())
|
||||
.toArray();
|
||||
return { fns };
|
||||
}
|
||||
}
|
||||
|
||||
class LocalAllocator {
|
||||
private locals: Local[] = [];
|
||||
|
||||
public alloc(vtype: VType, sym?: Ast.Sym): LocalId {
|
||||
const id = this.locals.length;
|
||||
this.locals.push({ id, mut: false, vtype, sym });
|
||||
return id;
|
||||
}
|
||||
|
||||
public allocMut(vtype: VType, sym?: Ast.Sym): LocalId {
|
||||
const id = this.locals.length;
|
||||
this.locals.push({ id, mut: true, vtype, sym });
|
||||
return id;
|
||||
}
|
||||
|
||||
public finish(): Local[] {
|
||||
return this.locals;
|
||||
}
|
||||
}
|
||||
|
||||
class FnAstLowerer {
|
||||
private locals = new LocalAllocator();
|
||||
private blockIdCounter = 0;
|
||||
private currentBlockId = 0;
|
||||
private blocks = new Map<BlockId, Block>();
|
||||
|
||||
private fnParamIndexLocals = new Map<number, LocalId>();
|
||||
private letStmtIdLocals = new Map<number, LocalId>();
|
||||
|
||||
private breakStack: { local: LocalId; block: BlockId }[] = [];
|
||||
|
||||
public constructor(private ast: Ast.Stmt) {}
|
||||
|
||||
public lower(): Fn {
|
||||
const stmt = this.ast;
|
||||
if (stmt.kind.type !== "fn") {
|
||||
throw new Error();
|
||||
}
|
||||
const vtype = stmt.kind.vtype;
|
||||
if (vtype?.type !== "fn") {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
const rLoc = this.locals.alloc(vtype.returnType);
|
||||
for (const param of stmt.kind.params) {
|
||||
const id = this.locals.allocMut(param.vtype!);
|
||||
this.fnParamIndexLocals.set(param.index!, id);
|
||||
}
|
||||
|
||||
const entry = this.pushBlock();
|
||||
const rVal = this.lowerBlockExpr(stmt.kind.body);
|
||||
this.addOp({ type: "assign", dst: rLoc, src: local(rVal) });
|
||||
this.setTer({ type: "return" });
|
||||
const exit = this.currentBlock();
|
||||
|
||||
const locals = this.locals.finish();
|
||||
const blocks = this.blocks.values().toArray();
|
||||
return { stmt, locals, blocks, entry, exit };
|
||||
}
|
||||
|
||||
private lowerStmt(stmt: Ast.Stmt) {
|
||||
switch (stmt.kind.type) {
|
||||
case "error":
|
||||
case "mod_block":
|
||||
case "mod_file":
|
||||
case "mod":
|
||||
break;
|
||||
case "break": {
|
||||
const { local: dst, block } = this.breakStack.at(-1)!;
|
||||
if (stmt.kind.expr) {
|
||||
const val = this.lowerExpr(stmt.kind.expr);
|
||||
this.addOp({ type: "assign", dst, src: local(val) });
|
||||
} else {
|
||||
this.addOp({ type: "assign", dst, src: { type: "null" } });
|
||||
}
|
||||
this.setTer({ type: "jump", target: block });
|
||||
this.pushBlock();
|
||||
return;
|
||||
}
|
||||
case "return":
|
||||
break;
|
||||
case "fn":
|
||||
// nothing
|
||||
return;
|
||||
case "let":
|
||||
this.lowerLetStmt(stmt);
|
||||
return;
|
||||
case "type_alias":
|
||||
break;
|
||||
case "assign":
|
||||
return this.lowerAssign(stmt);
|
||||
case "expr": {
|
||||
this.lowerExpr(stmt.kind.expr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new Error(`statement type '${stmt.kind.type}' not covered`);
|
||||
}
|
||||
|
||||
private lowerAssign(stmt: Ast.Stmt) {
|
||||
if (stmt.kind.type !== "assign") {
|
||||
throw new Error();
|
||||
}
|
||||
if (stmt.kind.assignType !== "=") {
|
||||
throw new Error("incomplete desugar");
|
||||
}
|
||||
const src = local(this.lowerExpr(stmt.kind.value));
|
||||
const s = stmt.kind.subject;
|
||||
switch (s.kind.type) {
|
||||
case "field": {
|
||||
const subject = local(this.lowerExpr(s.kind.subject));
|
||||
const ident = s.kind.ident;
|
||||
this.addOp({ type: "assign_field", subject, ident, src });
|
||||
return;
|
||||
}
|
||||
case "index": {
|
||||
const subject = local(this.lowerExpr(s.kind.subject));
|
||||
const index = local(this.lowerExpr(s.kind.value));
|
||||
this.addOp({ type: "assign_index", subject, index, src });
|
||||
return;
|
||||
}
|
||||
case "sym": {
|
||||
const sym = s.kind.sym;
|
||||
switch (sym.type) {
|
||||
case "let": {
|
||||
const dst = this.letStmtIdLocals.get(sym.stmt.id)!;
|
||||
this.addOp({ type: "assign", dst, src });
|
||||
return;
|
||||
}
|
||||
case "fn_param": {
|
||||
const dst = this.fnParamIndexLocals.get(
|
||||
sym.param.index!,
|
||||
)!;
|
||||
this.addOp({ type: "assign", dst, src });
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new Error(`symbol type '${sym.type}' not covered`);
|
||||
}
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
private lowerLetStmt(stmt: Ast.Stmt) {
|
||||
if (stmt.kind.type !== "let") {
|
||||
throw new Error();
|
||||
}
|
||||
const srcId = this.lowerExpr(stmt.kind.value);
|
||||
const dst = this.locals.allocMut(
|
||||
stmt.kind.param.vtype!,
|
||||
stmt.kind.param.sym!,
|
||||
);
|
||||
this.addOp({ type: "assign", dst, src: local(srcId) });
|
||||
this.letStmtIdLocals.set(stmt.id, dst);
|
||||
}
|
||||
|
||||
private lowerExpr(expr: Ast.Expr): LocalId {
|
||||
switch (expr.kind.type) {
|
||||
case "error": {
|
||||
const dst = this.locals.alloc({ type: "error" });
|
||||
this.addOp({ type: "assign", dst, src: { type: "error" } });
|
||||
return dst;
|
||||
}
|
||||
case "null": {
|
||||
const dst = this.locals.alloc({ type: "null" });
|
||||
this.addOp({ type: "assign", dst, src: { type: "null" } });
|
||||
return dst;
|
||||
}
|
||||
case "bool": {
|
||||
const val = expr.kind.value;
|
||||
const dst = this.locals.alloc({ type: "bool" });
|
||||
this.addOp({ type: "assign", dst, src: { type: "bool", val } });
|
||||
return dst;
|
||||
}
|
||||
case "int": {
|
||||
const val = expr.kind.value;
|
||||
const dst = this.locals.alloc({ type: "int" });
|
||||
this.addOp({ type: "assign", dst, src: { type: "int", val } });
|
||||
return dst;
|
||||
}
|
||||
case "string": {
|
||||
const val = expr.kind.value;
|
||||
const dst = this.locals.alloc({ type: "string" });
|
||||
this.addOp({
|
||||
type: "assign",
|
||||
dst,
|
||||
src: { type: "string", val },
|
||||
});
|
||||
return dst;
|
||||
}
|
||||
case "ident":
|
||||
throw new Error("should've been resolved");
|
||||
case "sym":
|
||||
return this.lowerSymExpr(expr);
|
||||
case "group":
|
||||
return this.lowerExpr(expr.kind.expr);
|
||||
case "ref": {
|
||||
const src = this.lowerExpr(expr.kind.subject);
|
||||
const dst = this.locals.alloc(expr.vtype!);
|
||||
this.addOp({ type: "ref", dst, src });
|
||||
return dst;
|
||||
}
|
||||
case "ref_mut": {
|
||||
const src = this.lowerExpr(expr.kind.subject);
|
||||
const dst = this.locals.alloc(expr.vtype!);
|
||||
this.addOp({ type: "ref_mut", dst, src });
|
||||
return dst;
|
||||
}
|
||||
case "deref": {
|
||||
const src = local(this.lowerExpr(expr.kind.subject));
|
||||
const dst = this.locals.alloc(expr.kind.subject.vtype!);
|
||||
this.addOp({ type: "deref", dst, src });
|
||||
return dst;
|
||||
}
|
||||
case "array":
|
||||
throw new Error("incomplete desugar");
|
||||
case "struct":
|
||||
throw new Error("incomplete desugar");
|
||||
case "field":
|
||||
return this.lowerFieldExpr(expr);
|
||||
case "index":
|
||||
return this.lowerIndexExpr(expr);
|
||||
case "call":
|
||||
return this.lowerCallExpr(expr);
|
||||
case "path":
|
||||
case "etype_args":
|
||||
case "unary":
|
||||
break;
|
||||
case "binary":
|
||||
return this.lowerBinaryExpr(expr);
|
||||
case "if":
|
||||
return this.lowerIfExpr(expr);
|
||||
case "loop":
|
||||
return this.lowerLoopExpr(expr);
|
||||
case "block":
|
||||
return this.lowerBlockExpr(expr);
|
||||
case "while":
|
||||
case "for_in":
|
||||
case "for":
|
||||
throw new Error("incomplete desugar");
|
||||
}
|
||||
throw new Error(`expression type '${expr.kind.type}' not covered`);
|
||||
}
|
||||
|
||||
private lowerSymExpr(expr: Ast.Expr): LocalId {
|
||||
if (expr.kind.type !== "sym") {
|
||||
throw new Error();
|
||||
}
|
||||
const sym = expr.kind.sym;
|
||||
switch (sym.type) {
|
||||
case "let":
|
||||
return this.letStmtIdLocals.get(sym.stmt.id)!;
|
||||
case "let_static":
|
||||
case "type_alias":
|
||||
break;
|
||||
case "fn": {
|
||||
const stmt = sym.stmt;
|
||||
if (sym.stmt.kind.type !== "fn") {
|
||||
throw new Error();
|
||||
}
|
||||
const dst = this.locals.alloc(sym.stmt.kind.vtype!);
|
||||
this.addOp({ type: "assign", dst, src: { type: "fn", stmt } });
|
||||
return dst;
|
||||
}
|
||||
case "fn_param": {
|
||||
return this.fnParamIndexLocals.get(sym.param.index!)!;
|
||||
}
|
||||
case "closure":
|
||||
case "generic":
|
||||
case "mod":
|
||||
}
|
||||
throw new Error(`symbol type '${sym.type}' not covered`);
|
||||
}
|
||||
|
||||
private lowerFieldExpr(expr: Ast.Expr): LocalId {
|
||||
if (expr.kind.type !== "field") {
|
||||
throw new Error();
|
||||
}
|
||||
const ident = expr.kind.ident;
|
||||
const subject = local(this.lowerExpr(expr.kind.subject));
|
||||
|
||||
const subjectVType = expr.kind.subject.vtype!;
|
||||
if (subjectVType.type !== "struct") {
|
||||
throw new Error();
|
||||
}
|
||||
const fieldVType = subjectVType.fields.find((field) =>
|
||||
field.ident === ident
|
||||
);
|
||||
if (fieldVType === undefined) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
const dst = this.locals.alloc(fieldVType.vtype);
|
||||
this.addOp({ type: "field", dst, subject, ident });
|
||||
return dst;
|
||||
}
|
||||
|
||||
private lowerIndexExpr(expr: Ast.Expr): LocalId {
|
||||
if (expr.kind.type !== "index") {
|
||||
throw new Error();
|
||||
}
|
||||
const subject = local(this.lowerExpr(expr.kind.subject));
|
||||
const index = local(this.lowerExpr(expr.kind.value));
|
||||
|
||||
const dstVType = ((): VType => {
|
||||
const outer = expr.kind.subject.vtype!;
|
||||
if (outer.type === "array") {
|
||||
return outer.subject;
|
||||
}
|
||||
if (outer.type === "string") {
|
||||
return { type: "int" };
|
||||
}
|
||||
throw new Error();
|
||||
})();
|
||||
|
||||
const dst = this.locals.alloc(dstVType);
|
||||
this.addOp({ type: "index", dst, subject, index });
|
||||
return dst;
|
||||
}
|
||||
|
||||
private lowerCallExpr(expr: Ast.Expr): LocalId {
|
||||
if (expr.kind.type !== "call") {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
const args = expr.kind.args.map((arg) => local(this.lowerExpr(arg)));
|
||||
|
||||
const subject = local(this.lowerExpr(expr.kind.subject));
|
||||
|
||||
const subjectVType = expr.kind.subject.vtype!;
|
||||
if (subjectVType.type !== "fn") {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
const dst = this.locals.alloc(subjectVType.returnType);
|
||||
this.addOp({ type: "call_val", dst, subject, args });
|
||||
return dst;
|
||||
}
|
||||
|
||||
private lowerBinaryExpr(expr: Ast.Expr): LocalId {
|
||||
if (expr.kind.type !== "binary") {
|
||||
throw new Error();
|
||||
}
|
||||
const leftVType = expr.kind.left.vtype!;
|
||||
const rightVType = expr.kind.right.vtype!;
|
||||
if (!vtypesEqual(leftVType, rightVType)) {
|
||||
throw new Error();
|
||||
}
|
||||
//const vtype = leftVType.type === "error" && rightVType || leftVType;
|
||||
|
||||
const binaryType = expr.kind.binaryType;
|
||||
const left = local(this.lowerExpr(expr.kind.left));
|
||||
const right = local(this.lowerExpr(expr.kind.right));
|
||||
|
||||
const dst = this.locals.alloc(expr.vtype!);
|
||||
|
||||
this.addOp({ type: "binary", binaryType, dst, left, right });
|
||||
return dst;
|
||||
|
||||
//throw new Error(
|
||||
// `binary vtype '${vtypeToString(leftVType)}' not covered`,
|
||||
//);
|
||||
}
|
||||
|
||||
private lowerIfExpr(expr: Ast.Expr): LocalId {
|
||||
if (expr.kind.type !== "if") {
|
||||
throw new Error();
|
||||
}
|
||||
const condBlock = this.currentBlock();
|
||||
const cond = local(this.lowerExpr(expr.kind.cond));
|
||||
const end = this.reserveBlock();
|
||||
|
||||
const val = this.locals.alloc(expr.vtype!);
|
||||
|
||||
const truthy = this.pushBlock();
|
||||
const truthyVal = local(this.lowerExpr(expr.kind.truthy));
|
||||
this.addOp({ type: "assign", dst: val, src: truthyVal });
|
||||
this.setTer({ type: "jump", target: end });
|
||||
|
||||
if (expr.kind.falsy) {
|
||||
const falsy = this.pushBlock();
|
||||
const falsyVal = local(this.lowerExpr(expr.kind.falsy));
|
||||
this.addOp({ type: "assign", dst: val, src: falsyVal });
|
||||
this.setTer({ type: "jump", target: end });
|
||||
|
||||
this.setTerOn(condBlock, { type: "if", cond, truthy, falsy });
|
||||
} else {
|
||||
this.setTerOn(condBlock, { type: "if", cond, truthy, falsy: end });
|
||||
}
|
||||
|
||||
this.pushBlockWithId(end);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
private lowerLoopExpr(expr: Ast.Expr): LocalId {
|
||||
if (expr.kind.type !== "loop") {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
const val = this.locals.alloc(expr.vtype!);
|
||||
const breakBlock = this.reserveBlock();
|
||||
this.breakStack.push({ local: val, block: breakBlock });
|
||||
|
||||
const before = this.currentBlock();
|
||||
const body = this.pushBlock();
|
||||
this.setTerOn(before, { type: "jump", target: body });
|
||||
|
||||
this.lowerExpr(expr.kind.body);
|
||||
this.setTer({ type: "jump", target: body });
|
||||
|
||||
this.breakStack.pop();
|
||||
|
||||
this.pushBlockWithId(breakBlock);
|
||||
return val;
|
||||
}
|
||||
|
||||
private lowerBlockExpr(expr: Ast.Expr): LocalId {
|
||||
if (expr.kind.type !== "block") {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
for (const stmt of expr.kind.stmts) {
|
||||
this.lowerStmt(stmt);
|
||||
}
|
||||
if (expr.kind.expr) {
|
||||
return this.lowerExpr(expr.kind.expr);
|
||||
} else {
|
||||
const local = this.locals.alloc({ type: "null" });
|
||||
this.addOp({ type: "assign", dst: local, src: { type: "null" } });
|
||||
return local;
|
||||
}
|
||||
}
|
||||
|
||||
private addOp(kind: OpKind) {
|
||||
this.blocks.get(this.currentBlockId)!.ops.push({ kind });
|
||||
}
|
||||
|
||||
private addOpOn(blockId: BlockId, kind: OpKind) {
|
||||
this.blocks.get(blockId)!.ops.push({ kind });
|
||||
}
|
||||
|
||||
private setTer(kind: TerKind) {
|
||||
this.blocks.get(this.currentBlockId)!.ter = { kind };
|
||||
}
|
||||
|
||||
private setTerOn(blockId: BlockId, kind: TerKind) {
|
||||
this.blocks.get(blockId)!.ter = { kind };
|
||||
}
|
||||
|
||||
private currentBlock(): BlockId {
|
||||
return this.currentBlockId;
|
||||
}
|
||||
|
||||
private reserveBlock(): BlockId {
|
||||
const id = this.blockIdCounter;
|
||||
this.blockIdCounter += 1;
|
||||
return id;
|
||||
}
|
||||
|
||||
private pushBlock(label?: string): BlockId {
|
||||
const id = this.blockIdCounter;
|
||||
this.blockIdCounter += 1;
|
||||
const ter: Ter = { kind: { type: "error" } };
|
||||
this.blocks.set(id, { id, ops: [], ter, label });
|
||||
this.currentBlockId = id;
|
||||
return id;
|
||||
}
|
||||
|
||||
private pushBlockWithId(id: BlockId): BlockId {
|
||||
const ter: Ter = { kind: { type: "error" } };
|
||||
this.blocks.set(id, { id, ops: [], ter });
|
||||
this.currentBlockId = id;
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
function local(id: LocalId): RValue {
|
||||
return { type: "local", id };
|
||||
}
|
397
compiler/middle/mir.ts
Normal file
397
compiler/middle/mir.ts
Normal file
@ -0,0 +1,397 @@
|
||||
import { BinaryType, Stmt, Sym } from "../ast.ts";
|
||||
import { VType, vtypeToString } from "../vtype.ts";
|
||||
|
||||
export type Mir = {
|
||||
fns: Fn[];
|
||||
};
|
||||
|
||||
export type Fn = {
|
||||
stmt: Stmt;
|
||||
locals: Local[];
|
||||
blocks: Block[];
|
||||
entry: BlockId;
|
||||
exit: BlockId;
|
||||
};
|
||||
|
||||
export type LocalId = number;
|
||||
|
||||
export type Local = {
|
||||
id: LocalId;
|
||||
mut: boolean;
|
||||
vtype: VType;
|
||||
sym?: Sym;
|
||||
};
|
||||
|
||||
export type BlockId = number;
|
||||
|
||||
export type Block = {
|
||||
id: BlockId;
|
||||
ops: Op[];
|
||||
ter: Ter;
|
||||
label?: string;
|
||||
};
|
||||
|
||||
export type Op = {
|
||||
kind: OpKind;
|
||||
};
|
||||
|
||||
type L = LocalId;
|
||||
type R = RValue;
|
||||
|
||||
export type OpKind =
|
||||
| { type: "error" }
|
||||
| { type: "assign"; dst: L; src: R }
|
||||
| { type: "ref"; dst: L; src: L }
|
||||
| { type: "ref_mut"; dst: L; src: L }
|
||||
| { type: "ptr"; dst: L; src: L }
|
||||
| { type: "ptr_mut"; dst: L; src: L }
|
||||
| { type: "drop"; src: L }
|
||||
| { type: "deref"; dst: L; src: R }
|
||||
| { type: "assign_deref"; subject: R; src: R }
|
||||
| { type: "field"; dst: L; subject: R; ident: string }
|
||||
| { type: "assign_field"; subject: R; ident: string; src: R }
|
||||
| { type: "index"; dst: L; subject: R; index: R }
|
||||
| { type: "assign_index"; subject: R; index: R; src: R }
|
||||
| { type: "call_val"; dst: L; subject: R; args: R[] }
|
||||
| { type: "binary"; binaryType: BinaryType; dst: L; left: R; right: R };
|
||||
|
||||
export type Ter = {
|
||||
kind: TerKind;
|
||||
};
|
||||
|
||||
export type TerKind =
|
||||
| { type: "error" }
|
||||
| { type: "return" }
|
||||
| { type: "jump"; target: BlockId }
|
||||
| { type: "if"; cond: R; truthy: BlockId; falsy: BlockId };
|
||||
|
||||
export type RValue =
|
||||
| { type: "error" }
|
||||
| { type: "local"; id: BlockId }
|
||||
| { type: "copy"; id: BlockId }
|
||||
| { type: "move"; id: BlockId }
|
||||
| { type: "null" }
|
||||
| { type: "bool"; val: boolean }
|
||||
| { type: "int"; val: number }
|
||||
| { type: "string"; val: string }
|
||||
| { type: "fn"; stmt: Stmt };
|
||||
|
||||
export function visitBlockDsts(
|
||||
block: Block,
|
||||
visit: (local: LocalId, index: number) => void,
|
||||
) {
|
||||
for (const [op, i] of block.ops.map((v, i) => [v, i] as const)) {
|
||||
const ok = op.kind;
|
||||
switch (ok.type) {
|
||||
case "error":
|
||||
break;
|
||||
case "assign":
|
||||
case "ref":
|
||||
case "ref_mut":
|
||||
case "ptr":
|
||||
case "ptr_mut":
|
||||
case "deref":
|
||||
case "field":
|
||||
case "index":
|
||||
case "call_val":
|
||||
case "binary":
|
||||
visit(ok.dst, i);
|
||||
break;
|
||||
case "assign_deref":
|
||||
case "assign_field":
|
||||
case "assign_index":
|
||||
break;
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function replaceBlockSrcs(
|
||||
block: Block,
|
||||
replace: (src: RValue) => RValue,
|
||||
) {
|
||||
for (const op of block.ops) {
|
||||
const ok = op.kind;
|
||||
switch (ok.type) {
|
||||
case "error":
|
||||
break;
|
||||
case "assign":
|
||||
ok.src = replace(ok.src);
|
||||
break;
|
||||
case "ref":
|
||||
case "ref_mut":
|
||||
case "ptr":
|
||||
case "ptr_mut":
|
||||
case "drop":
|
||||
break;
|
||||
case "deref":
|
||||
ok.src = replace(ok.src);
|
||||
break;
|
||||
case "assign_deref":
|
||||
ok.subject = replace(ok.subject);
|
||||
ok.src = replace(ok.src);
|
||||
break;
|
||||
case "field":
|
||||
ok.subject = replace(ok.subject);
|
||||
break;
|
||||
case "assign_field":
|
||||
ok.subject = replace(ok.subject);
|
||||
ok.src = replace(ok.src);
|
||||
break;
|
||||
case "index":
|
||||
ok.subject = replace(ok.subject);
|
||||
ok.index = replace(ok.index);
|
||||
break;
|
||||
case "assign_index":
|
||||
ok.subject = replace(ok.subject);
|
||||
ok.index = replace(ok.index);
|
||||
ok.src = replace(ok.src);
|
||||
break;
|
||||
case "call_val":
|
||||
ok.subject = replace(ok.subject);
|
||||
ok.args = ok.args.map((arg) => replace(arg));
|
||||
break;
|
||||
case "binary":
|
||||
ok.left = replace(ok.left);
|
||||
ok.right = replace(ok.right);
|
||||
break;
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
const tk = block.ter.kind;
|
||||
switch (tk.type) {
|
||||
case "error":
|
||||
break;
|
||||
case "return":
|
||||
break;
|
||||
case "jump":
|
||||
break;
|
||||
case "if":
|
||||
tk.cond = replace(tk.cond);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export function visitBlockSrcs(
|
||||
block: Block,
|
||||
visitor: (src: RValue, op?: Op, index?: number, ter?: Ter) => void,
|
||||
) {
|
||||
for (const [op, i] of block.ops.map((v, i) => [v, i] as const)) {
|
||||
const ok = op.kind;
|
||||
switch (ok.type) {
|
||||
case "error":
|
||||
break;
|
||||
case "assign":
|
||||
visitor(ok.src, op, i);
|
||||
break;
|
||||
case "ref":
|
||||
case "ref_mut":
|
||||
case "ptr":
|
||||
case "ptr_mut":
|
||||
case "drop":
|
||||
break;
|
||||
case "deref":
|
||||
visitor(ok.src, op, i);
|
||||
break;
|
||||
case "assign_deref":
|
||||
visitor(ok.src, op, i);
|
||||
visitor(ok.subject, op, i);
|
||||
break;
|
||||
case "field":
|
||||
visitor(ok.subject, op, i);
|
||||
break;
|
||||
case "assign_field":
|
||||
visitor(ok.subject, op, i);
|
||||
visitor(ok.src, op, i);
|
||||
break;
|
||||
case "index":
|
||||
visitor(ok.subject, op, i);
|
||||
visitor(ok.index, op, i);
|
||||
break;
|
||||
case "assign_index":
|
||||
visitor(ok.subject, op, i);
|
||||
visitor(ok.index, op, i);
|
||||
visitor(ok.src, op, i);
|
||||
break;
|
||||
case "call_val":
|
||||
visitor(ok.subject, op, i);
|
||||
ok.args.map((arg) => visitor(arg, op, i));
|
||||
break;
|
||||
case "binary":
|
||||
visitor(ok.left, op, i);
|
||||
visitor(ok.right, op, i);
|
||||
break;
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
const tk = block.ter.kind;
|
||||
switch (tk.type) {
|
||||
case "error":
|
||||
break;
|
||||
case "return":
|
||||
break;
|
||||
case "jump":
|
||||
break;
|
||||
case "if":
|
||||
visitor(tk.cond, undefined, undefined, block.ter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export function mirOpCount(mir: Mir): number {
|
||||
return mir.fns
|
||||
.reduce((acc, fn) =>
|
||||
acc + fn.blocks
|
||||
.reduce((acc, block) => acc + block.ops.length + 1, 0), 0);
|
||||
}
|
||||
|
||||
export function printMir(mir: Mir) {
|
||||
for (const fn of mir.fns) {
|
||||
const stmt = fn.stmt;
|
||||
if (stmt.kind.type !== "fn") {
|
||||
throw new Error();
|
||||
}
|
||||
const name = stmt.kind.sym!.fullPath;
|
||||
|
||||
const vtype = stmt.kind.vtype;
|
||||
if (vtype?.type !== "fn") {
|
||||
throw new Error();
|
||||
}
|
||||
const generics = vtype.genericParams
|
||||
?.map(({ ident }) => `${ident}`).join(", ") ?? "";
|
||||
const params = vtype.params
|
||||
.map(({ mut, vtype }, i) =>
|
||||
`${mut && "mut" || ""} _${fn.locals[i + 1].id}: ${
|
||||
vtypeToString(vtype)
|
||||
}`
|
||||
)
|
||||
.join(", ");
|
||||
const returnType = vtypeToString(vtype.returnType);
|
||||
console.log(`${name}${generics}(${params}) -> ${returnType} {`);
|
||||
|
||||
const paramIndices = vtype.params.map((_v, i) => i + 1);
|
||||
for (
|
||||
const { id, vtype, mut } of fn.locals
|
||||
.filter((_v, i) => !paramIndices.includes(i))
|
||||
) {
|
||||
const m = mut ? "mut" : "";
|
||||
const v = vtypeToString(vtype);
|
||||
console.log(` let ${m} _${id}: ${v};`);
|
||||
}
|
||||
for (const block of fn.blocks) {
|
||||
const l = (msg: string) => console.log(` ${msg}`);
|
||||
const r = rvalueToString;
|
||||
|
||||
console.log(` ${block.label ?? "bb" + block.id}: {`);
|
||||
for (const op of block.ops) {
|
||||
const k = op.kind;
|
||||
switch (k.type) {
|
||||
case "error":
|
||||
l(`<error>;`);
|
||||
break;
|
||||
case "assign":
|
||||
l(`_${k.dst} = ${r(k.src)};`);
|
||||
break;
|
||||
case "ref":
|
||||
l(`_${k.dst} = &_${k.src};`);
|
||||
break;
|
||||
case "ref_mut":
|
||||
l(`_${k.dst} = &mut _${k.src};`);
|
||||
break;
|
||||
case "ptr":
|
||||
l(`_${k.dst} = *_${k.src};`);
|
||||
break;
|
||||
case "ptr_mut":
|
||||
l(`_${k.dst} = *mut _${k.src};`);
|
||||
break;
|
||||
case "drop":
|
||||
l(`drop _${k.src};`);
|
||||
break;
|
||||
case "deref":
|
||||
l(`_${k.dst} = *${r(k.src)};`);
|
||||
break;
|
||||
case "assign_deref":
|
||||
l(`*${r(k.subject)} = ${r(k.src)};`);
|
||||
break;
|
||||
case "field":
|
||||
l(`_${k.dst} = ${r(k.subject)}.${k.ident};`);
|
||||
break;
|
||||
case "assign_field":
|
||||
l(`${r(k.subject)}.${k.ident} = ${r(k.src)};`);
|
||||
break;
|
||||
case "index":
|
||||
l(`_${k.dst} = ${r(k.subject)}[${r(k.index)}];`);
|
||||
break;
|
||||
case "assign_index":
|
||||
l(`${r(k.subject)}[${r(k.index)}] = ${r(k.src)};`);
|
||||
break;
|
||||
case "call_val": {
|
||||
const args = k.args.map((arg) => r(arg)).join(", ");
|
||||
l(`_${k.dst} = call ${r(k.subject)}(${args});`);
|
||||
break;
|
||||
}
|
||||
case "binary": {
|
||||
l(`_${k.dst} = ${r(k.left)} ${k.binaryType} ${
|
||||
r(k.right)
|
||||
};`);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
const tk = block.ter.kind;
|
||||
switch (tk.type) {
|
||||
case "error":
|
||||
l(`<error>;`);
|
||||
break;
|
||||
case "return":
|
||||
l(`return;`);
|
||||
break;
|
||||
case "jump":
|
||||
l(`jump bb${tk.target};`);
|
||||
break;
|
||||
case "if":
|
||||
l(`if ${
|
||||
r(tk.cond)
|
||||
}, true: bb${tk.truthy}, false: bb${tk.falsy};`);
|
||||
break;
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
console.log(" }");
|
||||
}
|
||||
console.log("}");
|
||||
}
|
||||
}
|
||||
|
||||
export function rvalueToString(rvalue: RValue): string {
|
||||
switch (rvalue.type) {
|
||||
case "error":
|
||||
return `<error>`;
|
||||
case "local":
|
||||
return `_${rvalue.id}`;
|
||||
case "copy":
|
||||
return `copy _${rvalue.id}`;
|
||||
case "move":
|
||||
return `move _${rvalue.id}`;
|
||||
case "null":
|
||||
return "null";
|
||||
case "bool":
|
||||
return `${rvalue.val}`;
|
||||
case "int":
|
||||
return `${rvalue.val}`;
|
||||
case "string":
|
||||
return `"${rvalue.val}"`;
|
||||
case "fn": {
|
||||
const stmt = rvalue.stmt;
|
||||
if (stmt.kind.type !== "fn") {
|
||||
throw new Error();
|
||||
}
|
||||
return stmt.kind.sym!.fullPath;
|
||||
}
|
||||
}
|
||||
}
|
315
compiler/mono.ts
Normal file
315
compiler/mono.ts
Normal file
@ -0,0 +1,315 @@
|
||||
import { Expr, Stmt } from "./ast.ts";
|
||||
import { AstVisitor, visitExpr, VisitRes, visitStmts } from "./ast_visitor.ts";
|
||||
import { GenericArgsMap, VType } from "./vtype.ts";
|
||||
|
||||
export class Monomorphizer {
|
||||
private fnIdCounter = 0;
|
||||
private fns: MonoFnsMap = {};
|
||||
private callMap: MonoCallNameGenMap = {};
|
||||
private allFns: Map<number, Stmt>;
|
||||
private entryFn: Stmt;
|
||||
|
||||
constructor(private ast: Stmt[]) {
|
||||
this.allFns = new AllFnsCollector().collect(this.ast);
|
||||
this.entryFn = findMain(this.allFns);
|
||||
}
|
||||
|
||||
public monomorphize(): MonoResult {
|
||||
this.monomorphizeFn(this.entryFn);
|
||||
return { monoFns: this.fns, callMap: this.callMap };
|
||||
}
|
||||
|
||||
private monomorphizeFn(
|
||||
stmt: Stmt,
|
||||
genericArgs?: GenericArgsMap,
|
||||
): MonoFn {
|
||||
const id = this.fnIdCounter;
|
||||
this.fnIdCounter += 1;
|
||||
const nameGen = monoFnNameGen(id, stmt, genericArgs);
|
||||
if (nameGen in this.fns) {
|
||||
return this.fns[nameGen];
|
||||
}
|
||||
const monoFn = { id, nameGen, stmt, genericArgs };
|
||||
this.fns[nameGen] = monoFn;
|
||||
const calls = new CallCollector().collect(stmt);
|
||||
for (const call of calls) {
|
||||
this.callMap[call.id] = nameGen;
|
||||
if (call.kind.type !== "call") {
|
||||
throw new Error();
|
||||
}
|
||||
if (
|
||||
call.kind.subject.vtype?.type === "fn" &&
|
||||
call.kind.subject.vtype.genericParams === undefined
|
||||
) {
|
||||
const fn = this.allFns.get(call.kind.subject.vtype.stmtId);
|
||||
if (fn === undefined) {
|
||||
throw new Error();
|
||||
}
|
||||
const monoFn = this.monomorphizeFn(fn);
|
||||
this.callMap[call.id] = monoFn.nameGen;
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
call.kind.subject.vtype?.type === "fn" &&
|
||||
call.kind.subject.vtype.genericParams !== undefined
|
||||
) {
|
||||
if (call.kind.genericArgs === undefined) {
|
||||
throw new Error();
|
||||
}
|
||||
const genericArgs = call.kind.genericArgs;
|
||||
|
||||
const monoArgs: GenericArgsMap = {};
|
||||
for (const key in call.kind.genericArgs) {
|
||||
const vtype = genericArgs[key];
|
||||
if (vtype.type === "generic") {
|
||||
if (genericArgs === undefined) {
|
||||
throw new Error();
|
||||
}
|
||||
monoArgs[key] = genericArgs[vtype.param.id];
|
||||
} else {
|
||||
monoArgs[key] = vtype;
|
||||
}
|
||||
}
|
||||
|
||||
const fnType = call.kind.subject.vtype!;
|
||||
if (fnType.type !== "fn") {
|
||||
throw new Error();
|
||||
}
|
||||
const fn = this.allFns.get(fnType.stmtId);
|
||||
if (fn === undefined) {
|
||||
throw new Error();
|
||||
}
|
||||
const monoFn = this.monomorphizeFn(fn, monoArgs);
|
||||
this.callMap[call.id] = monoFn.nameGen;
|
||||
continue;
|
||||
}
|
||||
if (call.kind.subject.vtype?.type === "generic_spec") {
|
||||
const genericSpecType = call.kind.subject.vtype!;
|
||||
if (genericSpecType.subject.type !== "fn") {
|
||||
throw new Error();
|
||||
}
|
||||
const fnType = genericSpecType.subject;
|
||||
|
||||
const monoArgs: GenericArgsMap = {};
|
||||
for (const key in genericSpecType.genericArgs) {
|
||||
const vtype = genericSpecType.genericArgs[key];
|
||||
if (vtype.type === "generic") {
|
||||
if (genericArgs === undefined) {
|
||||
throw new Error();
|
||||
}
|
||||
monoArgs[key] = genericArgs[vtype.param.id];
|
||||
} else {
|
||||
monoArgs[key] = vtype;
|
||||
}
|
||||
}
|
||||
|
||||
const fn = this.allFns.get(fnType.stmtId);
|
||||
if (fn === undefined) {
|
||||
throw new Error();
|
||||
}
|
||||
const monoFn = this.monomorphizeFn(fn, monoArgs);
|
||||
this.callMap[call.id] = monoFn.nameGen;
|
||||
continue;
|
||||
}
|
||||
throw new Error();
|
||||
}
|
||||
return monoFn;
|
||||
}
|
||||
}
|
||||
|
||||
export type MonoResult = {
|
||||
monoFns: MonoFnsMap;
|
||||
callMap: MonoCallNameGenMap;
|
||||
};
|
||||
|
||||
export type MonoFnsMap = { [nameGen: string]: MonoFn };
|
||||
|
||||
export type MonoFn = {
|
||||
id: number;
|
||||
nameGen: string;
|
||||
stmt: Stmt;
|
||||
genericArgs?: GenericArgsMap;
|
||||
};
|
||||
|
||||
export type MonoCallNameGenMap = { [exprId: number]: string };
|
||||
|
||||
function monoFnNameGen(
|
||||
id: number,
|
||||
stmt: Stmt,
|
||||
genericArgs?: GenericArgsMap,
|
||||
): string {
|
||||
if (stmt.kind.type !== "fn") {
|
||||
throw new Error();
|
||||
}
|
||||
if (stmt.kind.ident === "main") {
|
||||
return "main";
|
||||
}
|
||||
if (genericArgs === undefined) {
|
||||
return `${stmt.kind.ident}_${id}`;
|
||||
}
|
||||
const args = Object.values(genericArgs)
|
||||
.map((arg) => vtypeNameGenPart(arg))
|
||||
.join("_");
|
||||
return `${stmt.kind.ident}_${id}_${args}`;
|
||||
}
|
||||
|
||||
function vtypeNameGenPart(vtype: VType): string {
|
||||
switch (vtype.type) {
|
||||
case "error":
|
||||
throw new Error("error in type");
|
||||
case "string":
|
||||
case "int":
|
||||
case "bool":
|
||||
case "null":
|
||||
case "unknown":
|
||||
return vtype.type;
|
||||
case "ref":
|
||||
return `&${vtypeNameGenPart(vtype.subject)}`;
|
||||
case "ref_mut":
|
||||
return `&mut ${vtypeNameGenPart(vtype.subject)}`;
|
||||
case "ptr":
|
||||
return `*${vtypeNameGenPart(vtype.subject)}`;
|
||||
case "ptr_mut":
|
||||
return `*mut ${vtypeNameGenPart(vtype.subject)}`;
|
||||
case "array":
|
||||
return `[${vtypeNameGenPart(vtype.subject)}]`;
|
||||
case "struct": {
|
||||
const fields = vtype.fields
|
||||
.map((field) =>
|
||||
`${field.ident}, ${vtypeNameGenPart(field.vtype)}`
|
||||
)
|
||||
.join(", ");
|
||||
return `struct { ${fields} }`;
|
||||
}
|
||||
case "fn":
|
||||
return `fn(${vtype.stmtId})`;
|
||||
case "generic":
|
||||
case "generic_spec":
|
||||
throw new Error("cannot be monomorphized");
|
||||
}
|
||||
}
|
||||
|
||||
export class AllFnsCollector implements AstVisitor {
|
||||
private allFns = new Map<number, Stmt>();
|
||||
|
||||
public collect(ast: Stmt[]): Map<number, Stmt> {
|
||||
visitStmts(ast, this);
|
||||
return this.allFns;
|
||||
}
|
||||
|
||||
visitFnStmt(stmt: Stmt): VisitRes {
|
||||
if (stmt.kind.type !== "fn") {
|
||||
throw new Error();
|
||||
}
|
||||
this.allFns.set(stmt.id, stmt);
|
||||
}
|
||||
}
|
||||
|
||||
function findMain(fns: Map<number, Stmt>): Stmt {
|
||||
const mainId = fns.values().find((stmt) =>
|
||||
stmt.kind.type === "fn" && stmt.kind.ident === "main"
|
||||
);
|
||||
if (mainId === undefined) {
|
||||
console.error("error: cannot find function 'main'");
|
||||
console.error(apology);
|
||||
throw new Error("cannot find function 'main'");
|
||||
}
|
||||
return mainId;
|
||||
}
|
||||
|
||||
class CallCollector implements AstVisitor {
|
||||
private calls: Expr[] = [];
|
||||
|
||||
public collect(fn: Stmt): Expr[] {
|
||||
if (fn.kind.type !== "fn") {
|
||||
throw new Error();
|
||||
}
|
||||
visitExpr(fn.kind.body, this);
|
||||
return this.calls;
|
||||
}
|
||||
|
||||
visitFnStmt(_stmt: Stmt): VisitRes {
|
||||
return "stop";
|
||||
}
|
||||
|
||||
visitCallExpr(expr: Expr): VisitRes {
|
||||
if (expr.kind.type !== "call") {
|
||||
throw new Error();
|
||||
}
|
||||
this.calls.push(expr);
|
||||
}
|
||||
}
|
||||
|
||||
const apology = `
|
||||
Hear me out. Monomorphization, meaning the process
|
||||
inwich generic functions are stamped out into seperate
|
||||
specialized functions is actually really hard, and I
|
||||
have a really hard time right now, figuring out, how
|
||||
to do it in a smart way. To really explain it, let's
|
||||
imagine you have a function, you defined as a<T>().
|
||||
For each call with seperate generics arguments given,
|
||||
such as a::<int>() and a::<string>(), a specialized
|
||||
function has to be 'stamped out', ie. created and put
|
||||
into the compilation with the rest of the program. Now
|
||||
to the reason as to why 'main' is needed. To do the
|
||||
monomorphization, we have to do it recursively. To
|
||||
explain this, imagine you have a generic function a<T>
|
||||
and inside the body of a<T>, you call another generic
|
||||
function such as b<T> with the same generic type. This
|
||||
means that the monomorphization process of b<T> depends
|
||||
on the monomorphization of a<T>. What this essentially
|
||||
means, is that the monomorphization process works on
|
||||
the program as a call graph, meaning a graph or tree
|
||||
structure where each represents a function call to
|
||||
either another function or a recursive call to the
|
||||
function itself. But a problem arises from doing it
|
||||
this way, which is that a call graph will need an
|
||||
entrypoint. The language, as it is currently, does
|
||||
not really require a 'main'-function. Or maybe it
|
||||
does, but that's beside the point. The point is that
|
||||
we need a main function, to be the entry point for
|
||||
the call graph. The monomorphization process then
|
||||
runs through the program from that entry point. This
|
||||
means that each function we call, will itself be
|
||||
monomorphized and added to the compilation. It also
|
||||
means that functions that are not called, will also
|
||||
not be added to the compilation. This essentially
|
||||
eliminates uncalled/dead functions. Is this
|
||||
particularly smart to do in such a high level part
|
||||
of the compilation process? I don't know. It's
|
||||
obvious that we can't just use every function as
|
||||
an entry point in the call graph, because we're
|
||||
actively added new functions. Additionally, with
|
||||
generic functions, we don't know, if they're the
|
||||
entry point, what generic arguments, they should
|
||||
be monomorphized with. We could do monomorphization
|
||||
the same way C++ does it, where all non-generic
|
||||
functions before monomorphization are treated as
|
||||
entry points in the call graph. But this has the
|
||||
drawback that generic and non-generic functions
|
||||
are treated differently, which has many underlying
|
||||
drawbacks, especially pertaining to the amount of
|
||||
work needed to handle both in all proceeding steps
|
||||
of the compiler. Anyways, I just wanted to yap and
|
||||
complain about the way generics and monomorphization
|
||||
has made the compiler 100x more complicated, and
|
||||
that I find it really hard to implement in a way,
|
||||
that is not either too simplistic or so complicated
|
||||
and advanced I'm too dumb to implement it. So if
|
||||
you would be so kind as to make it clear to the
|
||||
compiler, what function it should designate as
|
||||
the entry point to the call graph, it will use
|
||||
for monomorphization, that would be very kind of
|
||||
you. The way you do this, is by added or selecting
|
||||
one of your current functions and giving it the
|
||||
name of 'main'. This is spelled m-a-i-n. The word
|
||||
is synonemous with the words primary and principle.
|
||||
The name is meant to designate the entry point into
|
||||
the program, which is why the monomorphization
|
||||
process uses this specific function as the entry
|
||||
point into the call graph, it generates. So if you
|
||||
would be so kind as to do that, that would really
|
||||
make my day. In any case, keep hacking ferociously
|
||||
on whatever you're working on. I have monomorphizer
|
||||
to implement. See ya. -Your favorite compiler girl <3
|
||||
`.replaceAll(" ", "").trim();
|
@ -1,5 +1,4 @@
|
||||
import {
|
||||
Anno,
|
||||
AssignType,
|
||||
AstCreator,
|
||||
BinaryType,
|
||||
@ -7,8 +6,11 @@ import {
|
||||
ETypeKind,
|
||||
Expr,
|
||||
ExprKind,
|
||||
Field,
|
||||
GenericParam,
|
||||
Param,
|
||||
Stmt,
|
||||
StmtDetails,
|
||||
StmtKind,
|
||||
UnaryType,
|
||||
} from "./ast.ts";
|
||||
@ -16,6 +18,8 @@ import { printStackTrace, Reporter } from "./info.ts";
|
||||
import { Lexer } from "./lexer.ts";
|
||||
import { Pos, Token } from "./token.ts";
|
||||
|
||||
type Res<T> = { ok: true; value: T } | { ok: false; pos?: Pos };
|
||||
|
||||
export class Parser {
|
||||
private currentToken: Token | null;
|
||||
|
||||
@ -34,26 +38,34 @@ export class Parser {
|
||||
private parseStmts(): Stmt[] {
|
||||
const stmts: Stmt[] = [];
|
||||
while (!this.done()) {
|
||||
if (this.test("fn")) {
|
||||
stmts.push(this.parseFn());
|
||||
} else if (
|
||||
this.test("let") || this.test("return") || this.test("break")
|
||||
) {
|
||||
stmts.push(this.parseSingleLineBlockStmt());
|
||||
this.eatSemicolon();
|
||||
} else if (
|
||||
["{", "if", "loop", "while", "for"].some((tt) => this.test(tt))
|
||||
) {
|
||||
const expr = this.parseMultiLineBlockExpr();
|
||||
stmts.push(this.stmt({ type: "expr", expr }, expr.pos));
|
||||
} else {
|
||||
stmts.push(this.parseAssign());
|
||||
this.eatSemicolon();
|
||||
}
|
||||
stmts.push(this.parseStmt());
|
||||
}
|
||||
return stmts;
|
||||
}
|
||||
|
||||
private parseStmt(): Stmt {
|
||||
if (
|
||||
["#", "pub", "mod", "fn"].some((tt) => this.test(tt))
|
||||
) {
|
||||
return this.parseItemStmt();
|
||||
} else if (
|
||||
["let", "type_alias", "return", "break"].some((tt) => this.test(tt))
|
||||
) {
|
||||
const expr = this.parseSingleLineBlockStmt();
|
||||
this.eatSemicolon();
|
||||
return expr;
|
||||
} else if (
|
||||
["{", "if", "loop", "while", "for"].some((tt) => this.test(tt))
|
||||
) {
|
||||
const expr = this.parseMultiLineBlockExpr();
|
||||
return (this.stmt({ type: "expr", expr }, expr.pos));
|
||||
} else {
|
||||
const expr = this.parseAssign();
|
||||
this.eatSemicolon();
|
||||
return expr;
|
||||
}
|
||||
}
|
||||
|
||||
private parseMultiLineBlockExpr(): Expr {
|
||||
const pos = this.pos();
|
||||
if (this.test("{")) {
|
||||
@ -80,6 +92,9 @@ export class Parser {
|
||||
if (this.test("let")) {
|
||||
return this.parseLet();
|
||||
}
|
||||
if (this.test("type_alias")) {
|
||||
return this.parseTypeAlias();
|
||||
}
|
||||
if (this.test("return")) {
|
||||
return this.parseReturn();
|
||||
}
|
||||
@ -107,19 +122,21 @@ export class Parser {
|
||||
private parseBlock(): Expr {
|
||||
const pos = this.pos();
|
||||
this.step();
|
||||
let stmts: Stmt[] = [];
|
||||
const stmts: Stmt[] = [];
|
||||
while (!this.done()) {
|
||||
if (this.test("}")) {
|
||||
this.step();
|
||||
return this.expr({ type: "block", stmts }, pos);
|
||||
} else if (
|
||||
this.test("return") || this.test("break") || this.test("let")
|
||||
["#", "pub", "mod", "fn"].some((tt) => this.test(tt))
|
||||
) {
|
||||
stmts.push(this.parseItemStmt());
|
||||
} else if (
|
||||
["let", "type_alias", "return", "break"]
|
||||
.some((tt) => this.test(tt))
|
||||
) {
|
||||
stmts.push(this.parseSingleLineBlockStmt());
|
||||
this.eatSemicolon();
|
||||
} else if (this.test("fn")) {
|
||||
stmts.push(this.parseSingleLineBlockStmt());
|
||||
stmts.push(this.parseFn());
|
||||
} else if (
|
||||
["{", "if", "loop", "while", "for"].some((tt) => this.test(tt))
|
||||
) {
|
||||
@ -163,7 +180,110 @@ export class Parser {
|
||||
return this.expr({ type: "error" }, pos);
|
||||
}
|
||||
|
||||
private parseFn(): Stmt {
|
||||
private parseItemStmt(
|
||||
pos = this.pos(),
|
||||
details: StmtDetails = {
|
||||
pub: false,
|
||||
annos: [],
|
||||
},
|
||||
): Stmt {
|
||||
const spos = this.pos();
|
||||
if (this.test("#") && !details.pub) {
|
||||
this.step();
|
||||
if (!this.test("[")) {
|
||||
this.report("expected '['");
|
||||
return this.stmt({ type: "error" }, spos);
|
||||
}
|
||||
this.step();
|
||||
if (!this.test("ident")) {
|
||||
this.report("expected 'ident'");
|
||||
return this.stmt({ type: "error" }, spos);
|
||||
}
|
||||
const ident = this.current().identValue!;
|
||||
this.step();
|
||||
const args: Expr[] = [];
|
||||
if (this.test("(")) {
|
||||
this.step();
|
||||
if (!this.done() && !this.test(")")) {
|
||||
args.push(this.parseExpr());
|
||||
while (this.test(",")) {
|
||||
this.step();
|
||||
if (this.done() || this.test(")")) {
|
||||
break;
|
||||
}
|
||||
args.push(this.parseExpr());
|
||||
}
|
||||
}
|
||||
if (!this.test(")")) {
|
||||
this.report("expected ')'");
|
||||
return this.stmt({ type: "error" }, spos);
|
||||
}
|
||||
this.step();
|
||||
}
|
||||
if (!this.test("]")) {
|
||||
this.report("expected ']'");
|
||||
return this.stmt({ type: "error" }, spos);
|
||||
}
|
||||
this.step();
|
||||
const anno = { ident, args, pos: spos };
|
||||
return this.parseItemStmt(pos, {
|
||||
...details,
|
||||
annos: [...details.annos, anno],
|
||||
});
|
||||
} else if (this.test("pub") && !details.pub) {
|
||||
this.step();
|
||||
return this.parseItemStmt(pos, { ...details, pub: true });
|
||||
} else if (this.test("mod")) {
|
||||
return this.parseMod(details);
|
||||
} else if (this.test("fn")) {
|
||||
return this.parseFn(details);
|
||||
} else {
|
||||
this.report("expected item statement");
|
||||
return this.stmt({ type: "error" }, pos);
|
||||
}
|
||||
}
|
||||
|
||||
private parseMod(details: StmtDetails): Stmt {
|
||||
const pos = this.pos();
|
||||
this.step();
|
||||
if (!this.test("ident")) {
|
||||
this.report("expected 'ident'");
|
||||
return this.stmt({ type: "error" }, pos);
|
||||
}
|
||||
const ident = this.current().identValue!;
|
||||
this.step();
|
||||
if (this.test(";")) {
|
||||
this.eatSemicolon();
|
||||
return this.stmt({ type: "mod_file", ident, filePath: ident }, pos);
|
||||
}
|
||||
if (this.test("string")) {
|
||||
const filePath = this.current().stringValue!;
|
||||
this.step();
|
||||
this.eatSemicolon();
|
||||
return this.stmt({ type: "mod_file", ident, filePath }, pos);
|
||||
}
|
||||
|
||||
if (!this.test("{")) {
|
||||
this.report("expected '{' or 'string'");
|
||||
return this.stmt({ type: "error" }, pos);
|
||||
}
|
||||
this.step();
|
||||
|
||||
const stmts: Stmt[] = [];
|
||||
while (!this.done() && !this.test("}")) {
|
||||
stmts.push(this.parseStmt());
|
||||
}
|
||||
|
||||
if (!this.test("}")) {
|
||||
this.report("expected '}'");
|
||||
return this.stmt({ type: "error" }, pos);
|
||||
}
|
||||
this.step();
|
||||
|
||||
return this.stmt({ type: "mod_block", ident, stmts }, pos, details);
|
||||
}
|
||||
|
||||
private parseFn(details: StmtDetails): Stmt {
|
||||
const pos = this.pos();
|
||||
this.step();
|
||||
if (!this.test("ident")) {
|
||||
@ -172,21 +292,21 @@ export class Parser {
|
||||
}
|
||||
const ident = this.current().identValue!;
|
||||
this.step();
|
||||
let genericParams: GenericParam[] | undefined;
|
||||
if (this.test("<")) {
|
||||
genericParams = this.parseFnETypeParams();
|
||||
}
|
||||
if (!this.test("(")) {
|
||||
this.report("expected '('");
|
||||
return this.stmt({ type: "error" }, pos);
|
||||
}
|
||||
const params = this.parseFnParams();
|
||||
let returnType: EType | null = null;
|
||||
let returnType: EType | undefined;
|
||||
if (this.test("->")) {
|
||||
this.step();
|
||||
returnType = this.parseEType();
|
||||
}
|
||||
|
||||
let anno: Anno | null = null;
|
||||
if (this.test("#")) {
|
||||
anno = this.parseAnno();
|
||||
}
|
||||
if (!this.test("{")) {
|
||||
this.report("expected block");
|
||||
return this.stmt({ type: "error" }, pos);
|
||||
@ -196,105 +316,104 @@ export class Parser {
|
||||
{
|
||||
type: "fn",
|
||||
ident,
|
||||
genericParams,
|
||||
params,
|
||||
returnType: returnType !== null ? returnType : undefined,
|
||||
returnType,
|
||||
body,
|
||||
anno: anno != null ? anno : undefined,
|
||||
},
|
||||
pos,
|
||||
details,
|
||||
);
|
||||
}
|
||||
|
||||
private parseAnnoArgs(): Expr[] {
|
||||
this.step();
|
||||
if (!this.test("(")) {
|
||||
this.report("expected '('");
|
||||
return [];
|
||||
}
|
||||
this.step();
|
||||
const annoArgs: Expr[] = [];
|
||||
if (!this.test(")")) {
|
||||
annoArgs.push(this.parseExpr());
|
||||
while (this.test(",")) {
|
||||
this.step();
|
||||
if (this.test(")")) {
|
||||
break;
|
||||
}
|
||||
annoArgs.push(this.parseExpr());
|
||||
}
|
||||
}
|
||||
if (!this.test(")")) {
|
||||
this.report("expected ')'");
|
||||
return [];
|
||||
}
|
||||
this.step();
|
||||
return annoArgs;
|
||||
private parseFnETypeParams(): GenericParam[] {
|
||||
return this.parseDelimitedList(this.parseETypeParam, ">", ",");
|
||||
}
|
||||
|
||||
private parseAnno(): Anno | null {
|
||||
private parseETypeParam(index: number): Res<GenericParam> {
|
||||
const pos = this.pos();
|
||||
this.step();
|
||||
if (!this.test("[")) {
|
||||
this.report("expected '['");
|
||||
return null;
|
||||
if (this.test("ident")) {
|
||||
const ident = this.current().identValue!;
|
||||
this.step();
|
||||
return {
|
||||
ok: true,
|
||||
value: this.astCreator.genericParam({ index, ident, pos }),
|
||||
};
|
||||
}
|
||||
this.step();
|
||||
if (!this.test("ident")) {
|
||||
this.report("expected identifier");
|
||||
return null;
|
||||
}
|
||||
const ident = this.current().identValue!;
|
||||
const values = this.parseAnnoArgs();
|
||||
if (!this.test("]")) {
|
||||
this.report("expected ']'");
|
||||
return null;
|
||||
}
|
||||
this.step();
|
||||
return { ident, pos, values };
|
||||
this.report("expected generic parameter");
|
||||
return { ok: false };
|
||||
}
|
||||
|
||||
private parseFnParams(): Param[] {
|
||||
this.step();
|
||||
if (this.test(")")) {
|
||||
this.step();
|
||||
return [];
|
||||
}
|
||||
const params: Param[] = [];
|
||||
const paramResult = this.parseParam();
|
||||
if (!paramResult.ok) {
|
||||
return [];
|
||||
}
|
||||
params.push(paramResult.value);
|
||||
while (this.test(",")) {
|
||||
this.step();
|
||||
if (this.test(")")) {
|
||||
break;
|
||||
}
|
||||
const paramResult = this.parseParam();
|
||||
if (!paramResult.ok) {
|
||||
return [];
|
||||
}
|
||||
params.push(paramResult.value);
|
||||
}
|
||||
if (!this.test(")")) {
|
||||
this.report("expected ')'");
|
||||
return params;
|
||||
}
|
||||
this.step();
|
||||
return params;
|
||||
return this.parseDelimitedList(this.parseParam, ")", ",");
|
||||
}
|
||||
|
||||
private parseParam(): { ok: true; value: Param } | { ok: false } {
|
||||
private parseDelimitedList<T>(
|
||||
parseElem: (this: Parser, index: number) => Res<T>,
|
||||
endToken: string,
|
||||
delimiter: string,
|
||||
): T[] {
|
||||
this.step();
|
||||
if (this.test(endToken)) {
|
||||
this.step();
|
||||
return [];
|
||||
}
|
||||
let i = 0;
|
||||
const elems: T[] = [];
|
||||
const elemRes = parseElem.call(this, i);
|
||||
if (!elemRes.ok) {
|
||||
return [];
|
||||
}
|
||||
elems.push(elemRes.value);
|
||||
i += 1;
|
||||
while (this.test(delimiter)) {
|
||||
this.step();
|
||||
if (this.test(endToken)) {
|
||||
break;
|
||||
}
|
||||
const elemRes = parseElem.call(this, i);
|
||||
if (!elemRes.ok) {
|
||||
return [];
|
||||
}
|
||||
elems.push(elemRes.value);
|
||||
i += 1;
|
||||
}
|
||||
if (!this.test(endToken)) {
|
||||
this.report(`expected '${endToken}'`);
|
||||
return elems;
|
||||
}
|
||||
this.step();
|
||||
return elems;
|
||||
}
|
||||
|
||||
private parseParam(index?: number): Res<Param> {
|
||||
const pos = this.pos();
|
||||
if (this.test("ident")) {
|
||||
if (this.test("ident") || this.test("mut")) {
|
||||
let mut = false;
|
||||
if (this.test("mut")) {
|
||||
mut = true;
|
||||
this.step();
|
||||
}
|
||||
const ident = this.current().identValue!;
|
||||
this.step();
|
||||
if (this.test(":")) {
|
||||
this.step();
|
||||
const etype = this.parseEType();
|
||||
return { ok: true, value: { ident, etype, pos } };
|
||||
return {
|
||||
ok: true,
|
||||
value: this.astCreator.param({
|
||||
index,
|
||||
ident,
|
||||
mut,
|
||||
etype,
|
||||
pos,
|
||||
}),
|
||||
};
|
||||
}
|
||||
return { ok: true, value: { ident, pos } };
|
||||
return {
|
||||
ok: true,
|
||||
value: this.astCreator.param({ index, ident, mut, pos }),
|
||||
};
|
||||
}
|
||||
this.report("expected param");
|
||||
return { ok: false };
|
||||
@ -317,6 +436,17 @@ export class Parser {
|
||||
return this.stmt({ type: "let", param, value }, pos);
|
||||
}
|
||||
|
||||
private parseTypeAlias(): Stmt {
|
||||
const pos = this.pos();
|
||||
this.step();
|
||||
const paramResult = this.parseParam();
|
||||
if (!paramResult.ok) {
|
||||
return this.stmt({ type: "error" }, pos);
|
||||
}
|
||||
const param = paramResult.value;
|
||||
return this.stmt({ type: "type_alias", param }, pos);
|
||||
}
|
||||
|
||||
private parseAssign(): Stmt {
|
||||
const pos = this.pos();
|
||||
const subject = this.parseExpr();
|
||||
@ -444,6 +574,80 @@ export class Parser {
|
||||
return this.expr({ type: "for", decl, cond, incr, body }, pos);
|
||||
}
|
||||
|
||||
private parseArray(): Expr {
|
||||
const pos = this.pos();
|
||||
this.step();
|
||||
const exprs: Expr[] = [];
|
||||
if (!this.test("]")) {
|
||||
exprs.push(this.parseExpr());
|
||||
while (this.test(",")) {
|
||||
this.step();
|
||||
if (this.done() || this.test("]")) {
|
||||
break;
|
||||
}
|
||||
exprs.push(this.parseExpr());
|
||||
}
|
||||
}
|
||||
if (!this.test("]")) {
|
||||
this.report("expected ']'");
|
||||
return this.expr({ type: "error" }, pos);
|
||||
}
|
||||
this.step();
|
||||
return this.expr({ type: "array", exprs }, pos);
|
||||
}
|
||||
|
||||
private parseStruct(): Expr {
|
||||
const pos = this.pos();
|
||||
this.step();
|
||||
if (!this.test("{")) {
|
||||
this.report("expected '{'");
|
||||
return this.expr({ type: "error" }, pos);
|
||||
}
|
||||
this.step();
|
||||
const fields: Field[] = [];
|
||||
if (!this.test("}")) {
|
||||
const res = this.parseStructField();
|
||||
if (!res.ok) {
|
||||
return this.expr({ type: "error" }, res.pos!);
|
||||
}
|
||||
fields.push(res.value);
|
||||
while (this.test(",")) {
|
||||
this.step();
|
||||
if (this.done() || this.test("}")) {
|
||||
break;
|
||||
}
|
||||
const res = this.parseStructField();
|
||||
if (!res.ok) {
|
||||
return this.expr({ type: "error" }, res.pos!);
|
||||
}
|
||||
fields.push(res.value);
|
||||
}
|
||||
}
|
||||
if (!this.test("}")) {
|
||||
this.report("expected '}'");
|
||||
return this.expr({ type: "error" }, pos);
|
||||
}
|
||||
this.step();
|
||||
return this.expr({ type: "struct", fields }, pos);
|
||||
}
|
||||
|
||||
private parseStructField(): Res<Field> {
|
||||
const pos = this.pos();
|
||||
if (!this.test("ident")) {
|
||||
this.report("expected 'ident'");
|
||||
return { ok: false, pos };
|
||||
}
|
||||
const ident = this.current().identValue!;
|
||||
this.step();
|
||||
if (!this.test(":")) {
|
||||
this.report("expected ':'");
|
||||
return { ok: false, pos };
|
||||
}
|
||||
this.step();
|
||||
const expr = this.parseExpr();
|
||||
return { ok: true, value: { ident, expr, pos } };
|
||||
}
|
||||
|
||||
private parseIf(): Expr {
|
||||
const pos = this.pos();
|
||||
this.step();
|
||||
@ -586,54 +790,45 @@ export class Parser {
|
||||
const subject = this.parsePrefix();
|
||||
return this.expr({ type: "unary", unaryType, subject }, pos);
|
||||
}
|
||||
if (this.test("&")) {
|
||||
this.step();
|
||||
let type: "ref" | "ref_mut" = "ref";
|
||||
if (this.test("mut")) {
|
||||
this.step();
|
||||
type = "ref_mut";
|
||||
}
|
||||
const subject = this.parsePrefix();
|
||||
return this.expr({ type, subject }, pos);
|
||||
}
|
||||
if (this.test("*")) {
|
||||
this.step();
|
||||
const subject = this.parsePrefix();
|
||||
return this.expr({ type: "deref", subject }, pos);
|
||||
}
|
||||
return this.parsePostfix();
|
||||
}
|
||||
|
||||
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("(")) {
|
||||
this.step();
|
||||
let args: Expr[] = [];
|
||||
if (!this.test(")")) {
|
||||
args.push(this.parseExpr());
|
||||
while (this.test(",")) {
|
||||
this.step();
|
||||
if (this.test(")")) {
|
||||
break;
|
||||
}
|
||||
args.push(this.parseExpr());
|
||||
}
|
||||
}
|
||||
if (!this.test(")")) {
|
||||
this.report("expected ')'");
|
||||
return this.expr({ type: "error" }, pos);
|
||||
}
|
||||
this.step();
|
||||
subject = this.expr({ type: "call", subject, args }, pos);
|
||||
subject = this.parseCallTail(subject);
|
||||
continue;
|
||||
}
|
||||
if (this.test("::")) {
|
||||
subject = this.parsePathTail(subject);
|
||||
continue;
|
||||
}
|
||||
if (this.test("::<")) {
|
||||
subject = this.parseETypeArgsTail(subject);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
@ -641,12 +836,79 @@ 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() };
|
||||
}
|
||||
|
||||
private parseETypeArg(): Res<EType> {
|
||||
return { ok: true, value: this.parseEType() };
|
||||
}
|
||||
|
||||
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!;
|
||||
@ -680,6 +942,12 @@ export class Parser {
|
||||
this.step();
|
||||
return this.expr({ type: "group", expr }, pos);
|
||||
}
|
||||
if (this.test("[")) {
|
||||
return this.parseArray();
|
||||
}
|
||||
if (this.test("struct")) {
|
||||
return this.parseStruct();
|
||||
}
|
||||
if (this.test("{")) {
|
||||
return this.parseBlock();
|
||||
}
|
||||
@ -690,27 +958,36 @@ export class Parser {
|
||||
return this.parseLoop();
|
||||
}
|
||||
|
||||
this.report("expected expr", pos);
|
||||
this.report(`expected expr, got '${this.current().type}'`, pos);
|
||||
this.step();
|
||||
return this.expr({ type: "error" }, pos);
|
||||
}
|
||||
|
||||
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();
|
||||
const inner = this.parseEType();
|
||||
const subject = this.parseEType();
|
||||
if (!this.test("]")) {
|
||||
this.report("expected ']'", pos);
|
||||
return this.etype({ type: "error" }, pos);
|
||||
}
|
||||
this.step();
|
||||
return this.etype({ type: "array", inner }, pos);
|
||||
return this.etype({ type: "array", subject }, pos);
|
||||
}
|
||||
if (this.test("struct")) {
|
||||
this.step();
|
||||
@ -721,6 +998,26 @@ export class Parser {
|
||||
const fields = this.parseETypeStructFields();
|
||||
return this.etype({ type: "struct", fields }, pos);
|
||||
}
|
||||
if (this.test("&")) {
|
||||
this.step();
|
||||
let type: "ref" | "ref_mut" = "ref";
|
||||
if (this.test("mut")) {
|
||||
this.step();
|
||||
type = "ref_mut";
|
||||
}
|
||||
const subject = this.parseEType();
|
||||
return this.etype({ type, subject }, pos);
|
||||
}
|
||||
if (this.test("*")) {
|
||||
this.step();
|
||||
let type: "ptr" | "ptr_mut" = "ptr";
|
||||
if (this.test("mut")) {
|
||||
this.step();
|
||||
type = "ptr_mut";
|
||||
}
|
||||
const subject = this.parseEType();
|
||||
return this.etype({ type, subject }, pos);
|
||||
}
|
||||
this.report("expected type");
|
||||
return this.etype({ type: "error" }, pos);
|
||||
}
|
||||
@ -777,7 +1074,6 @@ export class Parser {
|
||||
}
|
||||
|
||||
private report(msg: string, pos = this.pos()) {
|
||||
console.log(`Parser: ${msg} at ${pos.line}:${pos.col}`);
|
||||
this.reporter.reportError({
|
||||
msg,
|
||||
pos,
|
||||
@ -786,8 +1082,8 @@ export class Parser {
|
||||
printStackTrace();
|
||||
}
|
||||
|
||||
private stmt(kind: StmtKind, pos: Pos): Stmt {
|
||||
return this.astCreator.stmt(kind, pos);
|
||||
private stmt(kind: StmtKind, pos: Pos, details?: StmtDetails): Stmt {
|
||||
return this.astCreator.stmt(kind, pos, details);
|
||||
}
|
||||
|
||||
private expr(kind: ExprKind, pos: Pos): Expr {
|
||||
|
@ -1,31 +1,88 @@
|
||||
import { Expr, Stmt } from "./ast.ts";
|
||||
import { EType, Expr, Stmt } from "./ast.ts";
|
||||
import {
|
||||
AstVisitor,
|
||||
visitEType,
|
||||
visitExpr,
|
||||
visitParam,
|
||||
VisitRes,
|
||||
visitStmt,
|
||||
visitStmts,
|
||||
} from "./ast_visitor.ts";
|
||||
import { printStackTrace, Reporter } from "./info.ts";
|
||||
import {
|
||||
EntryModSyms,
|
||||
FnSyms,
|
||||
GlobalSyms,
|
||||
LeafSyms,
|
||||
StaticSyms,
|
||||
ModSyms,
|
||||
Syms,
|
||||
} from "./resolver_syms.ts";
|
||||
import { Pos } from "./token.ts";
|
||||
|
||||
export class Resolver implements AstVisitor<[Syms]> {
|
||||
private root = new GlobalSyms();
|
||||
|
||||
public constructor(private reporter: Reporter) {
|
||||
}
|
||||
|
||||
public resolve(stmts: Stmt[]): VisitRes {
|
||||
const scopeSyms = new StaticSyms(this.root);
|
||||
this.scoutFnStmts(stmts, scopeSyms);
|
||||
visitStmts(stmts, this, scopeSyms);
|
||||
const syms = new EntryModSyms("root");
|
||||
this.scout(stmts, syms);
|
||||
visitStmts(stmts, this, syms);
|
||||
return "stop";
|
||||
}
|
||||
|
||||
private scout(stmts: Stmt[], syms: Syms) {
|
||||
for (const stmt of stmts) {
|
||||
if (stmt.kind.type === "fn") {
|
||||
if (syms.definedLocally(stmt.kind.ident)) {
|
||||
this.reportAlreadyDefined(stmt.kind.ident, stmt.pos, syms);
|
||||
return;
|
||||
}
|
||||
const ident = stmt.kind.ident;
|
||||
stmt.kind.sym = syms.define(ident, {
|
||||
ident: stmt.kind.ident,
|
||||
type: "fn",
|
||||
fullPath: `${syms.pathString()}::${ident}`,
|
||||
pos: stmt.pos,
|
||||
stmt,
|
||||
});
|
||||
} else if (stmt.kind.type === "type_alias") {
|
||||
const ident = stmt.kind.param.ident;
|
||||
if (syms.definedLocally(ident)) {
|
||||
this.reportAlreadyDefined(ident, stmt.pos, syms);
|
||||
return;
|
||||
}
|
||||
syms.define(ident, {
|
||||
ident,
|
||||
type: "type_alias",
|
||||
fullPath: `${syms.pathString()}::${ident}`,
|
||||
pos: stmt.kind.param.pos,
|
||||
stmt,
|
||||
param: stmt.kind.param,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visitModStmt(stmt: Stmt, syms: Syms): VisitRes {
|
||||
if (stmt.kind.type !== "mod") {
|
||||
throw new Error("expected let statement");
|
||||
}
|
||||
const modSyms = new ModSyms(syms, stmt.kind.ident);
|
||||
const { mod, ident } = stmt.kind;
|
||||
this.scout(mod.ast, modSyms);
|
||||
visitStmts(mod.ast, this, modSyms);
|
||||
|
||||
if (syms.definedLocally(ident)) {
|
||||
this.reportAlreadyDefined(ident, stmt.pos, syms);
|
||||
return;
|
||||
}
|
||||
syms.define(ident, {
|
||||
type: "mod",
|
||||
ident,
|
||||
fullPath: `${syms.pathString()}::${ident}`,
|
||||
pos: stmt.pos,
|
||||
syms: modSyms,
|
||||
});
|
||||
|
||||
return "stop";
|
||||
}
|
||||
|
||||
@ -39,9 +96,10 @@ export class Resolver implements AstVisitor<[Syms]> {
|
||||
this.reportAlreadyDefined(ident, stmt.pos, syms);
|
||||
return;
|
||||
}
|
||||
syms.define(ident, {
|
||||
stmt.kind.param.sym = syms.define(ident, {
|
||||
ident,
|
||||
type: "let",
|
||||
fullPath: ident,
|
||||
pos: stmt.kind.param.pos,
|
||||
stmt,
|
||||
param: stmt.kind.param,
|
||||
@ -49,23 +107,11 @@ export class Resolver implements AstVisitor<[Syms]> {
|
||||
return "stop";
|
||||
}
|
||||
|
||||
private scoutFnStmts(stmts: Stmt[], syms: Syms) {
|
||||
for (const stmt of stmts) {
|
||||
if (stmt.kind.type !== "fn") {
|
||||
continue;
|
||||
}
|
||||
if (syms.definedLocally(stmt.kind.ident)) {
|
||||
this.reportAlreadyDefined(stmt.kind.ident, stmt.pos, syms);
|
||||
return;
|
||||
}
|
||||
const ident = stmt.kind.ident;
|
||||
syms.define(ident, {
|
||||
ident: stmt.kind.ident,
|
||||
type: "fn",
|
||||
pos: stmt.pos,
|
||||
stmt,
|
||||
});
|
||||
visitTypeAliasStmt(stmt: Stmt, _syms: Syms): VisitRes {
|
||||
if (stmt.kind.type !== "type_alias") {
|
||||
throw new Error("expected type_alias statement");
|
||||
}
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
visitFnStmt(stmt: Stmt, syms: Syms): VisitRes {
|
||||
@ -73,18 +119,37 @@ export class Resolver implements AstVisitor<[Syms]> {
|
||||
throw new Error("expected fn statement");
|
||||
}
|
||||
const fnScopeSyms = new FnSyms(syms);
|
||||
for (const param of stmt.kind.params) {
|
||||
for (const param of stmt.kind.genericParams ?? []) {
|
||||
if (fnScopeSyms.definedLocally(param.ident)) {
|
||||
this.reportAlreadyDefined(param.ident, param.pos, syms);
|
||||
continue;
|
||||
}
|
||||
fnScopeSyms.define(param.ident, {
|
||||
ident: param.ident,
|
||||
type: "generic",
|
||||
fullPath: param.ident,
|
||||
pos: param.pos,
|
||||
stmt,
|
||||
genericParam: param,
|
||||
});
|
||||
}
|
||||
for (const param of stmt.kind.params) {
|
||||
if (fnScopeSyms.definedLocally(param.ident)) {
|
||||
this.reportAlreadyDefined(param.ident, param.pos, syms);
|
||||
continue;
|
||||
}
|
||||
visitParam(param, this, fnScopeSyms);
|
||||
fnScopeSyms.define(param.ident, {
|
||||
ident: param.ident,
|
||||
type: "fn_param",
|
||||
fullPath: param.ident,
|
||||
pos: param.pos,
|
||||
param,
|
||||
});
|
||||
}
|
||||
if (stmt.kind.returnType) {
|
||||
visitEType(stmt.kind.returnType, this, fnScopeSyms);
|
||||
}
|
||||
visitExpr(stmt.kind.body, this, fnScopeSyms);
|
||||
return "stop";
|
||||
}
|
||||
@ -93,18 +158,51 @@ 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, sym };
|
||||
return "stop";
|
||||
}
|
||||
|
||||
visitPathExpr(expr: Expr, syms: Syms): VisitRes {
|
||||
if (expr.kind.type !== "path") {
|
||||
throw new Error("expected ident");
|
||||
}
|
||||
visitExpr(expr.kind.subject, this, syms);
|
||||
if (expr.kind.subject.kind.type !== "sym") {
|
||||
throw new Error("this error is not handled properly");
|
||||
}
|
||||
const subjectSym = expr.kind.subject.kind.sym;
|
||||
if (subjectSym.type !== "mod") {
|
||||
this.reporter.reportError({
|
||||
reporter: "Resolver",
|
||||
msg: `path expression are not implemented for '${subjectSym.type}' symbols`,
|
||||
pos: expr.pos,
|
||||
});
|
||||
printStackTrace();
|
||||
return "stop";
|
||||
}
|
||||
const getRes = subjectSym.syms.get(expr.kind.ident);
|
||||
if (!getRes.ok) {
|
||||
this.reportUseOfUndefined(
|
||||
expr.kind.ident,
|
||||
expr.pos,
|
||||
subjectSym.syms,
|
||||
);
|
||||
return "stop";
|
||||
}
|
||||
|
||||
expr.kind = {
|
||||
type: "sym",
|
||||
ident: ident.value,
|
||||
sym,
|
||||
ident: expr.kind.ident,
|
||||
sym: getRes.sym,
|
||||
};
|
||||
|
||||
return "stop";
|
||||
}
|
||||
|
||||
@ -113,7 +211,7 @@ export class Resolver implements AstVisitor<[Syms]> {
|
||||
throw new Error();
|
||||
}
|
||||
const childSyms = new LeafSyms(syms);
|
||||
this.scoutFnStmts(expr.kind.stmts, childSyms);
|
||||
this.scout(expr.kind.stmts, childSyms);
|
||||
visitStmts(expr.kind.stmts, this, childSyms);
|
||||
if (expr.kind.expr) {
|
||||
visitExpr(expr.kind.expr, this, childSyms);
|
||||
@ -134,6 +232,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",
|
||||
|
@ -1,66 +1,112 @@
|
||||
import { Sym } from "./ast.ts";
|
||||
import type { Sym } from "./ast.ts";
|
||||
|
||||
export type SymMap = { [ident: string]: Sym };
|
||||
|
||||
type GetRes = { ok: true; sym: Sym } | { ok: false };
|
||||
|
||||
export interface Syms {
|
||||
define(ident: string, sym: Sym): void;
|
||||
define(ident: string, sym: Sym): Sym;
|
||||
definedLocally(ident: string): boolean;
|
||||
get(ident: string): { ok: true; sym: Sym } | { ok: false };
|
||||
get(ident: string): GetRes;
|
||||
getPub(ident: string): GetRes;
|
||||
rootMod(): Sym;
|
||||
pathString(): string;
|
||||
}
|
||||
|
||||
export class GlobalSyms implements Syms {
|
||||
export class EntryModSyms implements Syms {
|
||||
private syms: SymMap = {};
|
||||
|
||||
public constructor() {}
|
||||
public constructor(private modName: string) {}
|
||||
|
||||
public define(ident: string, sym: Sym) {
|
||||
public define(ident: string, sym: Sym): Sym {
|
||||
if (sym.type === "let") {
|
||||
this.define(ident, {
|
||||
return this.define(ident, {
|
||||
...sym,
|
||||
type: "let_static",
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.syms[ident] = sym;
|
||||
return sym;
|
||||
}
|
||||
|
||||
public definedLocally(ident: string): boolean {
|
||||
return ident in this.syms;
|
||||
}
|
||||
|
||||
public get(ident: string): { ok: true; sym: Sym } | { ok: false } {
|
||||
public get(ident: string): GetRes {
|
||||
if (ident in this.syms) {
|
||||
return { ok: true, sym: this.syms[ident] };
|
||||
}
|
||||
return { ok: false };
|
||||
}
|
||||
|
||||
public getPub(ident: string): GetRes {
|
||||
if (ident in this.syms) {
|
||||
return { ok: true, sym: this.syms[ident] };
|
||||
}
|
||||
return { ok: false };
|
||||
}
|
||||
|
||||
public rootMod(): Sym {
|
||||
return {
|
||||
type: "mod",
|
||||
ident: this.modName,
|
||||
fullPath: this.modName,
|
||||
syms: this,
|
||||
};
|
||||
}
|
||||
|
||||
public pathString(): string {
|
||||
return this.modName;
|
||||
}
|
||||
}
|
||||
|
||||
export class StaticSyms implements Syms {
|
||||
export class ModSyms implements Syms {
|
||||
private syms: SymMap = {};
|
||||
|
||||
public constructor(private parent: GlobalSyms) {}
|
||||
public constructor(private parent: Syms, private modName: string) {
|
||||
this.syms["super"] = {
|
||||
type: "mod",
|
||||
ident: "super",
|
||||
fullPath: this.pathString(),
|
||||
syms: this.parent,
|
||||
};
|
||||
}
|
||||
|
||||
public define(ident: string, sym: Sym) {
|
||||
public define(ident: string, sym: Sym): Sym {
|
||||
if (sym.type === "let") {
|
||||
this.define(ident, {
|
||||
return this.define(ident, {
|
||||
...sym,
|
||||
type: "let_static",
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.syms[ident] = sym;
|
||||
return this.syms[ident] = sym;
|
||||
}
|
||||
|
||||
public definedLocally(ident: string): boolean {
|
||||
return ident in this.syms;
|
||||
}
|
||||
|
||||
public get(ident: string): { ok: true; sym: Sym } | { ok: false } {
|
||||
public get(ident: string): GetRes {
|
||||
if (ident in this.syms) {
|
||||
return { ok: true, sym: this.syms[ident] };
|
||||
}
|
||||
return this.parent.get(ident);
|
||||
return { ok: false };
|
||||
}
|
||||
|
||||
public getPub(ident: string): GetRes {
|
||||
if (ident in this.syms) {
|
||||
return { ok: true, sym: this.syms[ident] };
|
||||
}
|
||||
return { ok: false };
|
||||
}
|
||||
|
||||
public rootMod(): Sym {
|
||||
return this.parent.rootMod();
|
||||
}
|
||||
|
||||
public pathString(): string {
|
||||
return `${this.parent.pathString()}::${this.modName}`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,28 +115,43 @@ export class FnSyms implements Syms {
|
||||
|
||||
public constructor(private parent: Syms) {}
|
||||
|
||||
public define(ident: string, sym: Sym) {
|
||||
public define(ident: string, sym: Sym): Sym {
|
||||
if (sym.type === "let") {
|
||||
this.define(ident, {
|
||||
return this.define(ident, {
|
||||
...sym,
|
||||
type: "closure",
|
||||
inner: sym,
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.syms[ident] = sym;
|
||||
return sym;
|
||||
}
|
||||
|
||||
public definedLocally(ident: string): boolean {
|
||||
return ident in this.syms;
|
||||
}
|
||||
|
||||
public get(ident: string): { ok: true; sym: Sym } | { ok: false } {
|
||||
public get(ident: string): GetRes {
|
||||
if (ident in this.syms) {
|
||||
return { ok: true, sym: this.syms[ident] };
|
||||
}
|
||||
return this.parent.get(ident);
|
||||
}
|
||||
|
||||
public getPub(ident: string): GetRes {
|
||||
if (ident in this.syms) {
|
||||
return { ok: true, sym: this.syms[ident] };
|
||||
}
|
||||
return { ok: false };
|
||||
}
|
||||
|
||||
public rootMod(): Sym {
|
||||
return this.parent.rootMod();
|
||||
}
|
||||
|
||||
public pathString(): string {
|
||||
return this.parent.pathString();
|
||||
}
|
||||
}
|
||||
|
||||
export class LeafSyms implements Syms {
|
||||
@ -98,18 +159,34 @@ export class LeafSyms implements Syms {
|
||||
|
||||
public constructor(private parent: Syms) {}
|
||||
|
||||
public define(ident: string, sym: Sym) {
|
||||
public define(ident: string, sym: Sym): Sym {
|
||||
this.syms[ident] = sym;
|
||||
return sym;
|
||||
}
|
||||
|
||||
public definedLocally(ident: string): boolean {
|
||||
return ident in this.syms;
|
||||
}
|
||||
|
||||
public get(ident: string): { ok: true; sym: Sym } | { ok: false } {
|
||||
public get(ident: string): GetRes {
|
||||
if (ident in this.syms) {
|
||||
return { ok: true, sym: this.syms[ident] };
|
||||
}
|
||||
return this.parent.get(ident);
|
||||
}
|
||||
|
||||
public getPub(ident: string): GetRes {
|
||||
if (ident in this.syms) {
|
||||
return { ok: true, sym: this.syms[ident] };
|
||||
}
|
||||
return { ok: false };
|
||||
}
|
||||
|
||||
public rootMod(): Sym {
|
||||
return this.parent.rootMod();
|
||||
}
|
||||
|
||||
public pathString(): string {
|
||||
return this.parent.pathString();
|
||||
}
|
||||
}
|
||||
|
@ -5,27 +5,82 @@ export type VType =
|
||||
| { type: "int" }
|
||||
| { type: "string" }
|
||||
| { type: "bool" }
|
||||
| { type: "array"; inner: VType }
|
||||
| { type: "ref"; subject: VType }
|
||||
| { type: "ref_mut"; subject: VType }
|
||||
| { type: "ptr"; subject: VType }
|
||||
| { type: "ptr_mut"; subject: VType }
|
||||
| { type: "array"; subject: VType }
|
||||
| { type: "struct"; fields: VTypeParam[] }
|
||||
| { type: "fn"; params: VTypeParam[]; returnType: VType };
|
||||
| {
|
||||
type: "fn";
|
||||
genericParams?: VTypeGenericParam[];
|
||||
params: VTypeParam[];
|
||||
returnType: VType;
|
||||
stmtId: number;
|
||||
}
|
||||
| { type: "generic"; param: VTypeGenericParam }
|
||||
| {
|
||||
type: "generic_spec";
|
||||
subject: VType;
|
||||
genericArgs: GenericArgsMap;
|
||||
};
|
||||
|
||||
export type VTypeParam = {
|
||||
ident: string;
|
||||
mut: boolean;
|
||||
vtype: VType;
|
||||
};
|
||||
|
||||
export function vtypesEqual(a: VType, b: VType): boolean {
|
||||
if (a.type !== b.type) {
|
||||
return false;
|
||||
}
|
||||
export type VTypeGenericParam = {
|
||||
id: number;
|
||||
ident: string;
|
||||
};
|
||||
|
||||
export type GenericArgsMap = { [id: number]: VType };
|
||||
|
||||
export function vtypesEqual(
|
||||
a: VType,
|
||||
b: VType,
|
||||
generics?: GenericArgsMap,
|
||||
): boolean {
|
||||
if (
|
||||
["error", "unknown", "null", "int", "string", "bool"]
|
||||
.includes(a.type)
|
||||
.includes(a.type) && a.type === b.type
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (a.type === "ref" && b.type === "ref") {
|
||||
return vtypesEqual(a.subject, b.subject, generics);
|
||||
}
|
||||
if (a.type === "ref_mut" && b.type === "ref_mut") {
|
||||
return vtypesEqual(a.subject, b.subject, generics);
|
||||
}
|
||||
if (a.type === "ptr" && b.type === "ptr") {
|
||||
return vtypesEqual(a.subject, b.subject, generics);
|
||||
}
|
||||
if (a.type === "ptr_mut" && b.type === "ptr_mut") {
|
||||
return vtypesEqual(a.subject, b.subject, generics);
|
||||
}
|
||||
if (a.type === "array" && b.type === "array") {
|
||||
return vtypesEqual(a.inner, b.inner);
|
||||
return vtypesEqual(a.subject, b.subject, generics);
|
||||
}
|
||||
if (a.type === "struct" && b.type === "struct") {
|
||||
if (a.fields.length !== b.fields.length) {
|
||||
return false;
|
||||
}
|
||||
const match = a.fields
|
||||
.map((af) => ({
|
||||
ident: af.ident,
|
||||
af,
|
||||
bf: b.fields.find((bf) => bf.ident === af.ident),
|
||||
}));
|
||||
if (match.some((m) => m.bf === undefined)) {
|
||||
return false;
|
||||
}
|
||||
if (match.some((m) => !vtypesEqual(m.af.vtype, m.bf!.vtype))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (a.type === "fn" && b.type === "fn") {
|
||||
if (a.params.length !== b.params.length) {
|
||||
@ -36,11 +91,41 @@ export function vtypesEqual(a: VType, b: VType): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return vtypesEqual(a.returnType, b.returnType);
|
||||
return vtypesEqual(a.returnType, b.returnType, generics);
|
||||
}
|
||||
if (a.type === "generic" && b.type === "generic") {
|
||||
return a.param.id === b.param.id;
|
||||
}
|
||||
if (
|
||||
(a.type === "generic" || b.type === "generic") &&
|
||||
generics !== undefined
|
||||
) {
|
||||
if (generics === undefined) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
const generic = a.type === "generic" ? a : b;
|
||||
const concrete = a.type === "generic" ? b : a;
|
||||
|
||||
const genericType = extractGenericType(generic, generics);
|
||||
return vtypesEqual(genericType, concrete, generics);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function extractGenericType(
|
||||
generic: VType,
|
||||
generics: GenericArgsMap,
|
||||
): VType {
|
||||
if (generic.type !== "generic") {
|
||||
return generic;
|
||||
}
|
||||
if (!(generic.param.id in generics)) {
|
||||
throw new Error("generic not found (not supposed to happen)");
|
||||
}
|
||||
return generics[generic.param.id];
|
||||
}
|
||||
|
||||
export function vtypeToString(vtype: VType): string {
|
||||
if (
|
||||
["error", "unknown", "null", "int", "string", "bool"]
|
||||
@ -48,15 +133,36 @@ export function vtypeToString(vtype: VType): string {
|
||||
) {
|
||||
return vtype.type;
|
||||
}
|
||||
if (vtype.type === "ref") {
|
||||
return `&${vtypeToString(vtype.subject)}`;
|
||||
}
|
||||
if (vtype.type === "ref_mut") {
|
||||
return `&mut ${vtypeToString(vtype.subject)}`;
|
||||
}
|
||||
if (vtype.type === "ptr") {
|
||||
return `*${vtypeToString(vtype.subject)}`;
|
||||
}
|
||||
if (vtype.type === "ptr_mut") {
|
||||
return `*mut ${vtypeToString(vtype.subject)}`;
|
||||
}
|
||||
if (vtype.type === "array") {
|
||||
return `[${vtypeToString(vtype.inner)}]`;
|
||||
return `[${vtypeToString(vtype.subject)}]`;
|
||||
}
|
||||
if (vtype.type === "struct") {
|
||||
const fields = vtype.fields
|
||||
.map((field) => `${field.ident}: ${vtypeToString(field.vtype)}`)
|
||||
.join(", ");
|
||||
return `struct { ${fields} }`;
|
||||
}
|
||||
if (vtype.type === "fn") {
|
||||
const paramString = vtype.params.map((param) =>
|
||||
`${param.ident}: ${vtypeToString(param.vtype)}`
|
||||
)
|
||||
.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}'`);
|
||||
}
|
||||
|
@ -7,7 +7,8 @@ if exists("b:current_syntax")
|
||||
finish
|
||||
endif
|
||||
|
||||
syn keyword Keyword break return let fn loop if else struct import or and not while for in
|
||||
|
||||
syn keyword Keyword break return let fn loop if else struct import or and not while for in mod pub
|
||||
syn keyword Special null
|
||||
syn keyword Type int string bool
|
||||
syn keyword Boolean true false
|
||||
@ -37,6 +38,9 @@ syn match Number '0[0-7]\+'
|
||||
syn match Number '0x[0-9a-fA-F]\+'
|
||||
syn match Number '0b[01]\+'
|
||||
|
||||
syn match Character "'[^\\]'"
|
||||
syn match Character "'\\.'"
|
||||
|
||||
syn region String start=+"+ skip=+\\"+ end=+"+
|
||||
|
||||
syn keyword Todo contained TODO FIXME XXX NOTE
|
||||
@ -44,11 +48,23 @@ syn match Comment "//.*$" contains=Todo
|
||||
|
||||
syn region Comment start=+/\*+ end=+\*/+ contains=Todo
|
||||
|
||||
|
||||
syn match Identifier '[a-z_]\w*'
|
||||
syn match Type '[A-Z]\w*'
|
||||
|
||||
syn match Function '[a-zA-Z_]\w*\ze('
|
||||
|
||||
syn match Function '[a-zA-Z_]\w*\ze\s\{-}(.\{-})'
|
||||
|
||||
syn match sligePath '[a-zA-Z_]\w*\ze\s\{-}::'
|
||||
syn match Function '[a-zA-Z_]\w*\ze\s\{-}::<.\{-}>'
|
||||
|
||||
syn match Function ' \zs[a-zA-Z_]\w*\ze\s\{-}<.\{-}>\s\{-}(.\{-})'
|
||||
|
||||
syn region sligeBlock start="{" end="}" transparent fold
|
||||
|
||||
syn region sligeAnno start="#!\?\[" end="]" contains=Identifier,Type
|
||||
|
||||
hi def link sligeAnno PreProc
|
||||
hi def link sligePath Include
|
||||
|
||||
let b:current_syntax = "slige"
|
||||
|
@ -53,6 +53,17 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"chars": {
|
||||
"name": "string.quoted.double.slige",
|
||||
"begin": "'",
|
||||
"end": "'",
|
||||
"patterns": [
|
||||
{
|
||||
"name": "constant.character.escape.slige",
|
||||
"match": "\\\\."
|
||||
}
|
||||
]
|
||||
},
|
||||
"numbers": {
|
||||
"patterns": [
|
||||
{
|
||||
|
@ -1,4 +1,7 @@
|
||||
fn print(msg: string) #[builtin(print)] {
|
||||
//
|
||||
|
||||
#[builtin(Print)]
|
||||
fn print(msg: string) {
|
||||
"hello" + 0
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,14 @@ fn add(a: int, b: int) -> int {
|
||||
|
||||
fn main() -> int {
|
||||
let result = 0;
|
||||
let i = 0;
|
||||
|
||||
let a = 0;
|
||||
let b = a;
|
||||
let c = b;
|
||||
let d = c;
|
||||
|
||||
|
||||
let i = c;
|
||||
loop {
|
||||
if i >= 10 {
|
||||
break;
|
||||
|
21
examples/generic_array.slg
Normal file
21
examples/generic_array.slg
Normal file
@ -0,0 +1,21 @@
|
||||
mod std;
|
||||
|
||||
fn main() {
|
||||
let strings = std::array_new::<string>();
|
||||
std::array_push(strings, "hello");
|
||||
std::array_push(strings, "world");
|
||||
|
||||
let ints = std::array_new::<int>();
|
||||
std::array_push(ints, 1);
|
||||
std::array_push(ints, 2);
|
||||
|
||||
for v in strings {
|
||||
std::println(v)
|
||||
}
|
||||
|
||||
for v in ints {
|
||||
std::println(std::itos(v))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,12 +56,50 @@ fn input(prompt: string) -> string {
|
||||
|
||||
//
|
||||
|
||||
fn is_prime(n: int) -> bool {
|
||||
if n == 1 or n == 0{
|
||||
return false;
|
||||
fn min(a: int, b: int) -> int {
|
||||
if b < a { b } else { a }
|
||||
}
|
||||
|
||||
fn max(a: int, b: int) -> int {
|
||||
if a < b { b } else { b }
|
||||
}
|
||||
|
||||
fn sqrt(n: int) -> int {
|
||||
let low = min(1, n);
|
||||
let high = max(1, n);
|
||||
let mid = 0;
|
||||
|
||||
while 100 * low * low < n {
|
||||
low = low * 10;
|
||||
}
|
||||
|
||||
for (let i = 2; i < n; i += 1) {
|
||||
while (high * high) / 100 > n {
|
||||
high = high / 10;
|
||||
}
|
||||
|
||||
for (let i = 0; i < 100; i += 1) {
|
||||
mid = (low + high) / 2;
|
||||
if mid * mid == n {
|
||||
return mid;
|
||||
}
|
||||
if mid * mid > n {
|
||||
high = mid;
|
||||
} else {
|
||||
low = mid;
|
||||
}
|
||||
}
|
||||
mid
|
||||
}
|
||||
|
||||
fn is_prime(n: int) -> bool {
|
||||
if n == 0{
|
||||
return false;
|
||||
}
|
||||
if n == 1 {
|
||||
return true;
|
||||
}
|
||||
let n_root = sqrt(n);
|
||||
for (let i = 2; i < n_root; i += 1) {
|
||||
if remainder(n, i) == 0 {
|
||||
return false;
|
||||
}
|
||||
@ -70,7 +108,7 @@ fn is_prime(n: int) -> bool {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
for (let i = 1; i < 10000; i += 1) {
|
||||
for (let i = 1; i <= 10000; i += 1) {
|
||||
if is_prime(i) {
|
||||
print(int_to_string(i) + " ");
|
||||
}
|
||||
|
7
examples/refs.slg
Normal file
7
examples/refs.slg
Normal file
@ -0,0 +1,7 @@
|
||||
//
|
||||
|
||||
fn main() {
|
||||
let a = 5;
|
||||
let b: &int = &a;
|
||||
}
|
||||
|
@ -1,56 +1,3 @@
|
||||
|
||||
fn string_push_char(str: string, value: int) -> string #[builtin(StringPushChar)] {}
|
||||
fn string_char_at(str: string, index: int) -> int #[builtin(StringCharAt)] {}
|
||||
fn string_length(str: string) -> int #[builtin(StringLength)] {}
|
||||
|
||||
fn string_array_new() -> [string] #[builtin(ArrayNew)] {}
|
||||
fn string_array_push(array: [string], value: string) #[builtin(ArrayPush)] {}
|
||||
fn string_array_length(array: [string]) -> int #[builtin(ArrayLength)] {}
|
||||
fn string_array_at(array: [string], index: int) -> string #[builtin(ArrayAt)] {}
|
||||
|
||||
fn int_array_new() -> [int] #[builtin(ArrayNew)] {}
|
||||
fn int_array_push(array: [int], value: int) #[builtin(ArrayPush)] {}
|
||||
fn int_array_length(array: [int]) -> int #[builtin(ArrayLength)] {}
|
||||
fn int_array_at(array: [int], index: int) -> int #[builtin(ArrayAt)] {}
|
||||
|
||||
fn file_open(filename: string, mode: string) -> int #[builtin(FileOpen)] {}
|
||||
fn file_close(file: int) #[builtin(FileClose)] {}
|
||||
fn file_write_string(file: int, content: string) -> int #[builtin(FileWriteString)] {}
|
||||
fn file_read_char(file: int) -> int #[builtin(FileReadChar)] {}
|
||||
fn file_read_to_string(file: int) -> string #[builtin(FileReadToString)] {}
|
||||
fn file_flush(file: int) #[builtin(FileFlush)] {}
|
||||
fn file_eof(file: int) -> bool #[builtin(FileEof)] {}
|
||||
|
||||
fn stdin() -> int { 0 }
|
||||
fn stdout() -> int { 1 }
|
||||
fn stderr() -> int { 2 }
|
||||
|
||||
fn file_read_line(file: int) -> string {
|
||||
let line = "";
|
||||
loop {
|
||||
if file_eof(file) {
|
||||
break;
|
||||
}
|
||||
let ch = file_read_char(file);
|
||||
if ch == "\n"[0] {
|
||||
break;
|
||||
}
|
||||
line = string_push_char(line, ch);
|
||||
}
|
||||
line
|
||||
}
|
||||
|
||||
fn print(msg: string) #[builtin(Print)] {}
|
||||
fn println(msg: string) { print(msg + "\n") }
|
||||
|
||||
fn input(prompt: string) -> string {
|
||||
print("> ");
|
||||
file_flush(stdout());
|
||||
file_read_line(stdin())
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
fn main() {
|
||||
let i = 0;
|
||||
while i < 3 {
|
||||
@ -63,18 +10,34 @@ fn main() {
|
||||
for char in chars {
|
||||
println(string_push_char("", char));
|
||||
}
|
||||
|
||||
let values = array_new::<int>();
|
||||
array_push(values, 10);
|
||||
array_push(values, 20);
|
||||
array_push(values, 30);
|
||||
|
||||
let pairs = array_new::<[int]>();
|
||||
|
||||
for (let i = 0; i < array_length(values); i += 1) {
|
||||
let pair = array_new::<int>();
|
||||
array_push(pair, i);
|
||||
array_push(pair, values[i]);
|
||||
array_push(pairs, pair);
|
||||
}
|
||||
|
||||
for pair in pairs {
|
||||
println("values[" + itos(pair[0]) + "] = " + itos(pair[1]));
|
||||
}
|
||||
}
|
||||
|
||||
fn string_to_array(value: string) -> [int] {
|
||||
let result = int_array_new();
|
||||
let result = array_new::<int>();
|
||||
let length = string_length(value);
|
||||
|
||||
for (let i = 0; i < length; i += 1) {
|
||||
int_array_push(result, value[i]);
|
||||
array_push(result, value[i]);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
22
examples/transient_variable.slg
Normal file
22
examples/transient_variable.slg
Normal file
@ -0,0 +1,22 @@
|
||||
// mod std;
|
||||
|
||||
fn black_box(v: int) { }
|
||||
|
||||
fn add(a: int, b: int) -> int {
|
||||
let s = a + b;
|
||||
if false {}
|
||||
s
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let a = 5;
|
||||
|
||||
loop {
|
||||
a = 3;
|
||||
}
|
||||
|
||||
let b = a;
|
||||
let c = b;
|
||||
|
||||
black_box(b);
|
||||
}
|
6
examples/unused_variable.slg
Normal file
6
examples/unused_variable.slg
Normal file
@ -0,0 +1,6 @@
|
||||
// mod std;
|
||||
|
||||
fn main() {
|
||||
let a = 5;
|
||||
let b = a;
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
#include "alloc.hpp"
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using namespace sliger::heap;
|
||||
|
||||
@ -14,3 +15,18 @@ auto Array::at(int32_t index) & -> Value&
|
||||
}
|
||||
return values.at(static_cast<size_t>(index));
|
||||
}
|
||||
|
||||
auto Struct::at(const std::string& field) & -> Value&
|
||||
{
|
||||
if (this->fields.find(field) == this->fields.end()) {
|
||||
std::cout << std::format(
|
||||
"field name not in struct, got: \"{}\"\n", field);
|
||||
exit(1);
|
||||
}
|
||||
return this->fields.at(field);
|
||||
}
|
||||
|
||||
void Struct::assign(const std::string& field, Value&& value)
|
||||
{
|
||||
this->fields.insert_or_assign(field, value);
|
||||
}
|
||||
|
@ -19,6 +19,9 @@ struct Array {
|
||||
|
||||
struct Struct {
|
||||
std::unordered_map<std::string, Value> fields;
|
||||
|
||||
auto at(const std::string&) & -> Value&;
|
||||
void assign(const std::string&, Value&& value);
|
||||
};
|
||||
|
||||
enum class AllocType {
|
||||
@ -202,7 +205,7 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
size_t max_size = 4;
|
||||
size_t max_size = 512;
|
||||
|
||||
std::vector<AllocItem> heap_1;
|
||||
std::vector<AllocItem> heap_2;
|
||||
|
@ -41,7 +41,8 @@ enum class Op : uint32_t {
|
||||
};
|
||||
|
||||
enum class Builtin : uint32_t {
|
||||
IntToString = 0x00,
|
||||
Exit = 0x00,
|
||||
IntToString = 0x01,
|
||||
StringConcat = 0x10,
|
||||
StringEqual = 0x11,
|
||||
StringCharAt = 0x12,
|
||||
@ -53,7 +54,9 @@ enum class Builtin : uint32_t {
|
||||
ArrayPush = 0x22,
|
||||
ArrayAt = 0x23,
|
||||
ArrayLength = 0x24,
|
||||
StructSet = 0x30,
|
||||
StructNew = 0x30,
|
||||
StructSet = 0x31,
|
||||
StructAt = 0x32,
|
||||
Print = 0x40,
|
||||
FileOpen = 0x41,
|
||||
FileClose = 0x42,
|
||||
|
@ -289,6 +289,11 @@ void VM::run_instruction()
|
||||
this->current_pos = { index, line, col };
|
||||
break;
|
||||
}
|
||||
default:
|
||||
std::cerr << std::format("unrecognized instruction '{}', pc = {}",
|
||||
std::to_underlying(op), this->pc);
|
||||
std::exit(1);
|
||||
break;
|
||||
}
|
||||
this->instruction_counter += 1;
|
||||
}
|
||||
@ -300,9 +305,15 @@ void VM::run_builtin(Builtin builtin_id)
|
||||
maybe_builtin_to_string(static_cast<uint32_t>(builtin_id)));
|
||||
}
|
||||
switch (builtin_id) {
|
||||
case Builtin::Exit: {
|
||||
assert_stack_has(1);
|
||||
auto status_code = stack_pop().as_int().value;
|
||||
std::exit(status_code);
|
||||
break;
|
||||
}
|
||||
case Builtin::IntToString: {
|
||||
assert_stack_has(1);
|
||||
auto number = static_cast<int32_t>(stack_pop().as_int().value);
|
||||
auto number = stack_pop().as_int().value;
|
||||
auto str = std::to_string(number);
|
||||
stack_push(String(str));
|
||||
break;
|
||||
@ -325,12 +336,11 @@ void VM::run_builtin(Builtin builtin_id)
|
||||
run_array_builtin(builtin_id);
|
||||
break;
|
||||
|
||||
case Builtin::StructSet: {
|
||||
assert_stack_has(2);
|
||||
std::cerr << std::format("not implemented\n");
|
||||
std::exit(1);
|
||||
case Builtin::StructNew:
|
||||
case Builtin::StructSet:
|
||||
case Builtin::StructAt:
|
||||
run_struct_builtin(builtin_id);
|
||||
break;
|
||||
}
|
||||
|
||||
case Builtin::Print:
|
||||
case Builtin::FileOpen:
|
||||
@ -401,6 +411,7 @@ void VM::run_string_builtin(Builtin builtin_id)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void VM::run_array_builtin(Builtin builtin_id)
|
||||
{
|
||||
switch (builtin_id) {
|
||||
@ -450,6 +461,40 @@ void VM::run_array_builtin(Builtin builtin_id)
|
||||
}
|
||||
}
|
||||
|
||||
void VM::run_struct_builtin(Builtin builtin_id)
|
||||
{
|
||||
switch (builtin_id) {
|
||||
case Builtin::StructNew: {
|
||||
auto alloc_res = this->heap.alloc<heap::AllocType::Struct>();
|
||||
stack_push(Ptr(alloc_res.val()));
|
||||
break;
|
||||
}
|
||||
case Builtin::StructSet: {
|
||||
assert_stack_has(2);
|
||||
auto field = stack_pop().as_string().value;
|
||||
auto struct_ptr = stack_pop().as_ptr().value;
|
||||
auto value = stack_pop();
|
||||
|
||||
this->heap.at(struct_ptr)
|
||||
.val()
|
||||
->as_struct()
|
||||
.assign(field, std::move(value));
|
||||
stack_push(Null());
|
||||
break;
|
||||
}
|
||||
case Builtin::StructAt: {
|
||||
assert_stack_has(2);
|
||||
auto field = stack_pop().as_string().value;
|
||||
auto struct_ptr = stack_pop().as_ptr().value;
|
||||
|
||||
stack_push(this->heap.at(struct_ptr).val()->as_struct().at(field));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void VM::run_file_builtin(Builtin builtin_id)
|
||||
{
|
||||
switch (builtin_id) {
|
||||
|
@ -199,6 +199,7 @@ private:
|
||||
void run_builtin(Builtin builtin_id);
|
||||
void run_string_builtin(Builtin builtin_id);
|
||||
void run_array_builtin(Builtin builtin_id);
|
||||
void run_struct_builtin(Builtin builtin_id);
|
||||
void run_file_builtin(Builtin builtin_id);
|
||||
|
||||
inline void step() { this->pc += 1; }
|
||||
|
10
slige-run.sh
10
slige-run.sh
@ -3,11 +3,17 @@
|
||||
set -e
|
||||
|
||||
echo Text:
|
||||
cat $1
|
||||
|
||||
if command -v pygmentize 2>&1 >/dev/null
|
||||
then
|
||||
pygmentize -l rust -Ostyle="gruvbox-dark",linenos=1 $1
|
||||
else
|
||||
cat $1
|
||||
fi
|
||||
|
||||
echo Compiling $1...
|
||||
|
||||
deno run --allow-read --allow-write compiler/main.ts $1
|
||||
deno run --allow-read --allow-write --check compiler/main.ts $1
|
||||
|
||||
echo Running out.slgbc...
|
||||
|
||||
|
184
std/lib.slg
Normal file
184
std/lib.slg
Normal file
@ -0,0 +1,184 @@
|
||||
|
||||
// stdlib.slg
|
||||
|
||||
#[builtin(Exit)]
|
||||
pub fn exit(status_code: int) {}
|
||||
|
||||
#[builtin(Print)]
|
||||
pub fn print(msg: string) {}
|
||||
pub fn println(msg: string) { print(msg + "\n") }
|
||||
|
||||
#[builtin(IntToString)]
|
||||
pub fn int_to_string(number: int) -> string {}
|
||||
|
||||
#[builtin(StringPushChar)]
|
||||
pub fn string_push_char(str: string, value: int) -> string {}
|
||||
#[builtin(StringCharAt)]
|
||||
pub fn string_char_at(str: string, index: int) -> int {}
|
||||
#[builtin(StringLength)]
|
||||
pub fn string_length(str: string) -> int {}
|
||||
#[builtin(StringToInt)]
|
||||
pub fn string_to_int(str: string) -> int {}
|
||||
|
||||
#[builtin(ArrayNew)]
|
||||
pub fn array_new<T>() -> [T] {}
|
||||
#[builtin(ArrayPush)]
|
||||
pub fn array_push<T>(array: [T], value: T) {}
|
||||
#[builtin(ArrayLength)]
|
||||
pub fn array_length<T>(array: [T]) -> int {}
|
||||
#[builtin(ArrayAt)]
|
||||
pub fn array_at<T>(array: [T], index: int) -> T {}
|
||||
|
||||
#[builtin(StructNew)]
|
||||
pub fn struct_new<S>() -> S {}
|
||||
#[builtin(StructSet)]
|
||||
pub fn struct_set<S, T>(subject: S, value: T) {}
|
||||
|
||||
#[builtin(FileOpen)]
|
||||
pub fn file_open(filename: string, mode: string) -> int {}
|
||||
#[builtin(FileClose)]
|
||||
pub fn file_close(file: int) {}
|
||||
#[builtin(FileWriteString)]
|
||||
pub fn file_write_string(file: int, content: string) -> int {}
|
||||
#[builtin(FileReadChar)]
|
||||
pub fn file_read_char(file: int) -> int {}
|
||||
#[builtin(FileReadToString)]
|
||||
pub fn file_read_to_string(file: int) -> string {}
|
||||
#[builtin(FileFlush)]
|
||||
pub fn file_flush(file: int) {}
|
||||
#[builtin(FileEof)]
|
||||
pub fn file_eof(file: int) -> bool {}
|
||||
|
||||
#[builtin(IntToString)]
|
||||
pub fn itos(number: int) -> string {}
|
||||
#[builtin(StringToInt)]
|
||||
pub fn stoi(str: string) -> int {}
|
||||
|
||||
pub fn ctos(ch: int) -> string { string_push_char("", ch) }
|
||||
|
||||
pub fn stdin() -> int { 0 }
|
||||
pub fn stdout() -> int { 1 }
|
||||
pub fn stderr() -> int { 2 }
|
||||
|
||||
pub fn file_read_line(file: int) -> string {
|
||||
let line = "";
|
||||
loop {
|
||||
if file_eof(file) {
|
||||
break;
|
||||
}
|
||||
let ch = file_read_char(file);
|
||||
if ch == "\n"[0] {
|
||||
break;
|
||||
}
|
||||
line = string_push_char(line, ch);
|
||||
}
|
||||
line
|
||||
}
|
||||
|
||||
pub fn read_text_file(filename: string) -> string {
|
||||
let file = file_open(filename, "r");
|
||||
let text = file_read_to_string(file);
|
||||
file_close(file);
|
||||
text
|
||||
}
|
||||
|
||||
pub fn input(prompt: string) -> string {
|
||||
print(prompt);
|
||||
file_flush(stdout());
|
||||
file_read_line(stdin())
|
||||
}
|
||||
|
||||
pub fn string_abs(number: int) -> int {
|
||||
let result = number;
|
||||
if number < 0 {
|
||||
result = number - (number * 2);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn string_split(str: string, seperator: int) -> [string] {
|
||||
let result = array_new::<string>();
|
||||
|
||||
let i = 0;
|
||||
let current_str = "";
|
||||
loop {
|
||||
if i >= string_length(str) {
|
||||
break;
|
||||
}
|
||||
let char = str[i];
|
||||
if char == seperator {
|
||||
array_push(result, current_str);
|
||||
current_str = "";
|
||||
} else {
|
||||
current_str = string_push_char(current_str, char);
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
array_push(result, current_str);
|
||||
result
|
||||
}
|
||||
|
||||
pub fn string_slice(str: string, from: int, to: int) -> string {
|
||||
let result = "";
|
||||
let i = from;
|
||||
loop {
|
||||
if i >= string_length(str) {
|
||||
break;
|
||||
}
|
||||
if i >= to {
|
||||
break;
|
||||
}
|
||||
result = string_push_char(result, str[i]);
|
||||
i = i + 1;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn string_contains(str: string, ch: int) -> bool {
|
||||
let len = string_length(str);
|
||||
for (let i = 0; i < len; i += 1) {
|
||||
if str[i] == ch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn array_clone<T>(array: [T]) -> [T] {
|
||||
let len = array_length(array);
|
||||
let result = array_new::<T>();
|
||||
let i = 0;
|
||||
loop {
|
||||
if i >= len { break; }
|
||||
array_push(result, array[i]);
|
||||
i = 1 + 1;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn array_sort_mut(array: [int]) {
|
||||
let len = array_length(array);
|
||||
for (let i = 0; i < len; i += 1) {
|
||||
for (let j = i + 1; j < len; j += 1) {
|
||||
if array[j] < array[i] {
|
||||
let tmp = array[j];
|
||||
array[j] = array[i];
|
||||
array[i] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn array_to_sorted(array: [int]) -> [int] {
|
||||
let cloned = array_clone(array);
|
||||
array_sort_mut(array);
|
||||
cloned
|
||||
}
|
||||
|
||||
pub fn assert(value: bool, msg: string) {
|
||||
if not value {
|
||||
println("assertion failed: " + msg);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
151
stdlib.slg
151
stdlib.slg
@ -1,151 +0,0 @@
|
||||
|
||||
fn print(msg: string) #[builtin(Print)] {}
|
||||
fn println(msg: string) { print(msg + "\n") }
|
||||
|
||||
fn int_to_string(number: int) -> string #[builtin(IntToString)] {}
|
||||
|
||||
fn string_push_char(str: string, value: int) -> string #[builtin(StringPushChar)] {}
|
||||
fn string_char_at(str: string, index: int) -> int #[builtin(StringCharAt)] {}
|
||||
fn string_length(str: string) -> int #[builtin(StringLength)] {}
|
||||
fn string_to_int(str: string) -> int #[builtin(StringToInt)] {}
|
||||
|
||||
fn string_array_new() -> [string] #[builtin(ArrayNew)] {}
|
||||
fn string_array_push(array: [string], value: string) #[builtin(ArrayPush)] {}
|
||||
fn string_array_length(array: [string]) -> int #[builtin(ArrayLength)] {}
|
||||
fn string_array_at(array: [string], index: int) -> string #[builtin(ArrayAt)] {}
|
||||
|
||||
fn int_array_new() -> [int] #[builtin(ArrayNew)] {}
|
||||
fn int_array_push(array: [int], value: int) #[builtin(ArrayPush)] {}
|
||||
fn int_array_length(array: [int]) -> int #[builtin(ArrayLength)] {}
|
||||
fn int_array_at(array: [int], index: int) -> int #[builtin(ArrayAt)] {}
|
||||
|
||||
fn file_open(filename: string, mode: string) -> int #[builtin(FileOpen)] {}
|
||||
fn file_close(file: int) #[builtin(FileClose)] {}
|
||||
fn file_write_string(file: int, content: string) -> int #[builtin(FileWriteString)] {}
|
||||
fn file_read_char(file: int) -> int #[builtin(FileReadChar)] {}
|
||||
fn file_read_to_string(file: int) -> string #[builtin(FileReadToString)] {}
|
||||
fn file_flush(file: int) #[builtin(FileFlush)] {}
|
||||
fn file_eof(file: int) -> bool #[builtin(FileEof)] {}
|
||||
|
||||
fn itos(number: int) -> string #[builtin(IntToString)] {}
|
||||
fn stoi(str: string) -> int #[builtin(StringToInt)] {}
|
||||
|
||||
fn stdin() -> int { 0 }
|
||||
fn stdout() -> int { 1 }
|
||||
fn stderr() -> int { 2 }
|
||||
|
||||
fn file_read_line(file: int) -> string {
|
||||
let line = "";
|
||||
loop {
|
||||
if file_eof(file) {
|
||||
break;
|
||||
}
|
||||
let ch = file_read_char(file);
|
||||
if ch == "\n"[0] {
|
||||
break;
|
||||
}
|
||||
line = string_push_char(line, ch);
|
||||
}
|
||||
line
|
||||
}
|
||||
|
||||
fn read_text_file(filename: string) -> string {
|
||||
let file = file_open(filename, "r");
|
||||
let text = file_read_to_string(file);
|
||||
file_close(file);
|
||||
text
|
||||
}
|
||||
|
||||
fn input(prompt: string) -> string {
|
||||
print("> ");
|
||||
file_flush(stdout());
|
||||
file_read_line(stdin())
|
||||
}
|
||||
|
||||
fn string_abs(number: int) -> int {
|
||||
let result = number;
|
||||
if number < 0 {
|
||||
result = number - (number * 2);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn string_split(str: string, seperator: int) -> [string] {
|
||||
let result: [string] = string_array_new();
|
||||
|
||||
let i = 0;
|
||||
let current_str = "";
|
||||
loop {
|
||||
if i >= string_length(str) {
|
||||
break;
|
||||
}
|
||||
let char = str[i];
|
||||
if char == seperator {
|
||||
string_array_push(result, current_str);
|
||||
current_str = "";
|
||||
} else {
|
||||
current_str = string_push_char(current_str, char);
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
string_array_push(result, current_str);
|
||||
result
|
||||
}
|
||||
|
||||
fn string_slice(str: string, from: int, to: int) -> string {
|
||||
let result = "";
|
||||
let i = from;
|
||||
loop {
|
||||
if i >= string_length(str) {
|
||||
break;
|
||||
}
|
||||
if i >= to {
|
||||
break;
|
||||
}
|
||||
result = string_push_char(result, str[i]);
|
||||
i = i + 1;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn string_contains(str: string, ch: int) -> bool {
|
||||
let len = string_length(str);
|
||||
for (let i = 0; i < len; i += 1) {
|
||||
if str[i] == ch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn array_clone(array: [int]) -> [int] {
|
||||
let len = int_array_length(array);
|
||||
let result = int_array_new();
|
||||
let i = 0;
|
||||
loop {
|
||||
if i >= len { break; }
|
||||
int_array_push(result, array[i]);
|
||||
i = 1 + 1;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn array_sort_mut(array: [int]) {
|
||||
let len = int_array_length(array);
|
||||
for (let i = 0; i < len; i += 1) {
|
||||
for (let j = i + 1; j < len; j += 1) {
|
||||
if array[j] < array[i] {
|
||||
let tmp = array[j];
|
||||
array[j] = array[i];
|
||||
array[i] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn array_to_sorted(array: [int]) -> [int] {
|
||||
let cloned = array_clone(array);
|
||||
array_sort_mut(array);
|
||||
cloned
|
||||
}
|
||||
|
11
tests/array_literal.slg
Normal file
11
tests/array_literal.slg
Normal file
@ -0,0 +1,11 @@
|
||||
mod std;
|
||||
|
||||
fn main() {
|
||||
let ints = [1, 2, 3];
|
||||
std::assert(ints[1] == 2, "test int array");
|
||||
|
||||
let strings = ["foo", "bar", "baz"];
|
||||
std::assert(strings[1] == "bar", "test string array");
|
||||
|
||||
std::println("tests ran successfully");
|
||||
}
|
10
tests/char_literal.slg
Normal file
10
tests/char_literal.slg
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
fn exit(status_code: int) #[builtin(Exit)] {}
|
||||
|
||||
fn main() {
|
||||
if 'A' != 65 {
|
||||
exit(1);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
23
tests/generics.slg
Normal file
23
tests/generics.slg
Normal file
@ -0,0 +1,23 @@
|
||||
|
||||
fn exit(status_code: int) #[builtin(Exit)] {}
|
||||
|
||||
fn print(msg: string) #[builtin(Print)] {}
|
||||
fn println(msg: string) { print(msg + "\n") }
|
||||
|
||||
fn id<T>(v: T) -> T {
|
||||
v
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println("calling with int");
|
||||
if id::<int>(123) != 123 {
|
||||
exit(1);
|
||||
}
|
||||
println("calling with bool");
|
||||
if id::<bool>(true) != true {
|
||||
exit(1);
|
||||
}
|
||||
println("all tests ran successfully");
|
||||
exit(0);
|
||||
}
|
||||
|
19
tests/import_modules_entry.slg
Normal file
19
tests/import_modules_entry.slg
Normal file
@ -0,0 +1,19 @@
|
||||
|
||||
|
||||
fn exit(status_code: int) #[builtin(Exit)] {}
|
||||
|
||||
fn print(msg: string) #[builtin(Print)] {}
|
||||
fn println(msg: string) { print(msg + "\n") }
|
||||
|
||||
mod inner "import_modules_inner.slg";
|
||||
|
||||
fn main() {
|
||||
println("test function from module");
|
||||
let res = inner::inner_fn(32);
|
||||
if res != 64 {
|
||||
println("failed");
|
||||
exit(1);
|
||||
}
|
||||
println("all tests ran successfully");
|
||||
exit(0);
|
||||
}
|
5
tests/import_modules_inner.slg
Normal file
5
tests/import_modules_inner.slg
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
fn inner_fn(a: int) -> int {
|
||||
a + 32
|
||||
}
|
||||
|
25
tests/modules.slg
Normal file
25
tests/modules.slg
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
fn exit(status_code: int) #[builtin(Exit)] {}
|
||||
|
||||
fn print(msg: string) #[builtin(Print)] {}
|
||||
fn println(msg: string) { print(msg + "\n") }
|
||||
|
||||
mod my_module {
|
||||
|
||||
fn inner_fn(a: int) -> int {
|
||||
a + 32
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println("test function from module");
|
||||
let res = my_module::inner_fn(32);
|
||||
if res != 64 {
|
||||
println("failed");
|
||||
exit(1);
|
||||
}
|
||||
println("all tests ran successfully");
|
||||
exit(0);
|
||||
}
|
||||
|
20
tests/struct_literal.slg
Normal file
20
tests/struct_literal.slg
Normal file
@ -0,0 +1,20 @@
|
||||
mod std;
|
||||
|
||||
fn main() {
|
||||
let d = true;
|
||||
|
||||
let v = struct {
|
||||
a: 123,
|
||||
b: struct {
|
||||
c: "foo",
|
||||
d: d,
|
||||
},
|
||||
};
|
||||
|
||||
std::assert(v.a == 123, "test field");
|
||||
std::assert(v.b.c == "foo", "test nested field");
|
||||
std::assert(v.b.d == true, "test resolved field");
|
||||
|
||||
std::println("tests ran successfully");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user