Compare commits
28 Commits
629a821c9e
...
5d6b1abefc
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 | |||
552ac1d59c |
@ -40,7 +40,8 @@ export const Ops = {
|
|||||||
|
|
||||||
export type Builtins = typeof Builtins;
|
export type Builtins = typeof Builtins;
|
||||||
export const Builtins = {
|
export const Builtins = {
|
||||||
IntToString: 0x00,
|
Exit: 0x00,
|
||||||
|
IntToString: 0x01,
|
||||||
StringConcat: 0x10,
|
StringConcat: 0x10,
|
||||||
StringEqual: 0x11,
|
StringEqual: 0x11,
|
||||||
StringCharAt: 0x12,
|
StringCharAt: 0x12,
|
||||||
@ -52,7 +53,9 @@ export const Builtins = {
|
|||||||
ArrayPush: 0x22,
|
ArrayPush: 0x22,
|
||||||
ArrayAt: 0x23,
|
ArrayAt: 0x23,
|
||||||
ArrayLength: 0x24,
|
ArrayLength: 0x24,
|
||||||
StructSet: 0x30,
|
StructNew: 0x30,
|
||||||
|
StructSet: 0x31,
|
||||||
|
StructAt: 0x32,
|
||||||
Print: 0x40,
|
Print: 0x40,
|
||||||
FileOpen: 0x41,
|
FileOpen: 0x41,
|
||||||
FileClose: 0x42,
|
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 { 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 = {
|
export type Stmt = {
|
||||||
kind: StmtKind;
|
kind: StmtKind;
|
||||||
pos: Pos;
|
pos: Pos;
|
||||||
|
details?: StmtDetails;
|
||||||
id: number;
|
id: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type StmtKind =
|
export type StmtKind =
|
||||||
| { type: "error" }
|
| { 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: "break"; expr?: Expr }
|
||||||
| { type: "return"; expr?: Expr }
|
| { type: "return"; expr?: Expr }
|
||||||
| {
|
| FnStmtKind
|
||||||
type: "fn";
|
|
||||||
ident: string;
|
|
||||||
params: Param[];
|
|
||||||
returnType?: EType;
|
|
||||||
body: Expr;
|
|
||||||
anno?: Anno;
|
|
||||||
vtype?: VType;
|
|
||||||
}
|
|
||||||
| { type: "let"; param: Param; value: Expr }
|
| { type: "let"; param: Param; value: Expr }
|
||||||
|
| { type: "type_alias"; param: Param }
|
||||||
| { type: "assign"; assignType: AssignType; subject: Expr; value: Expr }
|
| { type: "assign"; assignType: AssignType; subject: Expr; value: Expr }
|
||||||
| { type: "expr"; expr: 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 AssignType = "=" | "+=" | "-=";
|
||||||
|
|
||||||
|
export type StmtDetails = {
|
||||||
|
pub: boolean;
|
||||||
|
annos: Anno[];
|
||||||
|
};
|
||||||
|
|
||||||
export type Expr = {
|
export type Expr = {
|
||||||
kind: ExprKind;
|
kind: ExprKind;
|
||||||
pos: Pos;
|
pos: Pos;
|
||||||
@ -38,11 +56,28 @@ export type ExprKind =
|
|||||||
| { type: "error" }
|
| { type: "error" }
|
||||||
| { type: "int"; value: number }
|
| { type: "int"; value: number }
|
||||||
| { type: "string"; value: string }
|
| { type: "string"; value: string }
|
||||||
| { type: "ident"; value: string }
|
| { type: "ident"; ident: string }
|
||||||
|
| {
|
||||||
|
type: "sym";
|
||||||
|
ident: string;
|
||||||
|
sym: Sym;
|
||||||
|
}
|
||||||
| { type: "group"; expr: Expr }
|
| { type: "group"; expr: Expr }
|
||||||
| { type: "field"; subject: Expr; value: string }
|
| { type: "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: "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: "unary"; unaryType: UnaryType; subject: Expr }
|
||||||
| { type: "binary"; binaryType: BinaryType; left: Expr; right: Expr }
|
| { type: "binary"; binaryType: BinaryType; left: Expr; right: Expr }
|
||||||
| { type: "if"; cond: Expr; truthy: Expr; falsy?: Expr; elsePos?: Pos }
|
| { type: "if"; cond: Expr; truthy: Expr; falsy?: Expr; elsePos?: Pos }
|
||||||
@ -50,11 +85,6 @@ export type ExprKind =
|
|||||||
| { type: "null" }
|
| { type: "null" }
|
||||||
| { type: "loop"; body: Expr }
|
| { type: "loop"; body: Expr }
|
||||||
| { type: "block"; stmts: Stmt[]; expr?: Expr }
|
| { type: "block"; stmts: Stmt[]; expr?: Expr }
|
||||||
| {
|
|
||||||
type: "sym";
|
|
||||||
ident: string;
|
|
||||||
sym: Sym;
|
|
||||||
}
|
|
||||||
| { type: "while"; cond: Expr; body: Expr }
|
| { type: "while"; cond: Expr; body: Expr }
|
||||||
| { type: "for_in"; param: Param; value: Expr; body: Expr }
|
| { type: "for_in"; param: Param; value: Expr; body: Expr }
|
||||||
| {
|
| {
|
||||||
@ -80,25 +110,38 @@ export type BinaryType =
|
|||||||
| "or"
|
| "or"
|
||||||
| "and";
|
| "and";
|
||||||
|
|
||||||
export type Param = {
|
export type Field = {
|
||||||
ident: string;
|
ident: string;
|
||||||
|
expr: Expr;
|
||||||
|
pos: Pos;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Param = {
|
||||||
|
id: number;
|
||||||
|
index?: number;
|
||||||
|
ident: string;
|
||||||
|
mut: boolean;
|
||||||
etype?: EType;
|
etype?: EType;
|
||||||
pos: Pos;
|
pos: Pos;
|
||||||
|
sym?: Sym;
|
||||||
vtype?: VType;
|
vtype?: VType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Sym = {
|
export type Sym = {
|
||||||
ident: string;
|
ident: string;
|
||||||
|
fullPath: string;
|
||||||
pos?: Pos;
|
pos?: Pos;
|
||||||
} & SymKind;
|
} & SymKind;
|
||||||
|
|
||||||
export type SymKind =
|
export type SymKind =
|
||||||
| { type: "let"; stmt: Stmt; param: Param }
|
| { type: "let"; stmt: Stmt; param: Param }
|
||||||
| { type: "let_static"; stmt: Stmt; param: Param }
|
| { type: "let_static"; stmt: Stmt; param: Param }
|
||||||
|
| { type: "type_alias"; stmt: Stmt; param: Param }
|
||||||
| { type: "fn"; stmt: Stmt }
|
| { type: "fn"; stmt: Stmt }
|
||||||
| { type: "fn_param"; param: Param }
|
| { type: "fn_param"; param: Param }
|
||||||
| { type: "closure"; inner: Sym }
|
| { type: "closure"; inner: Sym }
|
||||||
| { type: "builtin"; builtinId: number };
|
| { type: "generic"; stmt: Stmt; genericParam: GenericParam }
|
||||||
|
| { type: "mod"; syms: Syms };
|
||||||
|
|
||||||
export type EType = {
|
export type EType = {
|
||||||
kind: ETypeKind;
|
kind: ETypeKind;
|
||||||
@ -108,11 +151,27 @@ export type EType = {
|
|||||||
|
|
||||||
export type ETypeKind =
|
export type ETypeKind =
|
||||||
| { type: "error" }
|
| { type: "error" }
|
||||||
| { type: "ident"; value: string }
|
| { type: "null" }
|
||||||
| { type: "array"; inner: EType }
|
| { type: "int" }
|
||||||
| { type: "struct"; fields: Param[] };
|
| { 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;
|
ident: string;
|
||||||
pos: Pos;
|
pos: Pos;
|
||||||
vtype?: VType;
|
vtype?: VType;
|
||||||
@ -120,28 +179,63 @@ export type ETypeParam = {
|
|||||||
|
|
||||||
export type Anno = {
|
export type Anno = {
|
||||||
ident: string;
|
ident: string;
|
||||||
values: Expr[];
|
args: Expr[];
|
||||||
pos: Pos;
|
pos: Pos;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class AstCreator {
|
export class AstCreator {
|
||||||
private nextNodeId = 0;
|
private nextNodeId = 0;
|
||||||
|
|
||||||
public stmt(kind: StmtKind, pos: Pos): Stmt {
|
public stmt(kind: StmtKind, pos: Pos, details?: StmtDetails): Stmt {
|
||||||
const id = this.nextNodeId;
|
const id = this.genId();
|
||||||
this.nextNodeId += 1;
|
return { kind, pos, details, id };
|
||||||
return { kind, pos, id };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public expr(kind: ExprKind, pos: Pos): Expr {
|
public expr(kind: ExprKind, pos: Pos): Expr {
|
||||||
const id = this.nextNodeId;
|
const id = this.genId();
|
||||||
this.nextNodeId += 1;
|
|
||||||
return { kind, pos, id };
|
return { kind, pos, id };
|
||||||
}
|
}
|
||||||
|
|
||||||
public etype(kind: ETypeKind, pos: Pos): EType {
|
public etype(kind: ETypeKind, pos: Pos): EType {
|
||||||
const id = this.nextNodeId;
|
const id = this.genId();
|
||||||
this.nextNodeId += 1;
|
|
||||||
return { kind, pos, id };
|
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;
|
export type VisitRes = "stop" | void;
|
||||||
|
|
||||||
@ -6,11 +6,14 @@ export interface AstVisitor<Args extends unknown[] = []> {
|
|||||||
visitStmts?(stmts: Stmt[], ...args: Args): VisitRes;
|
visitStmts?(stmts: Stmt[], ...args: Args): VisitRes;
|
||||||
visitStmt?(stmt: Stmt, ...args: Args): VisitRes;
|
visitStmt?(stmt: Stmt, ...args: Args): VisitRes;
|
||||||
visitErrorStmt?(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;
|
visitBreakStmt?(stmt: Stmt, ...args: Args): VisitRes;
|
||||||
visitReturnStmt?(stmt: Stmt, ...args: Args): VisitRes;
|
visitReturnStmt?(stmt: Stmt, ...args: Args): VisitRes;
|
||||||
visitFnStmt?(stmt: Stmt, ...args: Args): VisitRes;
|
visitFnStmt?(stmt: Stmt, ...args: Args): VisitRes;
|
||||||
visitLetStmt?(stmt: Stmt, ...args: Args): VisitRes;
|
visitLetStmt?(stmt: Stmt, ...args: Args): VisitRes;
|
||||||
|
visitTypeAliasStmt?(stmt: Stmt, ...args: Args): VisitRes;
|
||||||
visitAssignStmt?(stmt: Stmt, ...args: Args): VisitRes;
|
visitAssignStmt?(stmt: Stmt, ...args: Args): VisitRes;
|
||||||
visitExprStmt?(stmt: Stmt, ...args: Args): VisitRes;
|
visitExprStmt?(stmt: Stmt, ...args: Args): VisitRes;
|
||||||
visitExpr?(expr: Expr, ...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;
|
visitStringExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||||
visitIdentExpr?(expr: Expr, ...args: Args): VisitRes;
|
visitIdentExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||||
visitGroupExpr?(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;
|
visitFieldExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||||
visitIndexExpr?(expr: Expr, ...args: Args): VisitRes;
|
visitIndexExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||||
visitCallExpr?(expr: Expr, ...args: Args): VisitRes;
|
visitCallExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||||
|
visitPathExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||||
|
visitETypeArgsExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||||
visitUnaryExpr?(expr: Expr, ...args: Args): VisitRes;
|
visitUnaryExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||||
visitBinaryExpr?(expr: Expr, ...args: Args): VisitRes;
|
visitBinaryExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||||
visitIfExpr?(expr: Expr, ...args: Args): VisitRes;
|
visitIfExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||||
@ -34,11 +44,22 @@ export interface AstVisitor<Args extends unknown[] = []> {
|
|||||||
visitBlockExpr?(expr: Expr, ...args: Args): VisitRes;
|
visitBlockExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||||
visitSymExpr?(expr: Expr, ...args: Args): VisitRes;
|
visitSymExpr?(expr: Expr, ...args: Args): VisitRes;
|
||||||
visitParam?(param: Param, ...args: Args): VisitRes;
|
visitParam?(param: Param, ...args: Args): VisitRes;
|
||||||
|
visitField?(field: Field, ...args: Args): VisitRes;
|
||||||
visitEType?(etype: EType, ...args: Args): VisitRes;
|
visitEType?(etype: EType, ...args: Args): VisitRes;
|
||||||
visitErrorEType?(etype: EType, ...args: Args): VisitRes;
|
visitErrorEType?(etype: EType, ...args: Args): VisitRes;
|
||||||
|
visitNullEType?(etype: EType, ...args: Args): VisitRes;
|
||||||
|
visitIntEType?(etype: EType, ...args: Args): VisitRes;
|
||||||
|
visitBoolEType?(etype: EType, ...args: Args): VisitRes;
|
||||||
|
visitStringEType?(etype: EType, ...args: Args): VisitRes;
|
||||||
visitIdentEType?(etype: EType, ...args: Args): VisitRes;
|
visitIdentEType?(etype: EType, ...args: Args): VisitRes;
|
||||||
|
visitSymEType?(etype: EType, ...args: Args): VisitRes;
|
||||||
|
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;
|
visitArrayEType?(etype: EType, ...args: Args): VisitRes;
|
||||||
visitStructEType?(etype: EType, ...args: Args): VisitRes;
|
visitStructEType?(etype: EType, ...args: Args): VisitRes;
|
||||||
|
visitTypeOfEType?(etype: EType, ...args: Args): VisitRes;
|
||||||
visitAnno?(etype: EType, ...args: Args): VisitRes;
|
visitAnno?(etype: EType, ...args: Args): VisitRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,9 +82,16 @@ export function visitStmt<Args extends unknown[] = []>(
|
|||||||
case "error":
|
case "error":
|
||||||
if (v.visitErrorStmt?.(stmt, ...args) == "stop") return;
|
if (v.visitErrorStmt?.(stmt, ...args) == "stop") return;
|
||||||
break;
|
break;
|
||||||
case "import":
|
case "mod_file":
|
||||||
if (v.visitImportStmt?.(stmt, ...args) == "stop") return;
|
if (v.visitModFileStmt?.(stmt, ...args) == "stop") return;
|
||||||
visitExpr(stmt.kind.path, v, ...args);
|
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;
|
break;
|
||||||
case "break":
|
case "break":
|
||||||
if (v.visitBreakStmt?.(stmt, ...args) == "stop") return;
|
if (v.visitBreakStmt?.(stmt, ...args) == "stop") return;
|
||||||
@ -86,6 +114,10 @@ export function visitStmt<Args extends unknown[] = []>(
|
|||||||
visitParam(stmt.kind.param, v, ...args);
|
visitParam(stmt.kind.param, v, ...args);
|
||||||
visitExpr(stmt.kind.value, v, ...args);
|
visitExpr(stmt.kind.value, v, ...args);
|
||||||
break;
|
break;
|
||||||
|
case "type_alias":
|
||||||
|
if (v.visitTypeAliasStmt?.(stmt, ...args) == "stop") return;
|
||||||
|
visitParam(stmt.kind.param, v, ...args);
|
||||||
|
break;
|
||||||
case "assign":
|
case "assign":
|
||||||
if (v.visitAssignStmt?.(stmt, ...args) == "stop") return;
|
if (v.visitAssignStmt?.(stmt, ...args) == "stop") return;
|
||||||
visitExpr(stmt.kind.subject, v, ...args);
|
visitExpr(stmt.kind.subject, v, ...args);
|
||||||
@ -95,6 +127,12 @@ export function visitStmt<Args extends unknown[] = []>(
|
|||||||
if (v.visitExprStmt?.(stmt, ...args) == "stop") return;
|
if (v.visitExprStmt?.(stmt, ...args) == "stop") return;
|
||||||
visitExpr(stmt.kind.expr, v, ...args);
|
visitExpr(stmt.kind.expr, v, ...args);
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`statement '${
|
||||||
|
(stmt.kind as { type: string }).type
|
||||||
|
}' not implemented`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,6 +159,18 @@ export function visitExpr<Args extends unknown[] = []>(
|
|||||||
if (v.visitGroupExpr?.(expr, ...args) == "stop") return;
|
if (v.visitGroupExpr?.(expr, ...args) == "stop") return;
|
||||||
visitExpr(expr.kind.expr, v, ...args);
|
visitExpr(expr.kind.expr, v, ...args);
|
||||||
break;
|
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":
|
case "field":
|
||||||
if (v.visitFieldExpr?.(expr, ...args) == "stop") return;
|
if (v.visitFieldExpr?.(expr, ...args) == "stop") return;
|
||||||
visitExpr(expr.kind.subject, v, ...args);
|
visitExpr(expr.kind.subject, v, ...args);
|
||||||
@ -135,6 +185,15 @@ export function visitExpr<Args extends unknown[] = []>(
|
|||||||
visitExpr(expr.kind.subject, v, ...args);
|
visitExpr(expr.kind.subject, v, ...args);
|
||||||
expr.kind.args.map((arg) => visitExpr(arg, v, ...args));
|
expr.kind.args.map((arg) => visitExpr(arg, v, ...args));
|
||||||
break;
|
break;
|
||||||
|
case "path":
|
||||||
|
if (v.visitPathExpr?.(expr, ...args) == "stop") return;
|
||||||
|
visitExpr(expr.kind.subject, v, ...args);
|
||||||
|
break;
|
||||||
|
case "etype_args":
|
||||||
|
if (v.visitETypeArgsExpr?.(expr, ...args) == "stop") return;
|
||||||
|
visitExpr(expr.kind.subject, v, ...args);
|
||||||
|
expr.kind.etypeArgs.map((arg) => visitEType(arg, v, ...args));
|
||||||
|
break;
|
||||||
case "unary":
|
case "unary":
|
||||||
if (v.visitUnaryExpr?.(expr, ...args) == "stop") return;
|
if (v.visitUnaryExpr?.(expr, ...args) == "stop") return;
|
||||||
visitExpr(expr.kind.subject, v, ...args);
|
visitExpr(expr.kind.subject, v, ...args);
|
||||||
@ -144,6 +203,14 @@ export function visitExpr<Args extends unknown[] = []>(
|
|||||||
visitExpr(expr.kind.left, v, ...args);
|
visitExpr(expr.kind.left, v, ...args);
|
||||||
visitExpr(expr.kind.right, v, ...args);
|
visitExpr(expr.kind.right, v, ...args);
|
||||||
break;
|
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":
|
case "if":
|
||||||
if (v.visitIfExpr?.(expr, ...args) == "stop") return;
|
if (v.visitIfExpr?.(expr, ...args) == "stop") return;
|
||||||
visitExpr(expr.kind.cond, v, ...args);
|
visitExpr(expr.kind.cond, v, ...args);
|
||||||
@ -186,6 +253,12 @@ export function visitExpr<Args extends unknown[] = []>(
|
|||||||
case "sym":
|
case "sym":
|
||||||
if (v.visitSymExpr?.(expr, ...args) == "stop") return;
|
if (v.visitSymExpr?.(expr, ...args) == "stop") return;
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`expression '${
|
||||||
|
(expr.kind as { type: string }).type
|
||||||
|
}' not implemented`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,6 +271,15 @@ export function visitParam<Args extends unknown[] = []>(
|
|||||||
if (param.etype) visitEType(param.etype, v, ...args);
|
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[] = []>(
|
export function visitEType<Args extends unknown[] = []>(
|
||||||
etype: EType,
|
etype: EType,
|
||||||
v: AstVisitor<Args>,
|
v: AstVisitor<Args>,
|
||||||
@ -208,17 +290,58 @@ export function visitEType<Args extends unknown[] = []>(
|
|||||||
case "error":
|
case "error":
|
||||||
if (v.visitErrorEType?.(etype, ...args) == "stop") return;
|
if (v.visitErrorEType?.(etype, ...args) == "stop") return;
|
||||||
break;
|
break;
|
||||||
|
case "string":
|
||||||
|
if (v.visitStringEType?.(etype, ...args) == "stop") return;
|
||||||
|
break;
|
||||||
|
case "null":
|
||||||
|
if (v.visitNullEType?.(etype, ...args) == "stop") return;
|
||||||
|
break;
|
||||||
|
case "int":
|
||||||
|
if (v.visitIntEType?.(etype, ...args) == "stop") return;
|
||||||
|
break;
|
||||||
|
case "bool":
|
||||||
|
if (v.visitBoolEType?.(etype, ...args) == "stop") return;
|
||||||
|
break;
|
||||||
case "ident":
|
case "ident":
|
||||||
if (v.visitIdentEType?.(etype, ...args) == "stop") return;
|
if (v.visitIdentEType?.(etype, ...args) == "stop") return;
|
||||||
break;
|
break;
|
||||||
|
case "sym":
|
||||||
|
if (v.visitSymEType?.(etype, ...args) == "stop") return;
|
||||||
|
break;
|
||||||
|
case "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":
|
case "array":
|
||||||
if (v.visitArrayEType?.(etype, ...args) == "stop") return;
|
if (v.visitArrayEType?.(etype, ...args) == "stop") return;
|
||||||
if (etype.kind.inner) visitEType(etype.kind.inner, v, ...args);
|
visitEType(etype.kind.subject, v, ...args);
|
||||||
break;
|
break;
|
||||||
case "struct":
|
case "struct":
|
||||||
if (v.visitStructEType?.(etype, ...args) == "stop") return;
|
if (v.visitStructEType?.(etype, ...args) == "stop") return;
|
||||||
etype.kind.fields.map((field) => visitParam(field, v, ...args));
|
etype.kind.fields.map((field) => visitParam(field, v, ...args));
|
||||||
break;
|
break;
|
||||||
|
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 { printStackTrace, Reporter } from "./info.ts";
|
||||||
import { Pos } from "./token.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 {
|
export class Checker {
|
||||||
private fnReturnStack: VType[] = [];
|
private fnReturnStack: VType[] = [];
|
||||||
private loopBreakStack: VType[][] = [];
|
private loopBreakStack: VType[][] = [];
|
||||||
|
|
||||||
|
private globalIdToGenericParamMap = new Map<number, VTypeGenericParam>();
|
||||||
|
|
||||||
public constructor(private reporter: Reporter) {}
|
public constructor(private reporter: Reporter) {}
|
||||||
|
|
||||||
public check(stmts: Stmt[]) {
|
public check(stmts: Stmt[]) {
|
||||||
this.checkFnHeaders(stmts);
|
this.scout(stmts);
|
||||||
for (const stmt of stmts) {
|
for (const stmt of stmts) {
|
||||||
this.checkStmt(stmt);
|
this.checkStmt(stmt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private checkFnHeaders(stmts: Stmt[]) {
|
private scout(stmts: Stmt[]) {
|
||||||
for (const stmt of stmts) {
|
for (const stmt of stmts) {
|
||||||
if (stmt.kind.type !== "fn") {
|
if (stmt.kind.type === "fn") {
|
||||||
continue;
|
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 returnType: VType = stmt.kind.returnType
|
|
||||||
? this.checkEType(stmt.kind.returnType)
|
|
||||||
: { type: "null" };
|
|
||||||
const params: VTypeParam[] = [];
|
const params: VTypeParam[] = [];
|
||||||
for (const param of stmt.kind.params) {
|
for (const param of stmt.kind.params) {
|
||||||
if (param.etype === undefined) {
|
if (param.etype === undefined) {
|
||||||
this.report("parameter types must be defined", param.pos);
|
this.report(
|
||||||
|
"parameter types must be defined",
|
||||||
|
param.pos,
|
||||||
|
);
|
||||||
stmt.kind.vtype = { type: "error" };
|
stmt.kind.vtype = { type: "error" };
|
||||||
}
|
}
|
||||||
const vtype = this.checkEType(param.etype!);
|
const vtype = this.checkEType(param.etype!);
|
||||||
param.vtype = vtype;
|
param.vtype = vtype;
|
||||||
params.push({ ident: param.ident, 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) {
|
switch (stmt.kind.type) {
|
||||||
case "error":
|
case "error":
|
||||||
return { type: "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":
|
case "break":
|
||||||
return this.checkBreakStmt(stmt);
|
return this.checkBreakStmt(stmt);
|
||||||
case "return":
|
case "return":
|
||||||
@ -50,6 +90,8 @@ export class Checker {
|
|||||||
return this.checkFnStmt(stmt);
|
return this.checkFnStmt(stmt);
|
||||||
case "let":
|
case "let":
|
||||||
return this.checkLetStmt(stmt);
|
return this.checkLetStmt(stmt);
|
||||||
|
case "type_alias":
|
||||||
|
return this.checkTypeAliasStmt(stmt);
|
||||||
case "assign":
|
case "assign":
|
||||||
return this.checkAssignStmt(stmt);
|
return this.checkAssignStmt(stmt);
|
||||||
case "expr":
|
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) {
|
public checkBreakStmt(stmt: Stmt) {
|
||||||
if (stmt.kind.type !== "break") {
|
if (stmt.kind.type !== "break") {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
@ -103,8 +156,8 @@ export class Checker {
|
|||||||
if (!vtypesEqual(exprType, returnType)) {
|
if (!vtypesEqual(exprType, returnType)) {
|
||||||
this.report(
|
this.report(
|
||||||
`incompatible return type` +
|
`incompatible return type` +
|
||||||
`, got ${exprType}` +
|
`, expected ${vtypeToString(returnType)}` +
|
||||||
`, expected ${returnType}`,
|
`, got ${vtypeToString(exprType)}`,
|
||||||
pos,
|
pos,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -119,16 +172,17 @@ export class Checker {
|
|||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
const annos = new AnnoView(stmt.details);
|
||||||
stmt.kind.anno?.ident === "remainder" ||
|
if (annos.has("builtin", "remainder")) {
|
||||||
stmt.kind.anno?.ident === "builtin"
|
// NOTE: handled in lowerer
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { returnType } = stmt.kind.vtype!;
|
const { returnType } = stmt.kind.vtype!;
|
||||||
|
if (returnType.type === "error") return returnType;
|
||||||
this.fnReturnStack.push(returnType);
|
this.fnReturnStack.push(returnType);
|
||||||
const body = this.checkExpr(stmt.kind.body);
|
const body = this.checkExpr(stmt.kind.body);
|
||||||
|
if (body.type === "error") return body;
|
||||||
this.fnReturnStack.pop();
|
this.fnReturnStack.pop();
|
||||||
|
|
||||||
if (!vtypesEqual(returnType, body)) {
|
if (!vtypesEqual(returnType, body)) {
|
||||||
@ -147,13 +201,17 @@ export class Checker {
|
|||||||
}
|
}
|
||||||
const pos = stmt.pos;
|
const pos = stmt.pos;
|
||||||
const value = this.checkExpr(stmt.kind.value);
|
const value = this.checkExpr(stmt.kind.value);
|
||||||
|
if (value.type === "error") {
|
||||||
|
return stmt.kind.param.vtype = value;
|
||||||
|
}
|
||||||
if (stmt.kind.param.etype) {
|
if (stmt.kind.param.etype) {
|
||||||
const paramVtype = this.checkEType(stmt.kind.param.etype);
|
const paramVType = this.checkEType(stmt.kind.param.etype);
|
||||||
if (!vtypesEqual(value, paramVtype)) {
|
if (paramVType.type === "error") return paramVType;
|
||||||
|
if (!vtypesEqual(value, paramVType)) {
|
||||||
this.report(
|
this.report(
|
||||||
`incompatible value type` +
|
`incompatible value type` +
|
||||||
`, got '${vtypeToString(value)}'` +
|
`, got '${vtypeToString(value)}'` +
|
||||||
`, expected '${vtypeToString(paramVtype)}'`,
|
`, expected '${vtypeToString(paramVType)}'`,
|
||||||
pos,
|
pos,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@ -162,6 +220,18 @@ export class Checker {
|
|||||||
stmt.kind.param.vtype = value;
|
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) {
|
public checkAssignStmt(stmt: Stmt) {
|
||||||
if (stmt.kind.type !== "assign") {
|
if (stmt.kind.type !== "assign") {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
@ -178,13 +248,13 @@ export class Checker {
|
|||||||
this.report("cannot use field on non-struct", pos);
|
this.report("cannot use field on non-struct", pos);
|
||||||
return { type: "error" };
|
return { type: "error" };
|
||||||
}
|
}
|
||||||
const fieldValue = stmt.kind.subject.kind.value;
|
const fieldValue = stmt.kind.subject.kind.ident;
|
||||||
const found = subject.fields.find((param) =>
|
const found = subject.fields.find((param) =>
|
||||||
param.ident === fieldValue
|
param.ident === fieldValue
|
||||||
);
|
);
|
||||||
if (!found) {
|
if (!found) {
|
||||||
this.report(
|
this.report(
|
||||||
`no field named '${stmt.kind.subject.kind.value}' on struct`,
|
`no field named '${stmt.kind.subject.kind.ident}' on struct`,
|
||||||
pos,
|
pos,
|
||||||
);
|
);
|
||||||
return { type: "error" };
|
return { type: "error" };
|
||||||
@ -216,7 +286,7 @@ export class Checker {
|
|||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
subject.type == "array" &&
|
subject.type == "array" &&
|
||||||
!vtypesEqual(subject.inner, value)
|
!vtypesEqual(subject.subject, value)
|
||||||
) {
|
) {
|
||||||
this.report(
|
this.report(
|
||||||
`cannot assign incompatible type to array ` +
|
`cannot assign incompatible type to array ` +
|
||||||
@ -262,6 +332,9 @@ export class Checker {
|
|||||||
case "error":
|
case "error":
|
||||||
throw new Error("error in AST");
|
throw new Error("error in AST");
|
||||||
case "ident":
|
case "ident":
|
||||||
|
if (this.reporter.errorOccured()) {
|
||||||
|
return { type: "error" };
|
||||||
|
}
|
||||||
throw new Error("ident expr in AST");
|
throw new Error("ident expr in AST");
|
||||||
case "sym":
|
case "sym":
|
||||||
return this.checkSymExpr(expr);
|
return this.checkSymExpr(expr);
|
||||||
@ -275,12 +348,26 @@ export class Checker {
|
|||||||
return { type: "string" };
|
return { type: "string" };
|
||||||
case "group":
|
case "group":
|
||||||
return this.checkExpr(expr.kind.expr);
|
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":
|
case "field":
|
||||||
return this.checkFieldExpr(expr);
|
return this.checkFieldExpr(expr);
|
||||||
case "index":
|
case "index":
|
||||||
return this.checkIndexExpr(expr);
|
return this.checkIndexExpr(expr);
|
||||||
case "call":
|
case "call":
|
||||||
return this.checkCallExpr(expr);
|
return this.checkCallExpr(expr);
|
||||||
|
case "path":
|
||||||
|
return this.checkPathExpr(expr);
|
||||||
|
case "etype_args":
|
||||||
|
return this.checkETypeArgsExpr(expr);
|
||||||
case "unary":
|
case "unary":
|
||||||
return this.checkUnaryExpr(expr);
|
return this.checkUnaryExpr(expr);
|
||||||
case "binary":
|
case "binary":
|
||||||
@ -307,11 +394,17 @@ export class Checker {
|
|||||||
if (expr.kind.type !== "sym") {
|
if (expr.kind.type !== "sym") {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
switch (expr.kind.sym.type) {
|
return this.checkSym(expr.kind.sym);
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkSym(sym: Sym): VType {
|
||||||
|
switch (sym.type) {
|
||||||
case "let":
|
case "let":
|
||||||
return expr.kind.sym.param.vtype!;
|
return sym.param.vtype!;
|
||||||
|
case "type_alias":
|
||||||
|
return sym.param.vtype!;
|
||||||
case "fn": {
|
case "fn": {
|
||||||
const fnStmt = expr.kind.sym.stmt!;
|
const fnStmt = sym.stmt!;
|
||||||
if (fnStmt.kind.type !== "fn") {
|
if (fnStmt.kind.type !== "fn") {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
@ -319,20 +412,117 @@ export class Checker {
|
|||||||
if (vtype.type !== "fn") {
|
if (vtype.type !== "fn") {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
const { params, returnType } = vtype;
|
return vtype;
|
||||||
return { type: "fn", params, returnType };
|
|
||||||
}
|
}
|
||||||
case "fn_param":
|
case "fn_param":
|
||||||
return expr.kind.sym.param.vtype!;
|
return sym.param.vtype!;
|
||||||
case "builtin":
|
|
||||||
case "let_static":
|
case "let_static":
|
||||||
case "closure":
|
case "closure":
|
||||||
|
case "generic":
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`not implemented, sym type '${expr.kind.sym.type}'`,
|
`not implemented, sym type '${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 {
|
public checkFieldExpr(expr: Expr): VType {
|
||||||
if (expr.kind.type !== "field") {
|
if (expr.kind.type !== "field") {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
@ -343,11 +533,11 @@ export class Checker {
|
|||||||
this.report("cannot use field on non-struct", pos);
|
this.report("cannot use field on non-struct", pos);
|
||||||
return { type: "error" };
|
return { type: "error" };
|
||||||
}
|
}
|
||||||
const value = expr.kind.value;
|
const value = expr.kind.ident;
|
||||||
const found = subject.fields.find((param) => param.ident === value);
|
const found = subject.fields.find((param) => param.ident === value);
|
||||||
if (!found) {
|
if (!found) {
|
||||||
this.report(
|
this.report(
|
||||||
`no field named '${expr.kind.value}' on struct`,
|
`no field named '${expr.kind.ident}' on struct`,
|
||||||
pos,
|
pos,
|
||||||
);
|
);
|
||||||
return { type: "error" };
|
return { type: "error" };
|
||||||
@ -371,7 +561,7 @@ export class Checker {
|
|||||||
return { type: "error" };
|
return { type: "error" };
|
||||||
}
|
}
|
||||||
if (subject.type === "array") {
|
if (subject.type === "array") {
|
||||||
return subject.inner;
|
return subject.subject;
|
||||||
}
|
}
|
||||||
return { type: "int" };
|
return { type: "int" };
|
||||||
}
|
}
|
||||||
@ -382,18 +572,62 @@ export class Checker {
|
|||||||
}
|
}
|
||||||
const pos = expr.pos;
|
const pos = expr.pos;
|
||||||
const subject = this.checkExpr(expr.kind.subject);
|
const subject = this.checkExpr(expr.kind.subject);
|
||||||
if (subject.type !== "fn") {
|
if (subject.type === "error") return subject;
|
||||||
this.report("cannot call non-fn", pos);
|
if (subject.type === "fn") {
|
||||||
return { type: "error" };
|
if (expr.kind.args.length !== subject.params.length) {
|
||||||
}
|
|
||||||
const args = expr.kind.args.map((arg) => this.checkExpr(arg));
|
|
||||||
if (args.length !== subject.params.length) {
|
|
||||||
this.report(
|
this.report(
|
||||||
`incorrect number of arguments` +
|
`expected ${subject.params.length} arguments` +
|
||||||
`, expected ${subject.params.length}`,
|
`, got ${expr.kind.args.length}`,
|
||||||
pos,
|
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) {
|
for (let i = 0; i < args.length; ++i) {
|
||||||
if (!vtypesEqual(args[i], subject.params[i].vtype)) {
|
if (!vtypesEqual(args[i], subject.params[i].vtype)) {
|
||||||
this.report(
|
this.report(
|
||||||
@ -405,15 +639,283 @@ export class Checker {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return subject.returnType;
|
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 {
|
public checkUnaryExpr(expr: Expr): VType {
|
||||||
if (expr.kind.type !== "unary") {
|
if (expr.kind.type !== "unary") {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
const pos = expr.pos;
|
const pos = expr.pos;
|
||||||
const subject = this.checkExpr(expr.kind.subject);
|
const subject = this.checkExpr(expr.kind.subject);
|
||||||
|
if (subject.type === "error") return subject;
|
||||||
for (const operation of simpleUnaryOperations) {
|
for (const operation of simpleUnaryOperations) {
|
||||||
if (operation.unaryType !== expr.kind.unaryType) {
|
if (operation.unaryType !== expr.kind.unaryType) {
|
||||||
continue;
|
continue;
|
||||||
@ -437,7 +939,9 @@ export class Checker {
|
|||||||
}
|
}
|
||||||
const pos = expr.pos;
|
const pos = expr.pos;
|
||||||
const left = this.checkExpr(expr.kind.left);
|
const left = this.checkExpr(expr.kind.left);
|
||||||
|
if (left.type === "error") return left;
|
||||||
const right = this.checkExpr(expr.kind.right);
|
const right = this.checkExpr(expr.kind.right);
|
||||||
|
if (right.type === "error") return right;
|
||||||
for (const operation of simpleBinaryOperations) {
|
for (const operation of simpleBinaryOperations) {
|
||||||
if (operation.binaryType !== expr.kind.binaryType) {
|
if (operation.binaryType !== expr.kind.binaryType) {
|
||||||
continue;
|
continue;
|
||||||
@ -466,10 +970,13 @@ export class Checker {
|
|||||||
}
|
}
|
||||||
const pos = expr.pos;
|
const pos = expr.pos;
|
||||||
const cond = this.checkExpr(expr.kind.cond);
|
const cond = this.checkExpr(expr.kind.cond);
|
||||||
|
if (cond.type === "error") return cond;
|
||||||
const truthy = this.checkExpr(expr.kind.truthy);
|
const truthy = this.checkExpr(expr.kind.truthy);
|
||||||
|
if (truthy.type === "error") return truthy;
|
||||||
const falsy = expr.kind.falsy
|
const falsy = expr.kind.falsy
|
||||||
? this.checkExpr(expr.kind.falsy)
|
? this.checkExpr(expr.kind.falsy)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
if (falsy?.type === "error") return falsy;
|
||||||
if (cond.type !== "bool") {
|
if (cond.type !== "bool") {
|
||||||
this.report(
|
this.report(
|
||||||
`if condition should be 'bool', got '${vtypeToString(cond)}'`,
|
`if condition should be 'bool', got '${vtypeToString(cond)}'`,
|
||||||
@ -545,7 +1052,7 @@ export class Checker {
|
|||||||
if (expr.kind.type !== "block") {
|
if (expr.kind.type !== "block") {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
this.checkFnHeaders(expr.kind.stmts);
|
this.scout(expr.kind.stmts);
|
||||||
for (const stmt of expr.kind.stmts) {
|
for (const stmt of expr.kind.stmts) {
|
||||||
this.checkStmt(stmt);
|
this.checkStmt(stmt);
|
||||||
}
|
}
|
||||||
@ -556,25 +1063,54 @@ export class Checker {
|
|||||||
|
|
||||||
public checkEType(etype: EType): VType {
|
public checkEType(etype: EType): VType {
|
||||||
const pos = etype.pos;
|
const pos = etype.pos;
|
||||||
if (etype.kind.type === "ident") {
|
switch (etype.kind.type) {
|
||||||
if (etype.kind.value === "null") {
|
case "null":
|
||||||
return { type: "null" };
|
return { type: "null" };
|
||||||
}
|
case "int":
|
||||||
if (etype.kind.value === "int") {
|
|
||||||
return { type: "int" };
|
return { type: "int" };
|
||||||
}
|
case "bool":
|
||||||
if (etype.kind.value === "bool") {
|
|
||||||
return { type: "bool" };
|
return { type: "bool" };
|
||||||
}
|
case "string":
|
||||||
if (etype.kind.value === "string") {
|
|
||||||
return { type: "string" };
|
return { type: "string" };
|
||||||
}
|
}
|
||||||
this.report(`undefined type '${etype.kind.value}'`, pos);
|
if (etype.kind.type === "ident") {
|
||||||
|
this.report(`undefined type '${etype.kind.ident}'`, pos);
|
||||||
return { type: "error" };
|
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") {
|
if (etype.kind.type === "array") {
|
||||||
const inner = this.checkEType(etype.kind.inner);
|
const subject = this.checkEType(etype.kind.subject);
|
||||||
return { type: "array", inner };
|
return { type: "array", subject };
|
||||||
}
|
}
|
||||||
if (etype.kind.type === "struct") {
|
if (etype.kind.type === "struct") {
|
||||||
const noTypeTest = etype.kind.fields.reduce(
|
const noTypeTest = etype.kind.fields.reduce(
|
||||||
@ -608,13 +1144,19 @@ export class Checker {
|
|||||||
this.report(`field ${declaredTwiceTest[2]} defined twice`, pos);
|
this.report(`field ${declaredTwiceTest[2]} defined twice`, pos);
|
||||||
return { type: "error" };
|
return { type: "error" };
|
||||||
}
|
}
|
||||||
const fields = etype.kind.fields.map((param): VTypeParam => ({
|
const fields = etype.kind.fields
|
||||||
|
.map((param): VTypeParam => ({
|
||||||
ident: param.ident,
|
ident: param.ident,
|
||||||
|
mut: true,
|
||||||
vtype: this.checkEType(param.etype!),
|
vtype: this.checkEType(param.etype!),
|
||||||
}));
|
}));
|
||||||
return { type: "struct", fields };
|
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) {
|
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 { Checker } from "./checker.ts";
|
||||||
import { CompoundAssignDesugarer } from "./desugar/compound_assign.ts";
|
import { CompoundAssignDesugarer } from "./desugar/compound_assign.ts";
|
||||||
|
import { StructLiteralDesugarer } from "./desugar/struct_literal.ts";
|
||||||
import { SpecialLoopDesugarer } from "./desugar/special_loop.ts";
|
import { SpecialLoopDesugarer } from "./desugar/special_loop.ts";
|
||||||
import { Reporter } from "./info.ts";
|
import { Reporter } from "./info.ts";
|
||||||
import { Lexer } from "./lexer.ts";
|
import { Lexer } from "./lexer.ts";
|
||||||
|
import { Monomorphizer } from "./mono.ts";
|
||||||
import { FnNamesMap, Lowerer } from "./lowerer.ts";
|
import { FnNamesMap, Lowerer } from "./lowerer.ts";
|
||||||
import { Parser } from "./parser.ts";
|
import { Parser } from "./parser.ts";
|
||||||
import { Resolver } from "./resolver.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 = {
|
import * as path from "jsr:@std/path";
|
||||||
filepath: string;
|
import { lowerAst } from "./middle/lower_ast.ts";
|
||||||
program: number[];
|
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 = {
|
export type CompileResult = {
|
||||||
program: number[];
|
program: number[];
|
||||||
@ -20,19 +31,22 @@ export type CompileResult = {
|
|||||||
|
|
||||||
export class Compiler {
|
export class Compiler {
|
||||||
private astCreator = new AstCreator();
|
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> {
|
public async compile(): Promise<CompileResult> {
|
||||||
const text = await Deno.readTextFile(this.startFilePath);
|
const { ast } = new ModTree(
|
||||||
|
this.startFilePath,
|
||||||
const lexer = new Lexer(text, this.reporter);
|
this.astCreator,
|
||||||
|
this.reporter,
|
||||||
const parser = new Parser(lexer, this.astCreator, this.reporter);
|
).resolve();
|
||||||
const ast = parser.parse();
|
|
||||||
|
|
||||||
new SpecialLoopDesugarer(this.astCreator).desugar(ast);
|
new SpecialLoopDesugarer(this.astCreator).desugar(ast);
|
||||||
|
new ArrayLiteralDesugarer(this.astCreator).desugar(ast);
|
||||||
|
new StructLiteralDesugarer(this.astCreator).desugar(ast);
|
||||||
|
|
||||||
new Resolver(this.reporter).resolve(ast);
|
new Resolver(this.reporter).resolve(ast);
|
||||||
|
|
||||||
@ -40,16 +54,130 @@ export class Compiler {
|
|||||||
|
|
||||||
new Checker(this.reporter).check(ast);
|
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()) {
|
if (this.reporter.errorOccured()) {
|
||||||
console.error("Errors occurred, stopping compilation.");
|
console.error("Errors occurred, stopping compilation.");
|
||||||
Deno.exit(1);
|
Deno.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const lowerer = new Lowerer(lexer.currentPos());
|
const mir = lowerAst(ast);
|
||||||
lowerer.lower(ast);
|
|
||||||
// lowerer.printProgram();
|
|
||||||
const { program, fnNames } = lowerer.finish();
|
|
||||||
|
|
||||||
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",
|
"version": "4",
|
||||||
"specifiers": {
|
"specifiers": {
|
||||||
|
"jsr:@std/path@*": "1.0.8",
|
||||||
"npm:@types/node@*": "22.5.4"
|
"npm:@types/node@*": "22.5.4"
|
||||||
},
|
},
|
||||||
|
"jsr": {
|
||||||
|
"@std/path@1.0.8": {
|
||||||
|
"integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be"
|
||||||
|
}
|
||||||
|
},
|
||||||
"npm": {
|
"npm": {
|
||||||
"@types/node@22.5.4": {
|
"@types/node@22.5.4": {
|
||||||
"integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
|
"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: [
|
stmts: [
|
||||||
Stmt({
|
Stmt({
|
||||||
type: "let",
|
type: "let",
|
||||||
param: { ident: "::values", pos: npos },
|
param: this.astCreator.param({
|
||||||
|
ident: "::values",
|
||||||
|
mut: true,
|
||||||
|
pos: npos,
|
||||||
|
}),
|
||||||
value: expr.kind.value,
|
value: expr.kind.value,
|
||||||
}),
|
}),
|
||||||
Stmt({
|
Stmt({
|
||||||
type: "let",
|
type: "let",
|
||||||
param: { ident: "::length", pos: npos },
|
param: this.astCreator.param({
|
||||||
|
ident: "::length",
|
||||||
|
mut: false,
|
||||||
|
pos: npos,
|
||||||
|
}),
|
||||||
value: Expr({
|
value: Expr({
|
||||||
type: "call",
|
type: "call",
|
||||||
|
subject: Expr({
|
||||||
|
type: "path",
|
||||||
subject: Expr({
|
subject: Expr({
|
||||||
type: "ident",
|
type: "ident",
|
||||||
value: "int_array_length",
|
ident: "std",
|
||||||
|
}),
|
||||||
|
ident: "array_length",
|
||||||
}),
|
}),
|
||||||
args: [
|
args: [
|
||||||
Expr({
|
Expr({
|
||||||
type: "ident",
|
type: "ident",
|
||||||
value: "::values",
|
ident: "::values",
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
Stmt({
|
Stmt({
|
||||||
type: "let",
|
type: "let",
|
||||||
param: { ident: "::index", pos: npos },
|
param: this.astCreator.param({
|
||||||
|
ident: "::index",
|
||||||
|
mut: true,
|
||||||
|
pos: npos,
|
||||||
|
}),
|
||||||
value: Expr({ type: "int", value: 0 }),
|
value: Expr({ type: "int", value: 0 }),
|
||||||
}, expr.pos),
|
}, expr.pos),
|
||||||
Stmt({
|
Stmt({
|
||||||
@ -114,11 +130,11 @@ export class SpecialLoopDesugarer implements AstVisitor {
|
|||||||
binaryType: "<",
|
binaryType: "<",
|
||||||
left: Expr({
|
left: Expr({
|
||||||
type: "ident",
|
type: "ident",
|
||||||
value: "::index",
|
ident: "::index",
|
||||||
}),
|
}),
|
||||||
right: Expr({
|
right: Expr({
|
||||||
type: "ident",
|
type: "ident",
|
||||||
value: "::length",
|
ident: "::length",
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
@ -139,11 +155,11 @@ export class SpecialLoopDesugarer implements AstVisitor {
|
|||||||
type: "index",
|
type: "index",
|
||||||
subject: Expr({
|
subject: Expr({
|
||||||
type: "ident",
|
type: "ident",
|
||||||
value: "::values",
|
ident: "::values",
|
||||||
}),
|
}),
|
||||||
value: Expr({
|
value: Expr({
|
||||||
type: "ident",
|
type: "ident",
|
||||||
value: "::index",
|
ident: "::index",
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}, expr.pos),
|
}, expr.pos),
|
||||||
@ -156,7 +172,7 @@ export class SpecialLoopDesugarer implements AstVisitor {
|
|||||||
assignType: "+=",
|
assignType: "+=",
|
||||||
subject: Expr({
|
subject: Expr({
|
||||||
type: "ident",
|
type: "ident",
|
||||||
value: "::index",
|
ident: "::index",
|
||||||
}),
|
}),
|
||||||
value: Expr({
|
value: Expr({
|
||||||
type: "int",
|
type: "int",
|
||||||
|
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";
|
import { Pos } from "./token.ts";
|
||||||
|
|
||||||
export type Report = {
|
export type Report = {
|
||||||
type: "error" | "note";
|
type: "error" | "warning" | "note";
|
||||||
reporter: string;
|
reporter: string;
|
||||||
pos?: Pos;
|
pos?: Pos;
|
||||||
msg: string;
|
msg: string;
|
||||||
@ -11,16 +11,27 @@ export class Reporter {
|
|||||||
private reports: Report[] = [];
|
private reports: Report[] = [];
|
||||||
private errorSet = false;
|
private errorSet = false;
|
||||||
|
|
||||||
|
public constructor(private filePath: string) {}
|
||||||
|
|
||||||
|
public setFilePath(filePath: string) {
|
||||||
|
this.filePath = filePath;
|
||||||
|
}
|
||||||
|
|
||||||
public reportError(report: Omit<Report, "type">) {
|
public reportError(report: Omit<Report, "type">) {
|
||||||
this.reports.push({ ...report, type: "error" });
|
this.reports.push({ ...report, type: "error" });
|
||||||
this.printReport({ ...report, type: "error" });
|
this.printReport({ ...report, type: "error" });
|
||||||
this.errorSet = true;
|
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) {
|
private printReport({ reporter, type, pos, msg }: Report) {
|
||||||
console.error(
|
console.error(
|
||||||
`${reporter} ${type}: ${msg}${
|
`${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();
|
this.step();
|
||||||
}
|
}
|
||||||
const keywords = [
|
const keywords = [
|
||||||
|
"false",
|
||||||
|
"true",
|
||||||
|
"null",
|
||||||
|
"int",
|
||||||
|
"bool",
|
||||||
|
"string",
|
||||||
"break",
|
"break",
|
||||||
"return",
|
"return",
|
||||||
"let",
|
"let",
|
||||||
|
"mut",
|
||||||
"fn",
|
"fn",
|
||||||
"loop",
|
"loop",
|
||||||
"if",
|
"if",
|
||||||
"else",
|
"else",
|
||||||
"struct",
|
"struct",
|
||||||
"import",
|
|
||||||
"false",
|
|
||||||
"true",
|
|
||||||
"null",
|
|
||||||
"or",
|
"or",
|
||||||
"and",
|
"and",
|
||||||
"not",
|
"not",
|
||||||
"while",
|
"while",
|
||||||
"for",
|
"for",
|
||||||
"in",
|
"in",
|
||||||
|
"mod",
|
||||||
|
"pub",
|
||||||
|
"use",
|
||||||
|
"type_alias",
|
||||||
];
|
];
|
||||||
if (keywords.includes(value)) {
|
if (keywords.includes(value)) {
|
||||||
return this.token(value, pos);
|
return this.token(value, pos);
|
||||||
@ -70,6 +77,32 @@ export class Lexer {
|
|||||||
return { ...this.token("int", pos), intValue: 0 };
|
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('"')) {
|
if (this.test('"')) {
|
||||||
this.step();
|
this.step();
|
||||||
let value = "";
|
let value = "";
|
||||||
@ -96,7 +129,7 @@ export class Lexer {
|
|||||||
this.step();
|
this.step();
|
||||||
return { ...this.token("string", pos), stringValue: value };
|
return { ...this.token("string", pos), stringValue: value };
|
||||||
}
|
}
|
||||||
if (this.test(/[\+\{\};=\-\*\(\)\.,:;\[\]><!0#]/)) {
|
if (this.test(/[\+\{\};=\-\*\(\)\.,:;\[\]><!0#&]/)) {
|
||||||
const first = this.current();
|
const first = this.current();
|
||||||
this.step();
|
this.step();
|
||||||
if (first === "=" && !this.done() && this.test("=")) {
|
if (first === "=" && !this.done() && this.test("=")) {
|
||||||
|
@ -1,48 +1,65 @@
|
|||||||
import { Builtins, Ops } from "./arch.ts";
|
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 { 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 { Pos } from "./token.ts";
|
||||||
|
import { vtypeToString } from "./vtype.ts";
|
||||||
|
|
||||||
export type FnNamesMap = { [pc: number]: string };
|
export type FnNamesMap = { [pc: number]: string };
|
||||||
|
|
||||||
export class Lowerer {
|
export class Lowerer {
|
||||||
private program = Assembler.newRoot();
|
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.addClearingSourceMap();
|
||||||
this.program.add(Ops.PushPtr, { label: "main" });
|
this.program.add(Ops.PushPtr, { label: "main" });
|
||||||
this.program.add(Ops.Call, 0);
|
this.program.add(Ops.Call, 0);
|
||||||
this.program.add(Ops.PushPtr, { label: "_exit" });
|
this.program.add(Ops.PushPtr, { label: "_exit" });
|
||||||
this.program.add(Ops.Jump);
|
this.program.add(Ops.Jump);
|
||||||
this.scoutFnHeaders(stmts);
|
|
||||||
for (const stmt of stmts) {
|
|
||||||
this.lowerStaticStmt(stmt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private addConcluding() {
|
||||||
this.program.setLabel({ label: "_exit" });
|
this.program.setLabel({ label: "_exit" });
|
||||||
this.addSourceMap(this.lastPos);
|
this.addSourceMap(this.lastPos);
|
||||||
this.program.add(Ops.Pop);
|
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) {
|
private addSourceMap({ index, line, col }: Pos) {
|
||||||
this.program.add(Ops.SourceMap, index, line, col);
|
this.program.add(Ops.SourceMap, index, line, col);
|
||||||
}
|
}
|
||||||
@ -51,30 +68,84 @@ export class Lowerer {
|
|||||||
this.program.add(Ops.SourceMap, 0, 1, 1);
|
this.program.add(Ops.SourceMap, 0, 1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private scoutFnHeaders(stmts: Stmt[]) {
|
public printProgram() {
|
||||||
for (const stmt of stmts) {
|
this.program.printProgram();
|
||||||
if (stmt.kind.type !== "fn") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const label = stmt.kind.ident === "main"
|
|
||||||
? "main"
|
|
||||||
: `${stmt.kind.ident}_${stmt.id}`;
|
|
||||||
this.fnStmtIdLabelMap[stmt.id] = label;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
private lowerFnStmt(stmt: Stmt) {
|
||||||
switch (stmt.kind.type) {
|
if (stmt.kind.type !== "fn") {
|
||||||
case "fn":
|
throw new Error();
|
||||||
return this.lowerFnStmt(stmt);
|
|
||||||
case "error":
|
|
||||||
case "break":
|
|
||||||
case "return":
|
|
||||||
case "let":
|
|
||||||
case "assign":
|
|
||||||
case "expr":
|
|
||||||
}
|
}
|
||||||
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) {
|
private lowerStmt(stmt: Stmt) {
|
||||||
@ -107,7 +178,7 @@ export class Lowerer {
|
|||||||
switch (stmt.kind.subject.kind.type) {
|
switch (stmt.kind.subject.kind.type) {
|
||||||
case "field": {
|
case "field": {
|
||||||
this.lowerExpr(stmt.kind.subject.kind.subject);
|
this.lowerExpr(stmt.kind.subject.kind.subject);
|
||||||
this.program.add(Ops.PushString, stmt.kind.subject.kind.value);
|
this.program.add(Ops.PushString, stmt.kind.subject.kind.ident);
|
||||||
this.program.add(Ops.Builtin, Builtins.StructSet);
|
this.program.add(Ops.Builtin, Builtins.StructSet);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -153,52 +224,6 @@ export class Lowerer {
|
|||||||
this.program.add(Ops.Jump);
|
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[]) {
|
private lowerFnBuiltinBody(annoArgs: Expr[]) {
|
||||||
if (annoArgs.length !== 1) {
|
if (annoArgs.length !== 1) {
|
||||||
throw new Error("invalid # of arguments to builtin annotation");
|
throw new Error("invalid # of arguments to builtin annotation");
|
||||||
@ -209,7 +234,7 @@ export class Lowerer {
|
|||||||
`unexpected argument type '${anno.kind.type}' expected 'ident'`,
|
`unexpected argument type '${anno.kind.type}' expected 'ident'`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const value = anno.kind.value;
|
const value = anno.kind.ident;
|
||||||
const builtin = Object.entries(Builtins).find((entry) =>
|
const builtin = Object.entries(Builtins).find((entry) =>
|
||||||
entry[0] === value
|
entry[0] === value
|
||||||
)?.[1];
|
)?.[1];
|
||||||
@ -252,11 +277,13 @@ export class Lowerer {
|
|||||||
case "group":
|
case "group":
|
||||||
return void this.lowerExpr(expr.kind.expr);
|
return void this.lowerExpr(expr.kind.expr);
|
||||||
case "field":
|
case "field":
|
||||||
break;
|
return this.lowerFieldExpr(expr);
|
||||||
case "index":
|
case "index":
|
||||||
return this.lowerIndexExpr(expr);
|
return this.lowerIndexExpr(expr);
|
||||||
case "call":
|
case "call":
|
||||||
return this.lowerCallExpr(expr);
|
return this.lowerCallExpr(expr);
|
||||||
|
case "etype_args":
|
||||||
|
return this.lowerETypeArgsExpr(expr);
|
||||||
case "unary":
|
case "unary":
|
||||||
return this.lowerUnaryExpr(expr);
|
return this.lowerUnaryExpr(expr);
|
||||||
case "binary":
|
case "binary":
|
||||||
@ -271,6 +298,20 @@ export class Lowerer {
|
|||||||
throw new Error(`unhandled expr '${expr.kind.type}'`);
|
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) {
|
private lowerIndexExpr(expr: Expr) {
|
||||||
if (expr.kind.type !== "index") {
|
if (expr.kind.type !== "index") {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
@ -306,8 +347,42 @@ export class Lowerer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (expr.kind.sym.type === "fn") {
|
if (expr.kind.sym.type === "fn") {
|
||||||
const label = this.fnStmtIdLabelMap[expr.kind.sym.stmt.id];
|
// Is this smart? Well, my presumption is
|
||||||
this.program.add(Ops.PushPtr, { label });
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
throw new Error(`unhandled sym type '${expr.kind.sym.type}'`);
|
throw new Error(`unhandled sym type '${expr.kind.sym.type}'`);
|
||||||
@ -430,6 +505,18 @@ export class Lowerer {
|
|||||||
default:
|
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 (vtype.type === "string") {
|
||||||
if (expr.kind.binaryType === "+") {
|
if (expr.kind.binaryType === "+") {
|
||||||
this.program.add(Ops.Builtin, Builtins.StringConcat);
|
this.program.add(Ops.Builtin, Builtins.StringConcat);
|
||||||
@ -462,9 +549,18 @@ export class Lowerer {
|
|||||||
this.lowerExpr(arg);
|
this.lowerExpr(arg);
|
||||||
}
|
}
|
||||||
this.lowerExpr(expr.kind.subject);
|
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);
|
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) {
|
private lowerIfExpr(expr: Expr) {
|
||||||
if (expr.kind.type !== "if") {
|
if (expr.kind.type !== "if") {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
@ -528,7 +624,6 @@ export class Lowerer {
|
|||||||
}
|
}
|
||||||
const outerLocals = this.locals;
|
const outerLocals = this.locals;
|
||||||
this.locals = new LocalLeaf(this.locals);
|
this.locals = new LocalLeaf(this.locals);
|
||||||
this.scoutFnHeaders(expr.kind.stmts);
|
|
||||||
for (const stmt of expr.kind.stmts) {
|
for (const stmt of expr.kind.stmts) {
|
||||||
this.addSourceMap(stmt.pos);
|
this.addSourceMap(stmt.pos);
|
||||||
this.lowerStmt(stmt);
|
this.lowerStmt(stmt);
|
||||||
@ -541,8 +636,4 @@ export class Lowerer {
|
|||||||
}
|
}
|
||||||
this.locals = outerLocals;
|
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 {
|
import {
|
||||||
Anno,
|
|
||||||
AssignType,
|
AssignType,
|
||||||
AstCreator,
|
AstCreator,
|
||||||
BinaryType,
|
BinaryType,
|
||||||
@ -7,8 +6,11 @@ import {
|
|||||||
ETypeKind,
|
ETypeKind,
|
||||||
Expr,
|
Expr,
|
||||||
ExprKind,
|
ExprKind,
|
||||||
|
Field,
|
||||||
|
GenericParam,
|
||||||
Param,
|
Param,
|
||||||
Stmt,
|
Stmt,
|
||||||
|
StmtDetails,
|
||||||
StmtKind,
|
StmtKind,
|
||||||
UnaryType,
|
UnaryType,
|
||||||
} from "./ast.ts";
|
} from "./ast.ts";
|
||||||
@ -16,6 +18,8 @@ import { printStackTrace, Reporter } from "./info.ts";
|
|||||||
import { Lexer } from "./lexer.ts";
|
import { Lexer } from "./lexer.ts";
|
||||||
import { Pos, Token } from "./token.ts";
|
import { Pos, Token } from "./token.ts";
|
||||||
|
|
||||||
|
type Res<T> = { ok: true; value: T } | { ok: false; pos?: Pos };
|
||||||
|
|
||||||
export class Parser {
|
export class Parser {
|
||||||
private currentToken: Token | null;
|
private currentToken: Token | null;
|
||||||
|
|
||||||
@ -34,25 +38,33 @@ export class Parser {
|
|||||||
private parseStmts(): Stmt[] {
|
private parseStmts(): Stmt[] {
|
||||||
const stmts: Stmt[] = [];
|
const stmts: Stmt[] = [];
|
||||||
while (!this.done()) {
|
while (!this.done()) {
|
||||||
if (this.test("fn")) {
|
stmts.push(this.parseStmt());
|
||||||
stmts.push(this.parseFn());
|
}
|
||||||
} else if (
|
return stmts;
|
||||||
this.test("let") || this.test("return") || this.test("break")
|
}
|
||||||
|
|
||||||
|
private parseStmt(): Stmt {
|
||||||
|
if (
|
||||||
|
["#", "pub", "mod", "fn"].some((tt) => this.test(tt))
|
||||||
) {
|
) {
|
||||||
stmts.push(this.parseSingleLineBlockStmt());
|
return this.parseItemStmt();
|
||||||
|
} else if (
|
||||||
|
["let", "type_alias", "return", "break"].some((tt) => this.test(tt))
|
||||||
|
) {
|
||||||
|
const expr = this.parseSingleLineBlockStmt();
|
||||||
this.eatSemicolon();
|
this.eatSemicolon();
|
||||||
|
return expr;
|
||||||
} else if (
|
} else if (
|
||||||
["{", "if", "loop", "while", "for"].some((tt) => this.test(tt))
|
["{", "if", "loop", "while", "for"].some((tt) => this.test(tt))
|
||||||
) {
|
) {
|
||||||
const expr = this.parseMultiLineBlockExpr();
|
const expr = this.parseMultiLineBlockExpr();
|
||||||
stmts.push(this.stmt({ type: "expr", expr }, expr.pos));
|
return (this.stmt({ type: "expr", expr }, expr.pos));
|
||||||
} else {
|
} else {
|
||||||
stmts.push(this.parseAssign());
|
const expr = this.parseAssign();
|
||||||
this.eatSemicolon();
|
this.eatSemicolon();
|
||||||
|
return expr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return stmts;
|
|
||||||
}
|
|
||||||
|
|
||||||
private parseMultiLineBlockExpr(): Expr {
|
private parseMultiLineBlockExpr(): Expr {
|
||||||
const pos = this.pos();
|
const pos = this.pos();
|
||||||
@ -80,6 +92,9 @@ export class Parser {
|
|||||||
if (this.test("let")) {
|
if (this.test("let")) {
|
||||||
return this.parseLet();
|
return this.parseLet();
|
||||||
}
|
}
|
||||||
|
if (this.test("type_alias")) {
|
||||||
|
return this.parseTypeAlias();
|
||||||
|
}
|
||||||
if (this.test("return")) {
|
if (this.test("return")) {
|
||||||
return this.parseReturn();
|
return this.parseReturn();
|
||||||
}
|
}
|
||||||
@ -107,19 +122,21 @@ export class Parser {
|
|||||||
private parseBlock(): Expr {
|
private parseBlock(): Expr {
|
||||||
const pos = this.pos();
|
const pos = this.pos();
|
||||||
this.step();
|
this.step();
|
||||||
let stmts: Stmt[] = [];
|
const stmts: Stmt[] = [];
|
||||||
while (!this.done()) {
|
while (!this.done()) {
|
||||||
if (this.test("}")) {
|
if (this.test("}")) {
|
||||||
this.step();
|
this.step();
|
||||||
return this.expr({ type: "block", stmts }, pos);
|
return this.expr({ type: "block", stmts }, pos);
|
||||||
} else if (
|
} 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());
|
stmts.push(this.parseSingleLineBlockStmt());
|
||||||
this.eatSemicolon();
|
this.eatSemicolon();
|
||||||
} else if (this.test("fn")) {
|
|
||||||
stmts.push(this.parseSingleLineBlockStmt());
|
|
||||||
stmts.push(this.parseFn());
|
|
||||||
} else if (
|
} else if (
|
||||||
["{", "if", "loop", "while", "for"].some((tt) => this.test(tt))
|
["{", "if", "loop", "while", "for"].some((tt) => this.test(tt))
|
||||||
) {
|
) {
|
||||||
@ -163,7 +180,110 @@ export class Parser {
|
|||||||
return this.expr({ type: "error" }, pos);
|
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();
|
const pos = this.pos();
|
||||||
this.step();
|
this.step();
|
||||||
if (!this.test("ident")) {
|
if (!this.test("ident")) {
|
||||||
@ -172,21 +292,21 @@ export class Parser {
|
|||||||
}
|
}
|
||||||
const ident = this.current().identValue!;
|
const ident = this.current().identValue!;
|
||||||
this.step();
|
this.step();
|
||||||
|
let genericParams: GenericParam[] | undefined;
|
||||||
|
if (this.test("<")) {
|
||||||
|
genericParams = this.parseFnETypeParams();
|
||||||
|
}
|
||||||
if (!this.test("(")) {
|
if (!this.test("(")) {
|
||||||
this.report("expected '('");
|
this.report("expected '('");
|
||||||
return this.stmt({ type: "error" }, pos);
|
return this.stmt({ type: "error" }, pos);
|
||||||
}
|
}
|
||||||
const params = this.parseFnParams();
|
const params = this.parseFnParams();
|
||||||
let returnType: EType | null = null;
|
let returnType: EType | undefined;
|
||||||
if (this.test("->")) {
|
if (this.test("->")) {
|
||||||
this.step();
|
this.step();
|
||||||
returnType = this.parseEType();
|
returnType = this.parseEType();
|
||||||
}
|
}
|
||||||
|
|
||||||
let anno: Anno | null = null;
|
|
||||||
if (this.test("#")) {
|
|
||||||
anno = this.parseAnno();
|
|
||||||
}
|
|
||||||
if (!this.test("{")) {
|
if (!this.test("{")) {
|
||||||
this.report("expected block");
|
this.report("expected block");
|
||||||
return this.stmt({ type: "error" }, pos);
|
return this.stmt({ type: "error" }, pos);
|
||||||
@ -196,105 +316,104 @@ export class Parser {
|
|||||||
{
|
{
|
||||||
type: "fn",
|
type: "fn",
|
||||||
ident,
|
ident,
|
||||||
|
genericParams,
|
||||||
params,
|
params,
|
||||||
returnType: returnType !== null ? returnType : undefined,
|
returnType,
|
||||||
body,
|
body,
|
||||||
anno: anno != null ? anno : undefined,
|
|
||||||
},
|
},
|
||||||
pos,
|
pos,
|
||||||
|
details,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private parseAnnoArgs(): Expr[] {
|
private parseFnETypeParams(): GenericParam[] {
|
||||||
this.step();
|
return this.parseDelimitedList(this.parseETypeParam, ">", ",");
|
||||||
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 parseAnno(): Anno | null {
|
private parseETypeParam(index: number): Res<GenericParam> {
|
||||||
const pos = this.pos();
|
const pos = this.pos();
|
||||||
this.step();
|
if (this.test("ident")) {
|
||||||
if (!this.test("[")) {
|
|
||||||
this.report("expected '['");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
this.step();
|
|
||||||
if (!this.test("ident")) {
|
|
||||||
this.report("expected identifier");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const ident = this.current().identValue!;
|
const ident = this.current().identValue!;
|
||||||
const values = this.parseAnnoArgs();
|
|
||||||
if (!this.test("]")) {
|
|
||||||
this.report("expected ']'");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
this.step();
|
this.step();
|
||||||
return { ident, pos, values };
|
return {
|
||||||
|
ok: true,
|
||||||
|
value: this.astCreator.genericParam({ index, ident, pos }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.report("expected generic parameter");
|
||||||
|
return { ok: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
private parseFnParams(): Param[] {
|
private parseFnParams(): Param[] {
|
||||||
this.step();
|
return this.parseDelimitedList(this.parseParam, ")", ",");
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
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!;
|
const ident = this.current().identValue!;
|
||||||
this.step();
|
this.step();
|
||||||
if (this.test(":")) {
|
if (this.test(":")) {
|
||||||
this.step();
|
this.step();
|
||||||
const etype = this.parseEType();
|
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");
|
this.report("expected param");
|
||||||
return { ok: false };
|
return { ok: false };
|
||||||
@ -317,6 +436,17 @@ export class Parser {
|
|||||||
return this.stmt({ type: "let", param, value }, pos);
|
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 {
|
private parseAssign(): Stmt {
|
||||||
const pos = this.pos();
|
const pos = this.pos();
|
||||||
const subject = this.parseExpr();
|
const subject = this.parseExpr();
|
||||||
@ -444,6 +574,80 @@ export class Parser {
|
|||||||
return this.expr({ type: "for", decl, cond, incr, body }, pos);
|
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 {
|
private parseIf(): Expr {
|
||||||
const pos = this.pos();
|
const pos = this.pos();
|
||||||
this.step();
|
this.step();
|
||||||
@ -586,54 +790,45 @@ export class Parser {
|
|||||||
const subject = this.parsePrefix();
|
const subject = this.parsePrefix();
|
||||||
return this.expr({ type: "unary", unaryType, subject }, pos);
|
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();
|
return this.parsePostfix();
|
||||||
}
|
}
|
||||||
|
|
||||||
private parsePostfix(): Expr {
|
private parsePostfix(): Expr {
|
||||||
let subject = this.parseOperand();
|
let subject = this.parseOperand();
|
||||||
while (true) {
|
while (true) {
|
||||||
const pos = this.pos();
|
|
||||||
if (this.test(".")) {
|
if (this.test(".")) {
|
||||||
this.step();
|
subject = this.parseFieldTail(subject);
|
||||||
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);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (this.test("[")) {
|
if (this.test("[")) {
|
||||||
this.step();
|
subject = this.parseIndexTail(subject);
|
||||||
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);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (this.test("(")) {
|
if (this.test("(")) {
|
||||||
this.step();
|
subject = this.parseCallTail(subject);
|
||||||
let args: Expr[] = [];
|
continue;
|
||||||
if (!this.test(")")) {
|
|
||||||
args.push(this.parseExpr());
|
|
||||||
while (this.test(",")) {
|
|
||||||
this.step();
|
|
||||||
if (this.test(")")) {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
args.push(this.parseExpr());
|
if (this.test("::")) {
|
||||||
|
subject = this.parsePathTail(subject);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
if (this.test("::<")) {
|
||||||
if (!this.test(")")) {
|
subject = this.parseETypeArgsTail(subject);
|
||||||
this.report("expected ')'");
|
|
||||||
return this.expr({ type: "error" }, pos);
|
|
||||||
}
|
|
||||||
this.step();
|
|
||||||
subject = this.expr({ type: "call", subject, args }, pos);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -641,12 +836,79 @@ export class Parser {
|
|||||||
return subject;
|
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 {
|
private parseOperand(): Expr {
|
||||||
const pos = this.pos();
|
const pos = this.pos();
|
||||||
if (this.test("ident")) {
|
if (this.test("ident")) {
|
||||||
const value = this.current().identValue!;
|
const ident = this.current().identValue!;
|
||||||
this.step();
|
this.step();
|
||||||
return this.expr({ type: "ident", value }, pos);
|
return this.expr({ type: "ident", ident }, pos);
|
||||||
}
|
}
|
||||||
if (this.test("int")) {
|
if (this.test("int")) {
|
||||||
const value = this.current().intValue!;
|
const value = this.current().intValue!;
|
||||||
@ -680,6 +942,12 @@ export class Parser {
|
|||||||
this.step();
|
this.step();
|
||||||
return this.expr({ type: "group", expr }, pos);
|
return this.expr({ type: "group", expr }, pos);
|
||||||
}
|
}
|
||||||
|
if (this.test("[")) {
|
||||||
|
return this.parseArray();
|
||||||
|
}
|
||||||
|
if (this.test("struct")) {
|
||||||
|
return this.parseStruct();
|
||||||
|
}
|
||||||
if (this.test("{")) {
|
if (this.test("{")) {
|
||||||
return this.parseBlock();
|
return this.parseBlock();
|
||||||
}
|
}
|
||||||
@ -690,27 +958,36 @@ export class Parser {
|
|||||||
return this.parseLoop();
|
return this.parseLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.report("expected expr", pos);
|
this.report(`expected expr, got '${this.current().type}'`, pos);
|
||||||
this.step();
|
this.step();
|
||||||
return this.expr({ type: "error" }, pos);
|
return this.expr({ type: "error" }, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
private parseEType(): EType {
|
private parseEType(): EType {
|
||||||
const pos = this.pos();
|
const pos = this.pos();
|
||||||
|
if (["null", "int", "bool", "string"].includes(this.current().type)) {
|
||||||
|
const type = this.current().type as
|
||||||
|
| "null"
|
||||||
|
| "int"
|
||||||
|
| "bool"
|
||||||
|
| "string";
|
||||||
|
this.step();
|
||||||
|
return this.etype({ type }, pos);
|
||||||
|
}
|
||||||
if (this.test("ident")) {
|
if (this.test("ident")) {
|
||||||
const ident = this.current().identValue!;
|
const ident = this.current().identValue!;
|
||||||
this.step();
|
this.step();
|
||||||
return this.etype({ type: "ident", value: ident }, pos);
|
return this.etype({ type: "ident", ident: ident }, pos);
|
||||||
}
|
}
|
||||||
if (this.test("[")) {
|
if (this.test("[")) {
|
||||||
this.step();
|
this.step();
|
||||||
const inner = this.parseEType();
|
const subject = this.parseEType();
|
||||||
if (!this.test("]")) {
|
if (!this.test("]")) {
|
||||||
this.report("expected ']'", pos);
|
this.report("expected ']'", pos);
|
||||||
return this.etype({ type: "error" }, pos);
|
return this.etype({ type: "error" }, pos);
|
||||||
}
|
}
|
||||||
this.step();
|
this.step();
|
||||||
return this.etype({ type: "array", inner }, pos);
|
return this.etype({ type: "array", subject }, pos);
|
||||||
}
|
}
|
||||||
if (this.test("struct")) {
|
if (this.test("struct")) {
|
||||||
this.step();
|
this.step();
|
||||||
@ -721,6 +998,26 @@ export class Parser {
|
|||||||
const fields = this.parseETypeStructFields();
|
const fields = this.parseETypeStructFields();
|
||||||
return this.etype({ type: "struct", fields }, pos);
|
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");
|
this.report("expected type");
|
||||||
return this.etype({ type: "error" }, pos);
|
return this.etype({ type: "error" }, pos);
|
||||||
}
|
}
|
||||||
@ -777,7 +1074,6 @@ export class Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private report(msg: string, pos = this.pos()) {
|
private report(msg: string, pos = this.pos()) {
|
||||||
console.log(`Parser: ${msg} at ${pos.line}:${pos.col}`);
|
|
||||||
this.reporter.reportError({
|
this.reporter.reportError({
|
||||||
msg,
|
msg,
|
||||||
pos,
|
pos,
|
||||||
@ -786,8 +1082,8 @@ export class Parser {
|
|||||||
printStackTrace();
|
printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
private stmt(kind: StmtKind, pos: Pos): Stmt {
|
private stmt(kind: StmtKind, pos: Pos, details?: StmtDetails): Stmt {
|
||||||
return this.astCreator.stmt(kind, pos);
|
return this.astCreator.stmt(kind, pos, details);
|
||||||
}
|
}
|
||||||
|
|
||||||
private expr(kind: ExprKind, pos: Pos): Expr {
|
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 {
|
import {
|
||||||
AstVisitor,
|
AstVisitor,
|
||||||
|
visitEType,
|
||||||
visitExpr,
|
visitExpr,
|
||||||
|
visitParam,
|
||||||
VisitRes,
|
VisitRes,
|
||||||
visitStmt,
|
visitStmt,
|
||||||
visitStmts,
|
visitStmts,
|
||||||
} from "./ast_visitor.ts";
|
} from "./ast_visitor.ts";
|
||||||
import { printStackTrace, Reporter } from "./info.ts";
|
import { printStackTrace, Reporter } from "./info.ts";
|
||||||
import {
|
import {
|
||||||
|
EntryModSyms,
|
||||||
FnSyms,
|
FnSyms,
|
||||||
GlobalSyms,
|
|
||||||
LeafSyms,
|
LeafSyms,
|
||||||
StaticSyms,
|
ModSyms,
|
||||||
Syms,
|
Syms,
|
||||||
} from "./resolver_syms.ts";
|
} from "./resolver_syms.ts";
|
||||||
import { Pos } from "./token.ts";
|
import { Pos } from "./token.ts";
|
||||||
|
|
||||||
export class Resolver implements AstVisitor<[Syms]> {
|
export class Resolver implements AstVisitor<[Syms]> {
|
||||||
private root = new GlobalSyms();
|
|
||||||
|
|
||||||
public constructor(private reporter: Reporter) {
|
public constructor(private reporter: Reporter) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public resolve(stmts: Stmt[]): VisitRes {
|
public resolve(stmts: Stmt[]): VisitRes {
|
||||||
const scopeSyms = new StaticSyms(this.root);
|
const syms = new EntryModSyms("root");
|
||||||
this.scoutFnStmts(stmts, scopeSyms);
|
this.scout(stmts, syms);
|
||||||
visitStmts(stmts, this, scopeSyms);
|
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";
|
return "stop";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,9 +96,10 @@ export class Resolver implements AstVisitor<[Syms]> {
|
|||||||
this.reportAlreadyDefined(ident, stmt.pos, syms);
|
this.reportAlreadyDefined(ident, stmt.pos, syms);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
syms.define(ident, {
|
stmt.kind.param.sym = syms.define(ident, {
|
||||||
ident,
|
ident,
|
||||||
type: "let",
|
type: "let",
|
||||||
|
fullPath: ident,
|
||||||
pos: stmt.kind.param.pos,
|
pos: stmt.kind.param.pos,
|
||||||
stmt,
|
stmt,
|
||||||
param: stmt.kind.param,
|
param: stmt.kind.param,
|
||||||
@ -49,23 +107,11 @@ export class Resolver implements AstVisitor<[Syms]> {
|
|||||||
return "stop";
|
return "stop";
|
||||||
}
|
}
|
||||||
|
|
||||||
private scoutFnStmts(stmts: Stmt[], syms: Syms) {
|
visitTypeAliasStmt(stmt: Stmt, _syms: Syms): VisitRes {
|
||||||
for (const stmt of stmts) {
|
if (stmt.kind.type !== "type_alias") {
|
||||||
if (stmt.kind.type !== "fn") {
|
throw new Error("expected type_alias statement");
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
// nothing to do here
|
||||||
}
|
}
|
||||||
|
|
||||||
visitFnStmt(stmt: Stmt, syms: Syms): VisitRes {
|
visitFnStmt(stmt: Stmt, syms: Syms): VisitRes {
|
||||||
@ -73,18 +119,37 @@ export class Resolver implements AstVisitor<[Syms]> {
|
|||||||
throw new Error("expected fn statement");
|
throw new Error("expected fn statement");
|
||||||
}
|
}
|
||||||
const fnScopeSyms = new FnSyms(syms);
|
const fnScopeSyms = new FnSyms(syms);
|
||||||
for (const param of stmt.kind.params) {
|
for (const param of stmt.kind.genericParams ?? []) {
|
||||||
if (fnScopeSyms.definedLocally(param.ident)) {
|
if (fnScopeSyms.definedLocally(param.ident)) {
|
||||||
this.reportAlreadyDefined(param.ident, param.pos, syms);
|
this.reportAlreadyDefined(param.ident, param.pos, syms);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
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, {
|
fnScopeSyms.define(param.ident, {
|
||||||
ident: param.ident,
|
ident: param.ident,
|
||||||
type: "fn_param",
|
type: "fn_param",
|
||||||
|
fullPath: param.ident,
|
||||||
pos: param.pos,
|
pos: param.pos,
|
||||||
param,
|
param,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (stmt.kind.returnType) {
|
||||||
|
visitEType(stmt.kind.returnType, this, fnScopeSyms);
|
||||||
|
}
|
||||||
visitExpr(stmt.kind.body, this, fnScopeSyms);
|
visitExpr(stmt.kind.body, this, fnScopeSyms);
|
||||||
return "stop";
|
return "stop";
|
||||||
}
|
}
|
||||||
@ -93,18 +158,51 @@ export class Resolver implements AstVisitor<[Syms]> {
|
|||||||
if (expr.kind.type !== "ident") {
|
if (expr.kind.type !== "ident") {
|
||||||
throw new Error("expected ident");
|
throw new Error("expected ident");
|
||||||
}
|
}
|
||||||
const ident = expr.kind;
|
const ident = expr.kind.ident;
|
||||||
const symResult = syms.get(ident.value);
|
const symResult = syms.get(ident);
|
||||||
if (!symResult.ok) {
|
if (!symResult.ok) {
|
||||||
this.reportUseOfUndefined(ident.value, expr.pos, syms);
|
this.reportUseOfUndefined(ident, expr.pos, syms);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const sym = symResult.sym;
|
const sym = symResult.sym;
|
||||||
|
expr.kind = { 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 = {
|
expr.kind = {
|
||||||
type: "sym",
|
type: "sym",
|
||||||
ident: ident.value,
|
ident: expr.kind.ident,
|
||||||
sym,
|
sym: getRes.sym,
|
||||||
};
|
};
|
||||||
|
|
||||||
return "stop";
|
return "stop";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,7 +211,7 @@ export class Resolver implements AstVisitor<[Syms]> {
|
|||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
const childSyms = new LeafSyms(syms);
|
const childSyms = new LeafSyms(syms);
|
||||||
this.scoutFnStmts(expr.kind.stmts, childSyms);
|
this.scout(expr.kind.stmts, childSyms);
|
||||||
visitStmts(expr.kind.stmts, this, childSyms);
|
visitStmts(expr.kind.stmts, this, childSyms);
|
||||||
if (expr.kind.expr) {
|
if (expr.kind.expr) {
|
||||||
visitExpr(expr.kind.expr, this, childSyms);
|
visitExpr(expr.kind.expr, this, childSyms);
|
||||||
@ -134,6 +232,21 @@ export class Resolver implements AstVisitor<[Syms]> {
|
|||||||
return "stop";
|
return "stop";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
visitIdentEType(etype: EType, syms: Syms): VisitRes {
|
||||||
|
if (etype.kind.type !== "ident") {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
const ident = etype.kind.ident;
|
||||||
|
const symResult = syms.get(ident);
|
||||||
|
if (!symResult.ok) {
|
||||||
|
this.reportUseOfUndefined(ident, etype.pos, syms);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const sym = symResult.sym;
|
||||||
|
etype.kind = { type: "sym", ident, sym };
|
||||||
|
return "stop";
|
||||||
|
}
|
||||||
|
|
||||||
private reportUseOfUndefined(ident: string, pos: Pos, _syms: Syms) {
|
private reportUseOfUndefined(ident: string, pos: Pos, _syms: Syms) {
|
||||||
this.reporter.reportError({
|
this.reporter.reportError({
|
||||||
reporter: "Resolver",
|
reporter: "Resolver",
|
||||||
|
@ -1,66 +1,112 @@
|
|||||||
import { Sym } from "./ast.ts";
|
import type { Sym } from "./ast.ts";
|
||||||
|
|
||||||
export type SymMap = { [ident: string]: Sym };
|
export type SymMap = { [ident: string]: Sym };
|
||||||
|
|
||||||
|
type GetRes = { ok: true; sym: Sym } | { ok: false };
|
||||||
|
|
||||||
export interface Syms {
|
export interface Syms {
|
||||||
define(ident: string, sym: Sym): void;
|
define(ident: string, sym: Sym): Sym;
|
||||||
definedLocally(ident: string): boolean;
|
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 = {};
|
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") {
|
if (sym.type === "let") {
|
||||||
this.define(ident, {
|
return this.define(ident, {
|
||||||
...sym,
|
...sym,
|
||||||
type: "let_static",
|
type: "let_static",
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
this.syms[ident] = sym;
|
this.syms[ident] = sym;
|
||||||
|
return sym;
|
||||||
}
|
}
|
||||||
|
|
||||||
public definedLocally(ident: string): boolean {
|
public definedLocally(ident: string): boolean {
|
||||||
return ident in this.syms;
|
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) {
|
if (ident in this.syms) {
|
||||||
return { ok: true, sym: this.syms[ident] };
|
return { ok: true, sym: this.syms[ident] };
|
||||||
}
|
}
|
||||||
return { ok: false };
|
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 = {};
|
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") {
|
if (sym.type === "let") {
|
||||||
this.define(ident, {
|
return this.define(ident, {
|
||||||
...sym,
|
...sym,
|
||||||
type: "let_static",
|
type: "let_static",
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
this.syms[ident] = sym;
|
return this.syms[ident] = sym;
|
||||||
}
|
}
|
||||||
|
|
||||||
public definedLocally(ident: string): boolean {
|
public definedLocally(ident: string): boolean {
|
||||||
return ident in this.syms;
|
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) {
|
if (ident in this.syms) {
|
||||||
return { ok: true, sym: this.syms[ident] };
|
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 constructor(private parent: Syms) {}
|
||||||
|
|
||||||
public define(ident: string, sym: Sym) {
|
public define(ident: string, sym: Sym): Sym {
|
||||||
if (sym.type === "let") {
|
if (sym.type === "let") {
|
||||||
this.define(ident, {
|
return this.define(ident, {
|
||||||
...sym,
|
...sym,
|
||||||
type: "closure",
|
type: "closure",
|
||||||
inner: sym,
|
inner: sym,
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
this.syms[ident] = sym;
|
this.syms[ident] = sym;
|
||||||
|
return sym;
|
||||||
}
|
}
|
||||||
|
|
||||||
public definedLocally(ident: string): boolean {
|
public definedLocally(ident: string): boolean {
|
||||||
return ident in this.syms;
|
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) {
|
if (ident in this.syms) {
|
||||||
return { ok: true, sym: this.syms[ident] };
|
return { ok: true, sym: this.syms[ident] };
|
||||||
}
|
}
|
||||||
return this.parent.get(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 {
|
export class LeafSyms implements Syms {
|
||||||
@ -98,18 +159,34 @@ export class LeafSyms implements Syms {
|
|||||||
|
|
||||||
public constructor(private parent: Syms) {}
|
public constructor(private parent: Syms) {}
|
||||||
|
|
||||||
public define(ident: string, sym: Sym) {
|
public define(ident: string, sym: Sym): Sym {
|
||||||
this.syms[ident] = sym;
|
this.syms[ident] = sym;
|
||||||
|
return sym;
|
||||||
}
|
}
|
||||||
|
|
||||||
public definedLocally(ident: string): boolean {
|
public definedLocally(ident: string): boolean {
|
||||||
return ident in this.syms;
|
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) {
|
if (ident in this.syms) {
|
||||||
return { ok: true, sym: this.syms[ident] };
|
return { ok: true, sym: this.syms[ident] };
|
||||||
}
|
}
|
||||||
return this.parent.get(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: "int" }
|
||||||
| { type: "string" }
|
| { type: "string" }
|
||||||
| { type: "bool" }
|
| { 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: "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 = {
|
export type VTypeParam = {
|
||||||
ident: string;
|
ident: string;
|
||||||
|
mut: boolean;
|
||||||
vtype: VType;
|
vtype: VType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function vtypesEqual(a: VType, b: VType): boolean {
|
export type VTypeGenericParam = {
|
||||||
if (a.type !== b.type) {
|
id: number;
|
||||||
return false;
|
ident: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export type GenericArgsMap = { [id: number]: VType };
|
||||||
|
|
||||||
|
export function vtypesEqual(
|
||||||
|
a: VType,
|
||||||
|
b: VType,
|
||||||
|
generics?: GenericArgsMap,
|
||||||
|
): boolean {
|
||||||
if (
|
if (
|
||||||
["error", "unknown", "null", "int", "string", "bool"]
|
["error", "unknown", "null", "int", "string", "bool"]
|
||||||
.includes(a.type)
|
.includes(a.type) && a.type === b.type
|
||||||
) {
|
) {
|
||||||
return true;
|
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") {
|
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.type === "fn" && b.type === "fn") {
|
||||||
if (a.params.length !== b.params.length) {
|
if (a.params.length !== b.params.length) {
|
||||||
@ -36,11 +91,41 @@ export function vtypesEqual(a: VType, b: VType): boolean {
|
|||||||
return false;
|
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;
|
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 {
|
export function vtypeToString(vtype: VType): string {
|
||||||
if (
|
if (
|
||||||
["error", "unknown", "null", "int", "string", "bool"]
|
["error", "unknown", "null", "int", "string", "bool"]
|
||||||
@ -48,15 +133,36 @@ export function vtypeToString(vtype: VType): string {
|
|||||||
) {
|
) {
|
||||||
return vtype.type;
|
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") {
|
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") {
|
if (vtype.type === "fn") {
|
||||||
const paramString = vtype.params.map((param) =>
|
const paramString = vtype.params.map((param) =>
|
||||||
`${param.ident}: ${vtypeToString(param.vtype)}`
|
`${param.ident}: ${vtypeToString(param.vtype)}`
|
||||||
)
|
)
|
||||||
.join(", ");
|
.join(", ");
|
||||||
return `fn (${paramString}) -> ${vtypeToString(vtype.returnType)}`;
|
return `fn(${paramString}) -> ${vtypeToString(vtype.returnType)}`;
|
||||||
|
}
|
||||||
|
if (vtype.type === "generic") {
|
||||||
|
return `generic`;
|
||||||
}
|
}
|
||||||
throw new Error(`unhandled vtype '${vtype.type}'`);
|
throw new Error(`unhandled vtype '${vtype.type}'`);
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,8 @@ if exists("b:current_syntax")
|
|||||||
finish
|
finish
|
||||||
endif
|
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 Special null
|
||||||
syn keyword Type int string bool
|
syn keyword Type int string bool
|
||||||
syn keyword Boolean true false
|
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 '0x[0-9a-fA-F]\+'
|
||||||
syn match Number '0b[01]\+'
|
syn match Number '0b[01]\+'
|
||||||
|
|
||||||
|
syn match Character "'[^\\]'"
|
||||||
|
syn match Character "'\\.'"
|
||||||
|
|
||||||
syn region String start=+"+ skip=+\\"+ end=+"+
|
syn region String start=+"+ skip=+\\"+ end=+"+
|
||||||
|
|
||||||
syn keyword Todo contained TODO FIXME XXX NOTE
|
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 region Comment start=+/\*+ end=+\*/+ contains=Todo
|
||||||
|
|
||||||
|
|
||||||
syn match Identifier '[a-z_]\w*'
|
syn match Identifier '[a-z_]\w*'
|
||||||
syn match Type '[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 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"
|
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": {
|
"numbers": {
|
||||||
"patterns": [
|
"patterns": [
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
fn print(msg: string) #[builtin(print)] {
|
//
|
||||||
|
|
||||||
|
#[builtin(Print)]
|
||||||
|
fn print(msg: string) {
|
||||||
"hello" + 0
|
"hello" + 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,14 @@ fn add(a: int, b: int) -> int {
|
|||||||
|
|
||||||
fn main() -> int {
|
fn main() -> int {
|
||||||
let result = 0;
|
let result = 0;
|
||||||
let i = 0;
|
|
||||||
|
let a = 0;
|
||||||
|
let b = a;
|
||||||
|
let c = b;
|
||||||
|
let d = c;
|
||||||
|
|
||||||
|
|
||||||
|
let i = c;
|
||||||
loop {
|
loop {
|
||||||
if i >= 10 {
|
if i >= 10 {
|
||||||
break;
|
break;
|
||||||
|
106
examples/example_performance.slg
Normal file
106
examples/example_performance.slg
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
|
||||||
|
fn main() {
|
||||||
|
for (let i = 0; i < 100; i += 1) {
|
||||||
|
string_to_int_impl("497");
|
||||||
|
string_to_int_impl("0b1001100000");
|
||||||
|
string_to_int_impl("0");
|
||||||
|
string_to_int_impl("0x9f");
|
||||||
|
string_to_int_impl("055");
|
||||||
|
string_to_int_impl("0b1000000");
|
||||||
|
string_to_int_impl("0b101111101");
|
||||||
|
string_to_int_impl("271");
|
||||||
|
string_to_int_impl("0x2bd");
|
||||||
|
string_to_int_impl("0x31e");
|
||||||
|
string_to_int_impl("0b1001101111");
|
||||||
|
string_to_int_impl("0535");
|
||||||
|
string_to_int_impl("0x233");
|
||||||
|
string_to_int_impl("01622");
|
||||||
|
string_to_int_impl("0");
|
||||||
|
string_to_int_impl("0b10001010");
|
||||||
|
string_to_int_impl("0b1111001101");
|
||||||
|
string_to_int_impl("0x93");
|
||||||
|
string_to_int_impl("0x23e");
|
||||||
|
string_to_int_impl("0316");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string_to_int_impl(text: string) -> int {
|
||||||
|
let base_2_digits = "01";
|
||||||
|
let base_8_digits = base_2_digits + "234567";
|
||||||
|
let base_10_digits = base_8_digits + "89";
|
||||||
|
let base_16_digits = base_10_digits + "abcdef";
|
||||||
|
|
||||||
|
let len = string_length(text);
|
||||||
|
if len == 0 {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if text[0] == "0"[0] {
|
||||||
|
if len == 1 {
|
||||||
|
0
|
||||||
|
} else if text[1] == "b"[0] {
|
||||||
|
parse_digits(string_slice(text, 2, -1), 2, base_2_digits)
|
||||||
|
} else if text[1] == "x"[0] {
|
||||||
|
parse_digits(string_slice(text, 2, -1), 16, base_16_digits)
|
||||||
|
} else {
|
||||||
|
parse_digits(string_slice(text, 1, -1), 8, base_8_digits)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parse_digits(text, 10, base_10_digits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_digits(text: string, base: int, digit_set: string) -> int {
|
||||||
|
let val = 0;
|
||||||
|
let len = string_length(text);
|
||||||
|
for (let i = 0; i < len; i += 1) {
|
||||||
|
let ch = text[i];
|
||||||
|
if not string_contains(digit_set, ch) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
val = val * base;
|
||||||
|
val += char_val(ch);
|
||||||
|
}
|
||||||
|
val
|
||||||
|
}
|
||||||
|
|
||||||
|
fn char_val(ch: int) -> int {
|
||||||
|
if ch >= "0"[0] and ch <= "9"[0] {
|
||||||
|
ch - "0"[0]
|
||||||
|
} else if ch >= "a"[0] and ch <= "f"[0] {
|
||||||
|
ch - "a"[0] + 10
|
||||||
|
} else {
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string_slice(str: string, from: int, to: int) -> string {
|
||||||
|
let result = "";
|
||||||
|
let len = string_length(str);
|
||||||
|
let abs_to =
|
||||||
|
if to >= len { len }
|
||||||
|
else if to < 0 { len + to + 1 }
|
||||||
|
else { to };
|
||||||
|
for (let i = from; i < abs_to; i += 1) {
|
||||||
|
result = string_push_char(result, str[i]);
|
||||||
|
}
|
||||||
|
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 print(msg: string) #[builtin(Print)] {}
|
||||||
|
fn println(msg: string) { print(msg + "\n") }
|
||||||
|
|
||||||
|
fn string_push_char(str: string, value: int) -> string #[builtin(StringPushChar)] {}
|
||||||
|
fn string_length(str: string) -> int #[builtin(StringLength)] {}
|
||||||
|
|
||||||
|
fn itos(number: int) -> string #[builtin(IntToString)] {}
|
||||||
|
|
111
examples/example_test_coverage.slg
Normal file
111
examples/example_test_coverage.slg
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
|
||||||
|
fn main() {
|
||||||
|
let ok = test_string_to_int_impl();
|
||||||
|
if not ok {
|
||||||
|
println("tests failed!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_string_to_int_impl() -> bool {
|
||||||
|
test("should convert zero", assert_int_equal(string_to_int_impl("0"), 0))
|
||||||
|
and test("should convert decimal", assert_int_equal(string_to_int_impl("10"), 10))
|
||||||
|
and test("should convert binary", assert_int_equal(string_to_int_impl("0b110"), 6))
|
||||||
|
and test("should convert octal", assert_int_equal(string_to_int_impl("071"), 57))
|
||||||
|
// and test("should convert hex", assert_int_equal(string_to_int_impl("0xaa"), 170))
|
||||||
|
and test("should fail", assert_int_equal(string_to_int_impl("john"), -1))
|
||||||
|
// and test("should fail", assert_int_equal(string_to_int_impl(""), -1))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_int_equal(value: int, target: int) -> bool {
|
||||||
|
if value != target {
|
||||||
|
println("assertion failed: " + itos(value) + " != " + itos(target));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test(name: string, assertion: bool) -> bool {
|
||||||
|
println(" * test: " + name + " -> " + if assertion { "ok" } else { "failed" });
|
||||||
|
assertion
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string_to_int_impl(text: string) -> int {
|
||||||
|
let base_2_digits = "01";
|
||||||
|
let base_8_digits = base_2_digits + "234567";
|
||||||
|
let base_10_digits = base_8_digits + "89";
|
||||||
|
let base_16_digits = base_10_digits + "abcdef";
|
||||||
|
|
||||||
|
let len = string_length(text);
|
||||||
|
if len == 0 {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if text[0] == "0"[0] {
|
||||||
|
if len == 1 {
|
||||||
|
0
|
||||||
|
} else if text[1] == "b"[0] {
|
||||||
|
parse_digits(string_slice(text, 2, -1), 2, base_2_digits)
|
||||||
|
} else if text[1] == "x"[0] {
|
||||||
|
parse_digits(string_slice(text, 2, -1), 16, base_16_digits)
|
||||||
|
} else {
|
||||||
|
parse_digits(string_slice(text, 1, -1), 8, base_8_digits)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parse_digits(text, 10, base_10_digits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_digits(text: string, base: int, digit_set: string) -> int {
|
||||||
|
let val = 0;
|
||||||
|
let len = string_length(text);
|
||||||
|
for (let i = 0; i < len; i += 1) {
|
||||||
|
let ch = text[i];
|
||||||
|
if not string_contains(digit_set, ch) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
val = val * base;
|
||||||
|
val += char_val(ch);
|
||||||
|
}
|
||||||
|
val
|
||||||
|
}
|
||||||
|
|
||||||
|
fn char_val(ch: int) -> int {
|
||||||
|
if ch >= "0"[0] and ch <= "9"[0] {
|
||||||
|
ch - "0"[0]
|
||||||
|
} else if ch >= "a"[0] and ch <= "f"[0] {
|
||||||
|
ch - "a"[0] + 10
|
||||||
|
} else {
|
||||||
|
-1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string_slice(str: string, from: int, to: int) -> string {
|
||||||
|
let result = "";
|
||||||
|
let len = string_length(str);
|
||||||
|
let abs_to =
|
||||||
|
if to >= len { len }
|
||||||
|
else if to < 0 { len + to + 1 }
|
||||||
|
else { to };
|
||||||
|
for (let i = from; i < abs_to; i += 1) {
|
||||||
|
result = string_push_char(result, str[i]);
|
||||||
|
}
|
||||||
|
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 print(msg: string) #[builtin(Print)] {}
|
||||||
|
fn println(msg: string) { print(msg + "\n") }
|
||||||
|
|
||||||
|
fn string_push_char(str: string, value: int) -> string #[builtin(StringPushChar)] {}
|
||||||
|
fn string_length(str: string) -> int #[builtin(StringLength)] {}
|
||||||
|
|
||||||
|
fn itos(number: int) -> string #[builtin(IntToString)] {}
|
||||||
|
|
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 {
|
fn min(a: int, b: int) -> int {
|
||||||
if n == 1 or n == 0{
|
if b < a { b } else { a }
|
||||||
return false;
|
}
|
||||||
|
|
||||||
|
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 {
|
if remainder(n, i) == 0 {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -70,7 +108,7 @@ fn is_prime(n: int) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
for (let i = 1; i < 10000; i += 1) {
|
for (let i = 1; i <= 10000; i += 1) {
|
||||||
if is_prime(i) {
|
if is_prime(i) {
|
||||||
print(int_to_string(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() {
|
fn main() {
|
||||||
let i = 0;
|
let i = 0;
|
||||||
while i < 3 {
|
while i < 3 {
|
||||||
@ -63,18 +10,34 @@ fn main() {
|
|||||||
for char in chars {
|
for char in chars {
|
||||||
println(string_push_char("", char));
|
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] {
|
fn string_to_array(value: string) -> [int] {
|
||||||
let result = int_array_new();
|
let result = array_new::<int>();
|
||||||
let length = string_length(value);
|
let length = string_length(value);
|
||||||
|
|
||||||
for (let i = 0; i < length; i += 1) {
|
for (let i = 0; i < length; i += 1) {
|
||||||
int_array_push(result, value[i]);
|
array_push(result, value[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
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 "alloc.hpp"
|
||||||
#include <format>
|
#include <format>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
using namespace sliger::heap;
|
using namespace sliger::heap;
|
||||||
|
|
||||||
@ -14,3 +15,18 @@ auto Array::at(int32_t index) & -> Value&
|
|||||||
}
|
}
|
||||||
return values.at(static_cast<size_t>(index));
|
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 {
|
struct Struct {
|
||||||
std::unordered_map<std::string, Value> fields;
|
std::unordered_map<std::string, Value> fields;
|
||||||
|
|
||||||
|
auto at(const std::string&) & -> Value&;
|
||||||
|
void assign(const std::string&, Value&& value);
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class AllocType {
|
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_1;
|
||||||
std::vector<AllocItem> heap_2;
|
std::vector<AllocItem> heap_2;
|
||||||
|
@ -41,7 +41,8 @@ enum class Op : uint32_t {
|
|||||||
};
|
};
|
||||||
|
|
||||||
enum class Builtin : uint32_t {
|
enum class Builtin : uint32_t {
|
||||||
IntToString = 0x00,
|
Exit = 0x00,
|
||||||
|
IntToString = 0x01,
|
||||||
StringConcat = 0x10,
|
StringConcat = 0x10,
|
||||||
StringEqual = 0x11,
|
StringEqual = 0x11,
|
||||||
StringCharAt = 0x12,
|
StringCharAt = 0x12,
|
||||||
@ -53,7 +54,9 @@ enum class Builtin : uint32_t {
|
|||||||
ArrayPush = 0x22,
|
ArrayPush = 0x22,
|
||||||
ArrayAt = 0x23,
|
ArrayAt = 0x23,
|
||||||
ArrayLength = 0x24,
|
ArrayLength = 0x24,
|
||||||
StructSet = 0x30,
|
StructNew = 0x30,
|
||||||
|
StructSet = 0x31,
|
||||||
|
StructAt = 0x32,
|
||||||
Print = 0x40,
|
Print = 0x40,
|
||||||
FileOpen = 0x41,
|
FileOpen = 0x41,
|
||||||
FileClose = 0x42,
|
FileClose = 0x42,
|
||||||
|
@ -289,6 +289,11 @@ void VM::run_instruction()
|
|||||||
this->current_pos = { index, line, col };
|
this->current_pos = { index, line, col };
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
std::cerr << std::format("unrecognized instruction '{}', pc = {}",
|
||||||
|
std::to_underlying(op), this->pc);
|
||||||
|
std::exit(1);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
this->instruction_counter += 1;
|
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)));
|
maybe_builtin_to_string(static_cast<uint32_t>(builtin_id)));
|
||||||
}
|
}
|
||||||
switch (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: {
|
case Builtin::IntToString: {
|
||||||
assert_stack_has(1);
|
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);
|
auto str = std::to_string(number);
|
||||||
stack_push(String(str));
|
stack_push(String(str));
|
||||||
break;
|
break;
|
||||||
@ -325,12 +336,11 @@ void VM::run_builtin(Builtin builtin_id)
|
|||||||
run_array_builtin(builtin_id);
|
run_array_builtin(builtin_id);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Builtin::StructSet: {
|
case Builtin::StructNew:
|
||||||
assert_stack_has(2);
|
case Builtin::StructSet:
|
||||||
std::cerr << std::format("not implemented\n");
|
case Builtin::StructAt:
|
||||||
std::exit(1);
|
run_struct_builtin(builtin_id);
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case Builtin::Print:
|
case Builtin::Print:
|
||||||
case Builtin::FileOpen:
|
case Builtin::FileOpen:
|
||||||
@ -401,6 +411,7 @@ void VM::run_string_builtin(Builtin builtin_id)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void VM::run_array_builtin(Builtin builtin_id)
|
void VM::run_array_builtin(Builtin builtin_id)
|
||||||
{
|
{
|
||||||
switch (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)
|
void VM::run_file_builtin(Builtin builtin_id)
|
||||||
{
|
{
|
||||||
switch (builtin_id) {
|
switch (builtin_id) {
|
||||||
|
@ -199,6 +199,7 @@ private:
|
|||||||
void run_builtin(Builtin builtin_id);
|
void run_builtin(Builtin builtin_id);
|
||||||
void run_string_builtin(Builtin builtin_id);
|
void run_string_builtin(Builtin builtin_id);
|
||||||
void run_array_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);
|
void run_file_builtin(Builtin builtin_id);
|
||||||
|
|
||||||
inline void step() { this->pc += 1; }
|
inline void step() { this->pc += 1; }
|
||||||
|
10
slige-run.sh
10
slige-run.sh
@ -3,11 +3,17 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
echo Text:
|
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...
|
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...
|
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