Compare commits

...

27 Commits

Author SHA1 Message Date
5d6b1abefc add borrow checker 2025-01-17 11:50:14 +01:00
a7349890d0 add optimizations 2025-01-17 11:50:14 +01:00
3babacd58d elaborate syms 2025-01-17 11:50:14 +01:00
01c80000ed fix param index increment 2025-01-17 11:50:14 +01:00
f3da09d9c2 add middle 2025-01-17 11:50:14 +01:00
82a2f259e1 desugar param 2025-01-17 11:50:14 +01:00
ac36353609 param ids, indices 2025-01-17 11:50:14 +01:00
26acdc10ca type alias 2025-01-17 11:50:14 +01:00
f56df189c4 struct and array literal syntax 2025-01-17 11:50:14 +01:00
d981e60f8f mod std 2025-01-17 11:50:14 +01:00
9f17396571 pub, mod, anno syntax 2025-01-17 11:50:14 +01:00
e56725dd4f pub stdlib 2025-01-17 11:50:14 +01:00
170e153947 apply new anno in stdlib 2025-01-17 11:50:14 +01:00
f2b1323337 details, not trailing anno 2025-01-17 11:50:14 +01:00
94a57029c0 Revert "this commit kinda sucks"
This reverts commit 7ca1ff1e25.
2025-01-17 11:50:14 +01:00
7ffd2879d1 this commit kinda sucks 2025-01-17 11:50:14 +01:00
5642e3fc5a modules 2025-01-17 11:50:14 +01:00
c65ab5329f karlkode lidt fixes her og der + vim syntax med generics 2025-01-17 11:50:14 +01:00
a4c1b60a61 karlkode generic type inferrence 2025-01-17 11:50:14 +01:00
5150090d2d karlkodet generics 2025-01-17 11:50:14 +01:00
cab2c9baa3 generics work 2025-01-17 11:50:14 +01:00
f712b0f3a5 Revert "do more in the likes of generics"
This reverts commit 0da3d4b7b6.
2025-01-17 11:50:14 +01:00
7b5fee745d do more in the likes of generics 2025-01-17 11:50:14 +01:00
cd923450f5 compiler: more work along the lines of generics 2025-01-17 11:50:14 +01:00
bc82124601 parse generics 2025-01-17 11:50:14 +01:00
7944c76a6a add char literals 2025-01-17 11:50:14 +01:00
c2ae0b2a2e move files to docs/ 2025-01-17 11:50:14 +01:00
55 changed files with 4609 additions and 691 deletions

View File

@ -40,7 +40,8 @@ export const Ops = {
export type Builtins = typeof Builtins;
export const Builtins = {
IntToString: 0x00,
Exit: 0x00,
IntToString: 0x01,
StringConcat: 0x10,
StringEqual: 0x11,
StringCharAt: 0x12,
@ -52,7 +53,9 @@ export const Builtins = {
ArrayPush: 0x22,
ArrayAt: 0x23,
ArrayLength: 0x24,
StructSet: 0x30,
StructNew: 0x30,
StructSet: 0x31,
StructAt: 0x32,
Print: 0x40,
FileOpen: 0x41,
FileClose: 0x42,

View File

@ -1,32 +1,50 @@
import type { Syms } from "./resolver_syms.ts";
import { Pos } from "./token.ts";
import { VType } from "./vtype.ts";
import { GenericArgsMap, VType } from "./vtype.ts";
export type Mod = {
filePath: string;
ast: Stmt[];
};
export type Stmt = {
kind: StmtKind;
pos: Pos;
details?: StmtDetails;
id: number;
};
export type StmtKind =
| { type: "error" }
| { type: "import"; path: Expr }
| { type: "mod_block"; ident: string; stmts: Stmt[] }
| { type: "mod_file"; ident: string; filePath: string }
| { type: "mod"; ident: string; mod: Mod }
| { type: "break"; expr?: Expr }
| { type: "return"; expr?: Expr }
| {
type: "fn";
ident: string;
params: Param[];
returnType?: EType;
body: Expr;
anno?: Anno;
vtype?: VType;
}
| FnStmtKind
| { type: "let"; param: Param; value: Expr }
| { type: "type_alias"; param: Param }
| { type: "assign"; assignType: AssignType; subject: Expr; value: Expr }
| { type: "expr"; expr: Expr };
export type FnStmtKind = {
type: "fn";
ident: string;
genericParams?: GenericParam[];
params: Param[];
returnType?: EType;
body: Expr;
sym?: Sym;
vtype?: VType;
};
export type AssignType = "=" | "+=" | "-=";
export type StmtDetails = {
pub: boolean;
annos: Anno[];
};
export type Expr = {
kind: ExprKind;
pos: Pos;
@ -38,11 +56,28 @@ export type ExprKind =
| { type: "error" }
| { type: "int"; value: number }
| { type: "string"; value: string }
| { type: "ident"; value: string }
| { type: "ident"; ident: string }
| {
type: "sym";
ident: string;
sym: Sym;
}
| { type: "group"; expr: Expr }
| { type: "field"; subject: Expr; value: string }
| { type: "ref"; subject: Expr }
| { type: "ref_mut"; subject: Expr }
| { type: "deref"; subject: Expr }
| { type: "array"; exprs: Expr[] }
| { type: "struct"; fields: Field[] }
| { type: "field"; subject: Expr; ident: string }
| { type: "index"; subject: Expr; value: Expr }
| { type: "call"; subject: Expr; args: Expr[] }
| {
type: "call";
subject: Expr;
args: Expr[];
genericArgs?: GenericArgsMap;
}
| { type: "path"; subject: Expr; ident: string }
| { type: "etype_args"; subject: Expr; etypeArgs: EType[] }
| { type: "unary"; unaryType: UnaryType; subject: Expr }
| { type: "binary"; binaryType: BinaryType; left: Expr; right: Expr }
| { type: "if"; cond: Expr; truthy: Expr; falsy?: Expr; elsePos?: Pos }
@ -50,11 +85,6 @@ export type ExprKind =
| { type: "null" }
| { type: "loop"; body: Expr }
| { type: "block"; stmts: Stmt[]; expr?: Expr }
| {
type: "sym";
ident: string;
sym: Sym;
}
| { type: "while"; cond: Expr; body: Expr }
| { type: "for_in"; param: Param; value: Expr; body: Expr }
| {
@ -80,25 +110,38 @@ export type BinaryType =
| "or"
| "and";
export type Param = {
export type Field = {
ident: string;
expr: Expr;
pos: Pos;
};
export type Param = {
id: number;
index?: number;
ident: string;
mut: boolean;
etype?: EType;
pos: Pos;
sym?: Sym;
vtype?: VType;
};
export type Sym = {
ident: string;
fullPath: string;
pos?: Pos;
} & SymKind;
export type SymKind =
| { type: "let"; stmt: Stmt; param: Param }
| { type: "let_static"; stmt: Stmt; param: Param }
| { type: "type_alias"; stmt: Stmt; param: Param }
| { type: "fn"; stmt: Stmt }
| { type: "fn_param"; param: Param }
| { type: "closure"; inner: Sym }
| { type: "builtin"; builtinId: number };
| { type: "generic"; stmt: Stmt; genericParam: GenericParam }
| { type: "mod"; syms: Syms };
export type EType = {
kind: ETypeKind;
@ -108,11 +151,27 @@ export type EType = {
export type ETypeKind =
| { type: "error" }
| { type: "ident"; value: string }
| { type: "array"; inner: EType }
| { type: "struct"; fields: Param[] };
| { type: "null" }
| { type: "int" }
| { type: "bool" }
| { type: "string" }
| { type: "ident"; ident: string }
| {
type: "sym";
ident: string;
sym: Sym;
}
| { type: "ref"; subject: EType }
| { type: "ref_mut"; subject: EType }
| { type: "ptr"; subject: EType }
| { type: "ptr_mut"; subject: EType }
| { type: "array"; subject: EType }
| { type: "struct"; fields: Param[] }
| { type: "type_of"; expr: Expr };
export type ETypeParam = {
export type GenericParam = {
id: number;
index: number;
ident: string;
pos: Pos;
vtype?: VType;
@ -120,28 +179,63 @@ export type ETypeParam = {
export type Anno = {
ident: string;
values: Expr[];
args: Expr[];
pos: Pos;
};
export class AstCreator {
private nextNodeId = 0;
public stmt(kind: StmtKind, pos: Pos): Stmt {
const id = this.nextNodeId;
this.nextNodeId += 1;
return { kind, pos, id };
public stmt(kind: StmtKind, pos: Pos, details?: StmtDetails): Stmt {
const id = this.genId();
return { kind, pos, details, id };
}
public expr(kind: ExprKind, pos: Pos): Expr {
const id = this.nextNodeId;
this.nextNodeId += 1;
const id = this.genId();
return { kind, pos, id };
}
public etype(kind: ETypeKind, pos: Pos): EType {
const id = this.nextNodeId;
this.nextNodeId += 1;
const id = this.genId();
return { kind, pos, id };
}
public param(val: Omit<Param, "id">): Param {
const id = this.genId();
return { ...val, id };
}
public genericParam(val: Omit<GenericParam, "id">): GenericParam {
const id = this.genId();
return { ...val, id };
}
private genId(): number {
const id = this.nextNodeId;
this.nextNodeId += 1;
return id;
}
}
export class AnnoView {
public constructor(private details?: StmtDetails) {}
public has(...idents: string[]): boolean {
return this.details?.annos.some((anno) =>
idents.some((ident) => anno.ident === ident)
) ?? false;
}
public get(ident: string): Anno {
const anno = this.details?.annos.find((anno) => anno.ident === ident);
if (!anno) {
throw new Error();
}
return anno;
}
}
export function forceType(v: unknown): { type: string } {
return v as { type: string };
}

View File

@ -1,4 +1,4 @@
import { EType, Expr, Param, Stmt } from "./ast.ts";
import { EType, Expr, Field, Param, Stmt } from "./ast.ts";
export type VisitRes = "stop" | void;
@ -6,11 +6,14 @@ export interface AstVisitor<Args extends unknown[] = []> {
visitStmts?(stmts: Stmt[], ...args: Args): VisitRes;
visitStmt?(stmt: Stmt, ...args: Args): VisitRes;
visitErrorStmt?(stmt: Stmt, ...args: Args): VisitRes;
visitImportStmt?(stmt: Stmt, ...args: Args): VisitRes;
visitModFileStmt?(stmt: Stmt, ...args: Args): VisitRes;
visitModBlockStmt?(stmt: Stmt, ...args: Args): VisitRes;
visitModStmt?(stmt: Stmt, ...args: Args): VisitRes;
visitBreakStmt?(stmt: Stmt, ...args: Args): VisitRes;
visitReturnStmt?(stmt: Stmt, ...args: Args): VisitRes;
visitFnStmt?(stmt: Stmt, ...args: Args): VisitRes;
visitLetStmt?(stmt: Stmt, ...args: Args): VisitRes;
visitTypeAliasStmt?(stmt: Stmt, ...args: Args): VisitRes;
visitAssignStmt?(stmt: Stmt, ...args: Args): VisitRes;
visitExprStmt?(stmt: Stmt, ...args: Args): VisitRes;
visitExpr?(expr: Expr, ...args: Args): VisitRes;
@ -19,9 +22,16 @@ export interface AstVisitor<Args extends unknown[] = []> {
visitStringExpr?(expr: Expr, ...args: Args): VisitRes;
visitIdentExpr?(expr: Expr, ...args: Args): VisitRes;
visitGroupExpr?(expr: Expr, ...args: Args): VisitRes;
visitRefExpr?(expr: Expr, ...args: Args): VisitRes;
visitRefMutExpr?(expr: Expr, ...args: Args): VisitRes;
visitDerefExpr?(expr: Expr, ...args: Args): VisitRes;
visitArrayExpr?(expr: Expr, ...args: Args): VisitRes;
visitStructExpr?(expr: Expr, ...args: Args): VisitRes;
visitFieldExpr?(expr: Expr, ...args: Args): VisitRes;
visitIndexExpr?(expr: Expr, ...args: Args): VisitRes;
visitCallExpr?(expr: Expr, ...args: Args): VisitRes;
visitPathExpr?(expr: Expr, ...args: Args): VisitRes;
visitETypeArgsExpr?(expr: Expr, ...args: Args): VisitRes;
visitUnaryExpr?(expr: Expr, ...args: Args): VisitRes;
visitBinaryExpr?(expr: Expr, ...args: Args): VisitRes;
visitIfExpr?(expr: Expr, ...args: Args): VisitRes;
@ -34,11 +44,22 @@ export interface AstVisitor<Args extends unknown[] = []> {
visitBlockExpr?(expr: Expr, ...args: Args): VisitRes;
visitSymExpr?(expr: Expr, ...args: Args): VisitRes;
visitParam?(param: Param, ...args: Args): VisitRes;
visitField?(field: Field, ...args: Args): VisitRes;
visitEType?(etype: EType, ...args: Args): VisitRes;
visitErrorEType?(etype: EType, ...args: Args): VisitRes;
visitNullEType?(etype: EType, ...args: Args): VisitRes;
visitIntEType?(etype: EType, ...args: Args): VisitRes;
visitBoolEType?(etype: EType, ...args: Args): VisitRes;
visitStringEType?(etype: EType, ...args: Args): VisitRes;
visitIdentEType?(etype: EType, ...args: Args): VisitRes;
visitSymEType?(etype: EType, ...args: Args): VisitRes;
visitRefEType?(etype: EType, ...args: Args): VisitRes;
visitRefMutEType?(etype: EType, ...args: Args): VisitRes;
visitPtrEType?(etype: EType, ...args: Args): VisitRes;
visitPtrMutEType?(etype: EType, ...args: Args): VisitRes;
visitArrayEType?(etype: EType, ...args: Args): VisitRes;
visitStructEType?(etype: EType, ...args: Args): VisitRes;
visitTypeOfEType?(etype: EType, ...args: Args): VisitRes;
visitAnno?(etype: EType, ...args: Args): VisitRes;
}
@ -61,9 +82,16 @@ export function visitStmt<Args extends unknown[] = []>(
case "error":
if (v.visitErrorStmt?.(stmt, ...args) == "stop") return;
break;
case "import":
if (v.visitImportStmt?.(stmt, ...args) == "stop") return;
visitExpr(stmt.kind.path, v, ...args);
case "mod_file":
if (v.visitModFileStmt?.(stmt, ...args) == "stop") return;
break;
case "mod_block":
if (v.visitModBlockStmt?.(stmt, ...args) == "stop") return;
visitStmts(stmt.kind.stmts, v, ...args);
break;
case "mod":
if (v.visitModStmt?.(stmt, ...args) == "stop") return;
visitStmts(stmt.kind.mod.ast, v, ...args);
break;
case "break":
if (v.visitBreakStmt?.(stmt, ...args) == "stop") return;
@ -86,6 +114,10 @@ export function visitStmt<Args extends unknown[] = []>(
visitParam(stmt.kind.param, v, ...args);
visitExpr(stmt.kind.value, v, ...args);
break;
case "type_alias":
if (v.visitTypeAliasStmt?.(stmt, ...args) == "stop") return;
visitParam(stmt.kind.param, v, ...args);
break;
case "assign":
if (v.visitAssignStmt?.(stmt, ...args) == "stop") return;
visitExpr(stmt.kind.subject, v, ...args);
@ -95,6 +127,12 @@ export function visitStmt<Args extends unknown[] = []>(
if (v.visitExprStmt?.(stmt, ...args) == "stop") return;
visitExpr(stmt.kind.expr, v, ...args);
break;
default:
throw new Error(
`statement '${
(stmt.kind as { type: string }).type
}' not implemented`,
);
}
}
@ -121,6 +159,18 @@ export function visitExpr<Args extends unknown[] = []>(
if (v.visitGroupExpr?.(expr, ...args) == "stop") return;
visitExpr(expr.kind.expr, v, ...args);
break;
case "ref":
if (v.visitRefExpr?.(expr, ...args) == "stop") return;
visitExpr(expr.kind.subject, v, ...args);
break;
case "ref_mut":
if (v.visitRefMutExpr?.(expr, ...args) == "stop") return;
visitExpr(expr.kind.subject, v, ...args);
break;
case "deref":
if (v.visitDerefExpr?.(expr, ...args) == "stop") return;
visitExpr(expr.kind.subject, v, ...args);
break;
case "field":
if (v.visitFieldExpr?.(expr, ...args) == "stop") return;
visitExpr(expr.kind.subject, v, ...args);
@ -135,6 +185,15 @@ export function visitExpr<Args extends unknown[] = []>(
visitExpr(expr.kind.subject, v, ...args);
expr.kind.args.map((arg) => visitExpr(arg, v, ...args));
break;
case "path":
if (v.visitPathExpr?.(expr, ...args) == "stop") return;
visitExpr(expr.kind.subject, v, ...args);
break;
case "etype_args":
if (v.visitETypeArgsExpr?.(expr, ...args) == "stop") return;
visitExpr(expr.kind.subject, v, ...args);
expr.kind.etypeArgs.map((arg) => visitEType(arg, v, ...args));
break;
case "unary":
if (v.visitUnaryExpr?.(expr, ...args) == "stop") return;
visitExpr(expr.kind.subject, v, ...args);
@ -144,6 +203,14 @@ export function visitExpr<Args extends unknown[] = []>(
visitExpr(expr.kind.left, v, ...args);
visitExpr(expr.kind.right, v, ...args);
break;
case "array":
if (v.visitArrayExpr?.(expr, ...args) == "stop") return;
expr.kind.exprs.map((expr) => visitExpr(expr, v, ...args));
break;
case "struct":
if (v.visitStructExpr?.(expr, ...args) == "stop") return;
expr.kind.fields.map((field) => visitField(field, v, ...args));
break;
case "if":
if (v.visitIfExpr?.(expr, ...args) == "stop") return;
visitExpr(expr.kind.cond, v, ...args);
@ -186,6 +253,12 @@ export function visitExpr<Args extends unknown[] = []>(
case "sym":
if (v.visitSymExpr?.(expr, ...args) == "stop") return;
break;
default:
throw new Error(
`expression '${
(expr.kind as { type: string }).type
}' not implemented`,
);
}
}
@ -198,6 +271,15 @@ export function visitParam<Args extends unknown[] = []>(
if (param.etype) visitEType(param.etype, v, ...args);
}
export function visitField<Args extends unknown[] = []>(
field: Field,
v: AstVisitor<Args>,
...args: Args
) {
if (v.visitField?.(field, ...args) == "stop") return;
visitExpr(field.expr, v, ...args);
}
export function visitEType<Args extends unknown[] = []>(
etype: EType,
v: AstVisitor<Args>,
@ -208,17 +290,58 @@ export function visitEType<Args extends unknown[] = []>(
case "error":
if (v.visitErrorEType?.(etype, ...args) == "stop") return;
break;
case "string":
if (v.visitStringEType?.(etype, ...args) == "stop") return;
break;
case "null":
if (v.visitNullEType?.(etype, ...args) == "stop") return;
break;
case "int":
if (v.visitIntEType?.(etype, ...args) == "stop") return;
break;
case "bool":
if (v.visitBoolEType?.(etype, ...args) == "stop") return;
break;
case "ident":
if (v.visitIdentEType?.(etype, ...args) == "stop") return;
break;
case "sym":
if (v.visitSymEType?.(etype, ...args) == "stop") return;
break;
case "ref":
if (v.visitRefEType?.(etype, ...args) == "stop") return;
visitEType(etype.kind.subject, v, ...args);
break;
case "ref_mut":
if (v.visitRefMutEType?.(etype, ...args) == "stop") return;
visitEType(etype.kind.subject, v, ...args);
break;
case "ptr":
if (v.visitPtrEType?.(etype, ...args) == "stop") return;
visitEType(etype.kind.subject, v, ...args);
break;
case "ptr_mut":
if (v.visitPtrMutEType?.(etype, ...args) == "stop") return;
visitEType(etype.kind.subject, v, ...args);
break;
case "array":
if (v.visitArrayEType?.(etype, ...args) == "stop") return;
if (etype.kind.inner) visitEType(etype.kind.inner, v, ...args);
visitEType(etype.kind.subject, v, ...args);
break;
case "struct":
if (v.visitStructEType?.(etype, ...args) == "stop") return;
etype.kind.fields.map((field) => visitParam(field, v, ...args));
break;
case "type_of":
if (v.visitTypeOfEType?.(etype, ...args) == "stop") return;
visitExpr(etype.kind.expr, v, ...args);
break;
default:
throw new Error(
`etype '${
(etype.kind as { type: string }).type
}' not implemented`,
);
}
}

View File

@ -1,40 +1,75 @@
import { EType, Expr, Stmt } from "./ast.ts";
import { AnnoView, EType, Expr, forceType, Stmt, Sym } from "./ast.ts";
import { printStackTrace, Reporter } from "./info.ts";
import { Pos } from "./token.ts";
import { VType, VTypeParam, vtypesEqual, vtypeToString } from "./vtype.ts";
import {
extractGenericType,
GenericArgsMap,
VType,
VTypeGenericParam,
VTypeParam,
vtypesEqual,
vtypeToString,
} from "./vtype.ts";
export class Checker {
private fnReturnStack: VType[] = [];
private loopBreakStack: VType[][] = [];
private globalIdToGenericParamMap = new Map<number, VTypeGenericParam>();
public constructor(private reporter: Reporter) {}
public check(stmts: Stmt[]) {
this.checkFnHeaders(stmts);
this.scout(stmts);
for (const stmt of stmts) {
this.checkStmt(stmt);
}
}
private checkFnHeaders(stmts: Stmt[]) {
private scout(stmts: Stmt[]) {
for (const stmt of stmts) {
if (stmt.kind.type !== "fn") {
continue;
}
const returnType: VType = stmt.kind.returnType
? this.checkEType(stmt.kind.returnType)
: { type: "null" };
const params: VTypeParam[] = [];
for (const param of stmt.kind.params) {
if (param.etype === undefined) {
this.report("parameter types must be defined", param.pos);
stmt.kind.vtype = { type: "error" };
if (stmt.kind.type === "fn") {
let genericParams: VTypeGenericParam[] | undefined;
if (stmt.kind.genericParams !== undefined) {
genericParams = [];
for (const etypeParam of stmt.kind.genericParams) {
const id = genericParams.length;
const globalId = etypeParam.id;
const param = { id, ident: etypeParam.ident };
genericParams.push(param);
this.globalIdToGenericParamMap.set(globalId, param);
}
}
const vtype = this.checkEType(param.etype!);
param.vtype = vtype;
params.push({ ident: param.ident, vtype });
const params: VTypeParam[] = [];
for (const param of stmt.kind.params) {
if (param.etype === undefined) {
this.report(
"parameter types must be defined",
param.pos,
);
stmt.kind.vtype = { type: "error" };
}
const vtype = this.checkEType(param.etype!);
param.vtype = vtype;
params.push({ ident: param.ident, mut: true, vtype });
}
const returnType: VType = stmt.kind.returnType
? this.checkEType(stmt.kind.returnType)
: { type: "null" };
stmt.kind.vtype = {
type: "fn",
genericParams,
params,
returnType,
stmtId: stmt.id,
};
} else if (stmt.kind.type === "type_alias") {
if (!stmt.kind.param.etype) {
this.report("no type specified", stmt.pos);
return;
}
stmt.kind.param.vtype = this.checkEType(stmt.kind.param.etype);
}
stmt.kind.vtype = { type: "fn", params, returnType };
}
}
@ -42,6 +77,11 @@ export class Checker {
switch (stmt.kind.type) {
case "error":
return { type: "error" };
case "mod_block":
case "mod_file":
throw new Error("mod declaration in ast, should be resolved");
case "mod":
return this.checkModStmt(stmt);
case "break":
return this.checkBreakStmt(stmt);
case "return":
@ -50,6 +90,8 @@ export class Checker {
return this.checkFnStmt(stmt);
case "let":
return this.checkLetStmt(stmt);
case "type_alias":
return this.checkTypeAliasStmt(stmt);
case "assign":
return this.checkAssignStmt(stmt);
case "expr":
@ -57,6 +99,17 @@ export class Checker {
}
}
public checkModStmt(stmt: Stmt) {
if (stmt.kind.type !== "mod") {
throw new Error();
}
const { ast } = stmt.kind.mod;
this.scout(ast);
for (const stmt of ast) {
this.checkStmt(stmt);
}
}
public checkBreakStmt(stmt: Stmt) {
if (stmt.kind.type !== "break") {
throw new Error();
@ -103,8 +156,8 @@ export class Checker {
if (!vtypesEqual(exprType, returnType)) {
this.report(
`incompatible return type` +
`, got ${exprType}` +
`, expected ${returnType}`,
`, expected ${vtypeToString(returnType)}` +
`, got ${vtypeToString(exprType)}`,
pos,
);
}
@ -119,16 +172,17 @@ export class Checker {
throw new Error();
}
if (
stmt.kind.anno?.ident === "remainder" ||
stmt.kind.anno?.ident === "builtin"
) {
const annos = new AnnoView(stmt.details);
if (annos.has("builtin", "remainder")) {
// NOTE: handled in lowerer
return;
}
const { returnType } = stmt.kind.vtype!;
if (returnType.type === "error") return returnType;
this.fnReturnStack.push(returnType);
const body = this.checkExpr(stmt.kind.body);
if (body.type === "error") return body;
this.fnReturnStack.pop();
if (!vtypesEqual(returnType, body)) {
@ -147,13 +201,17 @@ export class Checker {
}
const pos = stmt.pos;
const value = this.checkExpr(stmt.kind.value);
if (value.type === "error") {
return stmt.kind.param.vtype = value;
}
if (stmt.kind.param.etype) {
const paramVtype = this.checkEType(stmt.kind.param.etype);
if (!vtypesEqual(value, paramVtype)) {
const paramVType = this.checkEType(stmt.kind.param.etype);
if (paramVType.type === "error") return paramVType;
if (!vtypesEqual(value, paramVType)) {
this.report(
`incompatible value type` +
`, got '${vtypeToString(value)}'` +
`, expected '${vtypeToString(paramVtype)}'`,
`, expected '${vtypeToString(paramVType)}'`,
pos,
);
return;
@ -162,6 +220,18 @@ export class Checker {
stmt.kind.param.vtype = value;
}
public checkTypeAliasStmt(stmt: Stmt) {
if (stmt.kind.type !== "type_alias") {
throw new Error();
}
const pos = stmt.pos;
if (!stmt.kind.param.etype) {
this.report("no type specified", pos);
return;
}
stmt.kind.param.vtype = this.checkEType(stmt.kind.param.etype);
}
public checkAssignStmt(stmt: Stmt) {
if (stmt.kind.type !== "assign") {
throw new Error();
@ -178,13 +248,13 @@ export class Checker {
this.report("cannot use field on non-struct", pos);
return { type: "error" };
}
const fieldValue = stmt.kind.subject.kind.value;
const fieldValue = stmt.kind.subject.kind.ident;
const found = subject.fields.find((param) =>
param.ident === fieldValue
);
if (!found) {
this.report(
`no field named '${stmt.kind.subject.kind.value}' on struct`,
`no field named '${stmt.kind.subject.kind.ident}' on struct`,
pos,
);
return { type: "error" };
@ -216,7 +286,7 @@ export class Checker {
}
if (
subject.type == "array" &&
!vtypesEqual(subject.inner, value)
!vtypesEqual(subject.subject, value)
) {
this.report(
`cannot assign incompatible type to array ` +
@ -262,6 +332,9 @@ export class Checker {
case "error":
throw new Error("error in AST");
case "ident":
if (this.reporter.errorOccured()) {
return { type: "error" };
}
throw new Error("ident expr in AST");
case "sym":
return this.checkSymExpr(expr);
@ -275,12 +348,26 @@ export class Checker {
return { type: "string" };
case "group":
return this.checkExpr(expr.kind.expr);
case "ref":
return this.checkRefExpr(expr);
case "ref_mut":
return this.checkRefMutExpr(expr);
case "deref":
return this.checkDerefExpr(expr);
case "array":
throw new Error("should have been desugared");
case "struct":
return this.checkStructExpr(expr);
case "field":
return this.checkFieldExpr(expr);
case "index":
return this.checkIndexExpr(expr);
case "call":
return this.checkCallExpr(expr);
case "path":
return this.checkPathExpr(expr);
case "etype_args":
return this.checkETypeArgsExpr(expr);
case "unary":
return this.checkUnaryExpr(expr);
case "binary":
@ -307,11 +394,17 @@ export class Checker {
if (expr.kind.type !== "sym") {
throw new Error();
}
switch (expr.kind.sym.type) {
return this.checkSym(expr.kind.sym);
}
private checkSym(sym: Sym): VType {
switch (sym.type) {
case "let":
return expr.kind.sym.param.vtype!;
return sym.param.vtype!;
case "type_alias":
return sym.param.vtype!;
case "fn": {
const fnStmt = expr.kind.sym.stmt!;
const fnStmt = sym.stmt!;
if (fnStmt.kind.type !== "fn") {
throw new Error();
}
@ -319,20 +412,117 @@ export class Checker {
if (vtype.type !== "fn") {
throw new Error();
}
const { params, returnType } = vtype;
return { type: "fn", params, returnType };
return vtype;
}
case "fn_param":
return expr.kind.sym.param.vtype!;
case "builtin":
return sym.param.vtype!;
case "let_static":
case "closure":
case "generic":
throw new Error(
`not implemented, sym type '${expr.kind.sym.type}'`,
`not implemented, sym type '${sym.type}'`,
);
case "mod":
throw new Error("should already be resolved");
}
}
public checkRefExpr(expr: Expr): VType {
if (expr.kind.type !== "ref") {
throw new Error();
}
const subject = this.checkExpr(expr.kind.subject);
if (expr.kind.subject.kind.type === "sym") {
const sym = expr.kind.subject.kind.sym;
if (sym.type === "let" || sym.type === "fn_param") {
return { type: "ref", subject };
}
this.report(
`taking reference to symbol type '${sym.type}' not supported`,
expr.pos,
);
return { type: "error" };
}
this.report(
`taking reference to expression type '${
forceType(expr.kind.subject.kind).type
}' not supported`,
expr.pos,
);
return { type: "error" };
}
public checkRefMutExpr(expr: Expr): VType {
if (expr.kind.type !== "ref_mut") {
throw new Error();
}
const subject = this.checkExpr(expr.kind.subject);
if (expr.kind.subject.kind.type === "sym") {
const sym = expr.kind.subject.kind.sym;
if (sym.type === "let" || sym.type === "fn_param") {
if (!sym.param.mut) {
this.report(
`symbol '${sym.ident}' it not declared mutable`,
expr.pos,
);
this.reporter.addNote({
reporter: "checker",
msg: "symbol defined here",
pos: sym.param.pos,
});
return { type: "error" };
}
return { type: "ref_mut", subject };
}
this.report(
`taking reference to symbol type '${sym.type}' not supported`,
expr.pos,
);
return { type: "error" };
}
this.report(
`taking mutable reference to expression type '${
forceType(expr.kind.subject.kind).type
}' not supported`,
expr.pos,
);
return { type: "error" };
}
public checkDerefExpr(expr: Expr): VType {
if (expr.kind.type !== "deref") {
throw new Error();
}
const subject = this.checkExpr(expr.kind.subject);
switch (subject.type) {
case "ref":
return subject.subject;
case "ref_mut":
return subject.subject;
case "ptr":
return subject.subject;
case "ptr_mut":
return subject.subject;
}
this.report(
`dereferenced type is neither a reference nor a pointer`,
expr.pos,
);
return { type: "error" };
}
public checkStructExpr(expr: Expr): VType {
if (expr.kind.type !== "struct") {
throw new Error();
}
const fields: VTypeParam[] = expr.kind.fields
.map(({ ident, expr }): VTypeParam => ({
ident,
mut: true,
vtype: this.checkExpr(expr),
}));
return { type: "struct", fields };
}
public checkFieldExpr(expr: Expr): VType {
if (expr.kind.type !== "field") {
throw new Error();
@ -343,11 +533,11 @@ export class Checker {
this.report("cannot use field on non-struct", pos);
return { type: "error" };
}
const value = expr.kind.value;
const value = expr.kind.ident;
const found = subject.fields.find((param) => param.ident === value);
if (!found) {
this.report(
`no field named '${expr.kind.value}' on struct`,
`no field named '${expr.kind.ident}' on struct`,
pos,
);
return { type: "error" };
@ -371,7 +561,7 @@ export class Checker {
return { type: "error" };
}
if (subject.type === "array") {
return subject.inner;
return subject.subject;
}
return { type: "int" };
}
@ -382,18 +572,62 @@ export class Checker {
}
const pos = expr.pos;
const subject = this.checkExpr(expr.kind.subject);
if (subject.type !== "fn") {
this.report("cannot call non-fn", pos);
return { type: "error" };
}
const args = expr.kind.args.map((arg) => this.checkExpr(arg));
if (args.length !== subject.params.length) {
this.report(
`incorrect number of arguments` +
`, expected ${subject.params.length}`,
if (subject.type === "error") return subject;
if (subject.type === "fn") {
if (expr.kind.args.length !== subject.params.length) {
this.report(
`expected ${subject.params.length} arguments` +
`, got ${expr.kind.args.length}`,
pos,
);
}
const args = expr.kind.args.map((arg) => this.checkExpr(arg));
if (args.some((arg) => arg.type === "error")) {
return { type: "error" };
}
if (subject.genericParams === undefined) {
return this.checkCallExprNoGenericsTail(
expr,
subject,
args,
pos,
);
}
return this.checkCallExprInferredGenericsTail(
expr,
subject,
args,
pos,
);
}
if (subject.type === "generic_spec" && subject.subject.type === "fn") {
return this.checkCallExprExplicitGenericsTail(expr, subject);
}
this.report("cannot call non-fn", pos);
return { type: "error" };
}
private checkCallExprNoGenericsTail(
expr: Expr,
subject: VType,
args: VType[],
pos: Pos,
): VType {
if (
expr.kind.type !== "call" || subject.type !== "fn"
) {
throw new Error();
}
for (let i = 0; i < args.length; ++i) {
if (this.vtypeContainsGeneric(args[i])) {
this.report(
`amfibious generic parameter for argument ${i}, please specify generic types explicitly`,
pos,
);
return { type: "error" };
}
}
for (let i = 0; i < args.length; ++i) {
if (!vtypesEqual(args[i], subject.params[i].vtype)) {
this.report(
@ -405,15 +639,283 @@ export class Checker {
break;
}
}
return subject.returnType;
}
private checkCallExprInferredGenericsTail(
expr: Expr,
subject: VType,
args: VType[],
pos: Pos,
): VType {
if (
expr.kind.type !== "call" || subject.type !== "fn" ||
subject.genericParams === undefined
) {
throw new Error();
}
const genericArgsRes = this.inferGenericArgs(
subject.genericParams,
subject.params,
args,
pos,
);
if (!genericArgsRes.ok) {
return { type: "error" };
}
const genericArgs = genericArgsRes.value;
for (let i = 0; i < args.length; ++i) {
const vtypeCompatible = vtypesEqual(
args[i],
subject.params[i].vtype,
genericArgs,
);
if (!vtypeCompatible) {
this.report(
`incorrect argument ${i} '${subject.params[i].ident}'` +
`, expected ${
vtypeToString(
extractGenericType(
subject.params[i].vtype,
genericArgs,
),
)
}` +
`, got ${vtypeToString(args[i])}`,
pos,
);
break;
}
}
expr.kind.genericArgs = genericArgs;
return this.concretizeVType(subject.returnType, genericArgs);
}
private inferGenericArgs(
genericParams: VTypeGenericParam[],
params: VTypeParam[],
args: VType[],
pos: Pos,
): { ok: true; value: GenericArgsMap } | { ok: false } {
const genericArgs: GenericArgsMap = {};
for (let i = 0; i < params.length; ++i) {
if (!this.vtypeContainsGeneric(params[i].vtype)) {
continue;
}
const {
a: generic,
b: concrete,
} = this.reduceToSignificant(params[i].vtype, args[i]);
if (generic.type !== "generic") {
throw new Error();
}
const paramId = generic.param.id;
if (
paramId in genericArgs &&
!vtypesEqual(genericArgs[paramId], concrete)
) {
this.report(
`according to inferrence, argument ${i} has a conflicting type`,
pos,
);
return { ok: false };
}
genericArgs[paramId] = concrete;
}
for (const param of genericParams) {
if (!(param.id in genericArgs)) {
this.report(`could not infer generic type ${param.ident}`, pos);
return { ok: false };
}
}
return { ok: true, value: genericArgs };
}
private reduceToSignificant(a: VType, b: VType): { a: VType; b: VType } {
if (a.type !== b.type) {
return { a, b };
}
if (a.type === "array" && b.type === "array") {
return this.reduceToSignificant(a.subject, b.subject);
}
if (a.type === "generic" && b.type === "generic") {
return { a, b };
}
throw new Error("idk what to do here");
}
private vtypeContainsGeneric(vtype: VType): boolean {
switch (vtype.type) {
case "error":
case "string":
case "unknown":
case "null":
case "int":
case "bool":
return false;
case "ref":
case "ref_mut":
case "ptr":
case "ptr_mut":
return this.vtypeContainsGeneric(vtype.subject);
case "array":
return this.vtypeContainsGeneric(vtype.subject);
case "struct":
return vtype.fields.some((field) =>
this.vtypeContainsGeneric(field.vtype)
);
case "fn":
throw new Error("not implemented");
case "generic":
return true;
case "generic_spec":
throw new Error("listen kid, grrrrrrrr");
}
}
private checkCallExprExplicitGenericsTail(
expr: Expr,
subject: VType,
): VType {
if (
expr.kind.type !== "call" || subject.type !== "generic_spec" ||
subject.subject.type !== "fn"
) {
throw new Error();
}
const pos = expr.pos;
const inner = subject.subject;
const params = inner.params;
const args = expr.kind.args.map((arg) => this.checkExpr(arg));
if (args.length !== params.length) {
this.report(
`expected ${params.length} arguments` +
`, got ${args.length}`,
pos,
);
}
for (let i = 0; i < args.length; ++i) {
const vtypeCompatible = vtypesEqual(
args[i],
params[i].vtype,
subject.genericArgs,
);
if (!vtypeCompatible) {
this.report(
`incorrect argument ${i} '${inner.params[i].ident}'` +
`, expected ${
vtypeToString(
extractGenericType(
params[i].vtype,
subject.genericArgs,
),
)
}` +
`, got ${vtypeToString(args[i])}`,
pos,
);
break;
}
}
expr.kind.genericArgs = subject.genericArgs;
return this.concretizeVType(
subject.subject.returnType,
subject.genericArgs,
);
}
private concretizeVType(
vtype: VType,
generics: GenericArgsMap,
): VType {
switch (vtype.type) {
case "error":
case "unknown":
case "string":
case "null":
case "int":
case "bool":
return vtype;
case "ref":
case "ref_mut":
case "ptr":
case "ptr_mut":
case "array":
return {
type: vtype.type,
subject: this.concretizeVType(vtype.subject, generics),
};
case "struct":
return {
type: "struct",
fields: vtype.fields.map((field) => ({
...field,
vtype: this.concretizeVType(field.vtype, generics),
})),
};
case "fn":
throw new Error("not implemented");
case "generic":
return generics[vtype.param.id];
case "generic_spec":
throw new Error("not implemented");
}
}
public checkPathExpr(expr: Expr): VType {
if (expr.kind.type !== "path") {
throw new Error();
}
throw new Error("should already be resolved");
}
public checkETypeArgsExpr(expr: Expr): VType {
if (expr.kind.type !== "etype_args") {
throw new Error();
}
const pos = expr.pos;
const subject = this.checkExpr(expr.kind.subject);
if (subject.type !== "fn" || subject.genericParams === undefined) {
this.report(
"etype arguments must only be applied to generic functions",
expr.pos,
);
return { type: "error" };
}
const args = expr.kind.etypeArgs;
if (args.length !== subject.genericParams.length) {
this.report(
`incorrect number of arguments` +
`, expected ${subject.params.length}`,
pos,
);
}
const genericArgs: GenericArgsMap = {};
for (let i = 0; i < args.length; ++i) {
const etype = this.checkEType(args[i]);
genericArgs[subject.genericParams[i].id] = etype;
}
return {
type: "generic_spec",
subject,
genericArgs,
};
}
public checkUnaryExpr(expr: Expr): VType {
if (expr.kind.type !== "unary") {
throw new Error();
}
const pos = expr.pos;
const subject = this.checkExpr(expr.kind.subject);
if (subject.type === "error") return subject;
for (const operation of simpleUnaryOperations) {
if (operation.unaryType !== expr.kind.unaryType) {
continue;
@ -437,7 +939,9 @@ export class Checker {
}
const pos = expr.pos;
const left = this.checkExpr(expr.kind.left);
if (left.type === "error") return left;
const right = this.checkExpr(expr.kind.right);
if (right.type === "error") return right;
for (const operation of simpleBinaryOperations) {
if (operation.binaryType !== expr.kind.binaryType) {
continue;
@ -466,10 +970,13 @@ export class Checker {
}
const pos = expr.pos;
const cond = this.checkExpr(expr.kind.cond);
if (cond.type === "error") return cond;
const truthy = this.checkExpr(expr.kind.truthy);
if (truthy.type === "error") return truthy;
const falsy = expr.kind.falsy
? this.checkExpr(expr.kind.falsy)
: undefined;
if (falsy?.type === "error") return falsy;
if (cond.type !== "bool") {
this.report(
`if condition should be 'bool', got '${vtypeToString(cond)}'`,
@ -545,7 +1052,7 @@ export class Checker {
if (expr.kind.type !== "block") {
throw new Error();
}
this.checkFnHeaders(expr.kind.stmts);
this.scout(expr.kind.stmts);
for (const stmt of expr.kind.stmts) {
this.checkStmt(stmt);
}
@ -556,25 +1063,54 @@ export class Checker {
public checkEType(etype: EType): VType {
const pos = etype.pos;
if (etype.kind.type === "ident") {
if (etype.kind.value === "null") {
switch (etype.kind.type) {
case "null":
return { type: "null" };
}
if (etype.kind.value === "int") {
case "int":
return { type: "int" };
}
if (etype.kind.value === "bool") {
case "bool":
return { type: "bool" };
}
if (etype.kind.value === "string") {
case "string":
return { type: "string" };
}
this.report(`undefined type '${etype.kind.value}'`, pos);
}
if (etype.kind.type === "ident") {
this.report(`undefined type '${etype.kind.ident}'`, pos);
return { type: "error" };
}
if (etype.kind.type === "sym") {
if (etype.kind.sym.type === "type_alias") {
return etype.kind.sym.param.vtype!;
}
if (etype.kind.sym.type === "generic") {
const { id: globalId, ident } = etype.kind.sym.genericParam;
if (!this.globalIdToGenericParamMap.has(globalId)) {
throw new Error();
}
const { id } = this.globalIdToGenericParamMap.get(globalId)!;
return { type: "generic", param: { id, ident } };
}
this.report(`sym type '${etype.kind.sym.type}' used as type`, pos);
return { type: "error" };
}
if (etype.kind.type === "ref") {
const subject = this.checkEType(etype.kind.subject);
return { type: "ref", subject };
}
if (etype.kind.type === "ref_mut") {
const subject = this.checkEType(etype.kind.subject);
return { type: "ref", subject };
}
if (etype.kind.type === "ptr") {
const subject = this.checkEType(etype.kind.subject);
return { type: "ptr", subject };
}
if (etype.kind.type === "ptr_mut") {
const subject = this.checkEType(etype.kind.subject);
return { type: "ptr_mut", subject };
}
if (etype.kind.type === "array") {
const inner = this.checkEType(etype.kind.inner);
return { type: "array", inner };
const subject = this.checkEType(etype.kind.subject);
return { type: "array", subject };
}
if (etype.kind.type === "struct") {
const noTypeTest = etype.kind.fields.reduce(
@ -608,13 +1144,19 @@ export class Checker {
this.report(`field ${declaredTwiceTest[2]} defined twice`, pos);
return { type: "error" };
}
const fields = etype.kind.fields.map((param): VTypeParam => ({
ident: param.ident,
vtype: this.checkEType(param.etype!),
}));
const fields = etype.kind.fields
.map((param): VTypeParam => ({
ident: param.ident,
mut: true,
vtype: this.checkEType(param.etype!),
}));
return { type: "struct", fields };
}
throw new Error(`unknown explicit type ${etype.kind.type}`);
if (etype.kind.type === "type_of") {
const exprVType = this.checkExpr(etype.kind.expr);
return exprVType;
}
throw new Error(`unknown explicit type '${etype.kind.type}'`);
}
private report(msg: string, pos: Pos) {

View File

@ -1,17 +1,28 @@
import { AstCreator } from "./ast.ts";
import { AstCreator, Mod, Stmt } from "./ast.ts";
import { Checker } from "./checker.ts";
import { CompoundAssignDesugarer } from "./desugar/compound_assign.ts";
import { StructLiteralDesugarer } from "./desugar/struct_literal.ts";
import { SpecialLoopDesugarer } from "./desugar/special_loop.ts";
import { Reporter } from "./info.ts";
import { Lexer } from "./lexer.ts";
import { Monomorphizer } from "./mono.ts";
import { FnNamesMap, Lowerer } from "./lowerer.ts";
import { Parser } from "./parser.ts";
import { Resolver } from "./resolver.ts";
import { AstVisitor, VisitRes, visitStmts } from "./ast_visitor.ts";
import { Pos } from "./token.ts";
import { ArrayLiteralDesugarer } from "./desugar/array_literal.ts";
import { mirOpCount, printMir } from "./middle/mir.ts";
export type CompiledFile = {
filepath: string;
program: number[];
};
import * as path from "jsr:@std/path";
import { lowerAst } from "./middle/lower_ast.ts";
import { eliminateUnusedLocals } from "./middle/elim_unused_local.ts";
import {
eliminateOnlyChildsBlocks,
eliminateUnreachableBlocks,
} from "./middle/elim_blocks.ts";
import { checkBorrows } from "./middle/borrow_checker.ts";
import { makeMoveCopyExplicit } from "./middle/explicit_move_copy.ts";
export type CompileResult = {
program: number[];
@ -20,19 +31,22 @@ export type CompileResult = {
export class Compiler {
private astCreator = new AstCreator();
private reporter = new Reporter();
private reporter;
public constructor(private startFilePath: string) {}
public constructor(private startFilePath: string) {
this.reporter = new Reporter(this.startFilePath);
}
public async compile(): Promise<CompileResult> {
const text = await Deno.readTextFile(this.startFilePath);
const lexer = new Lexer(text, this.reporter);
const parser = new Parser(lexer, this.astCreator, this.reporter);
const ast = parser.parse();
const { ast } = new ModTree(
this.startFilePath,
this.astCreator,
this.reporter,
).resolve();
new SpecialLoopDesugarer(this.astCreator).desugar(ast);
new ArrayLiteralDesugarer(this.astCreator).desugar(ast);
new StructLiteralDesugarer(this.astCreator).desugar(ast);
new Resolver(this.reporter).resolve(ast);
@ -40,16 +54,130 @@ export class Compiler {
new Checker(this.reporter).check(ast);
//const mir = lowerAst(ast);
//
//console.log("Before optimizations:");
//printMir(mir);
//const mirHistory = [mirOpCount(mir)];
//for (let i = 0; i < 1; ++i) {
// eliminateUnusedLocals(mir, this.reporter, mirHistory.length === 1);
// eliminateOnlyChildsBlocks(mir);
// eliminateUnreachableBlocks(mir);
// eliminateTransientVals(mir);
//
// const opCount = mirOpCount(mir);
// const histOccurence = mirHistory
// .filter((v) => v === opCount).length;
// if (histOccurence >= 2) {
// break;
// }
// mirHistory.push(opCount);
//}
//
//console.log("After optimizations:");
//printMir(mir);
if (this.reporter.errorOccured()) {
console.error("Errors occurred, stopping compilation.");
Deno.exit(1);
}
const lowerer = new Lowerer(lexer.currentPos());
lowerer.lower(ast);
// lowerer.printProgram();
const { program, fnNames } = lowerer.finish();
const mir = lowerAst(ast);
return { program, fnNames };
makeMoveCopyExplicit(mir);
checkBorrows(mir, this.reporter);
printMir(mir);
//const { monoFns, callMap } = new Monomorphizer(ast).monomorphize();
//
//const lastPos = await lastPosInTextFile(this.startFilePath);
//
//const lowerer = new Lowerer(monoFns, callMap, lastPos);
//const { program, fnNames } = lowerer.lower();
////lowerer.printProgram();
//
//return { program, fnNames };
return { program: [], fnNames: {} };
}
}
export class ModTree implements AstVisitor<[string]> {
constructor(
private entryFilePath: string,
private astCreator: AstCreator,
private reporter: Reporter,
) {}
public resolve(): Mod {
const entryAst = this.parseFile(this.entryFilePath);
visitStmts(entryAst, this, this.entryFilePath);
return { filePath: this.entryFilePath, ast: entryAst };
}
private parseFile(filePath: string): Stmt[] {
const text = Deno.readTextFileSync(filePath);
const lexer = new Lexer(text, this.reporter);
const parser = new Parser(lexer, this.astCreator, this.reporter);
const ast = parser.parse();
return ast;
}
visitModBlockStmt(stmt: Stmt, filePath: string): VisitRes {
if (stmt.kind.type !== "mod_block") {
throw new Error();
}
const { ident, stmts: ast } = stmt.kind;
stmt.kind = {
type: "mod",
ident,
mod: { filePath, ast },
};
visitStmts(ast, this, filePath);
return "stop";
}
visitModFileStmt(stmt: Stmt, filePath: string): VisitRes {
if (stmt.kind.type !== "mod_file") {
throw new Error();
}
const { ident, filePath: modFilePath } = stmt.kind;
const ast = this.parseFile(
ident === "std"
? path.join(
path.dirname(path.fromFileUrl(Deno.mainModule)),
"../std/lib.slg",
)
: path.join(path.dirname(filePath), modFilePath),
);
stmt.kind = { type: "mod", ident, mod: { filePath, ast } };
visitStmts(ast, this, filePath);
return "stop";
}
}
async function lastPosInTextFile(filePath: string): Promise<Pos> {
const text = await Deno.readTextFile(filePath);
let index = 0;
let line = 1;
let col = 1;
while (index < text.length) {
if (text[index] == "\n") {
line += 1;
col = 1;
} else {
col += 1;
}
index += 1;
}
return { index, line, col };
}

View File

@ -1,8 +1,14 @@
{
"version": "4",
"specifiers": {
"jsr:@std/path@*": "1.0.8",
"npm:@types/node@*": "22.5.4"
},
"jsr": {
"@std/path@1.0.8": {
"integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be"
}
},
"npm": {
"@types/node@22.5.4": {
"integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",

View 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";
}
}

View File

@ -70,29 +70,45 @@ export class SpecialLoopDesugarer implements AstVisitor {
stmts: [
Stmt({
type: "let",
param: { ident: "::values", pos: npos },
param: this.astCreator.param({
ident: "::values",
mut: true,
pos: npos,
}),
value: expr.kind.value,
}),
Stmt({
type: "let",
param: { ident: "::length", pos: npos },
param: this.astCreator.param({
ident: "::length",
mut: false,
pos: npos,
}),
value: Expr({
type: "call",
subject: Expr({
type: "ident",
value: "int_array_length",
type: "path",
subject: Expr({
type: "ident",
ident: "std",
}),
ident: "array_length",
}),
args: [
Expr({
type: "ident",
value: "::values",
ident: "::values",
}),
],
}),
}),
Stmt({
type: "let",
param: { ident: "::index", pos: npos },
param: this.astCreator.param({
ident: "::index",
mut: true,
pos: npos,
}),
value: Expr({ type: "int", value: 0 }),
}, expr.pos),
Stmt({
@ -114,11 +130,11 @@ export class SpecialLoopDesugarer implements AstVisitor {
binaryType: "<",
left: Expr({
type: "ident",
value: "::index",
ident: "::index",
}),
right: Expr({
type: "ident",
value: "::length",
ident: "::length",
}),
}),
}),
@ -139,11 +155,11 @@ export class SpecialLoopDesugarer implements AstVisitor {
type: "index",
subject: Expr({
type: "ident",
value: "::values",
ident: "::values",
}),
value: Expr({
type: "ident",
value: "::index",
ident: "::index",
}),
}),
}, expr.pos),
@ -156,7 +172,7 @@ export class SpecialLoopDesugarer implements AstVisitor {
assignType: "+=",
subject: Expr({
type: "ident",
value: "::index",
ident: "::index",
}),
value: Expr({
type: "int",

View 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";
}
}

View File

@ -1,7 +1,7 @@
import { Pos } from "./token.ts";
export type Report = {
type: "error" | "note";
type: "error" | "warning" | "note";
reporter: string;
pos?: Pos;
msg: string;
@ -11,16 +11,27 @@ export class Reporter {
private reports: Report[] = [];
private errorSet = false;
public constructor(private filePath: string) {}
public setFilePath(filePath: string) {
this.filePath = filePath;
}
public reportError(report: Omit<Report, "type">) {
this.reports.push({ ...report, type: "error" });
this.printReport({ ...report, type: "error" });
this.errorSet = true;
}
public reportWarning(report: Omit<Report, "type">) {
this.reports.push({ ...report, type: "warning" });
this.printReport({ ...report, type: "warning" });
}
private printReport({ reporter, type, pos, msg }: Report) {
console.error(
`${reporter} ${type}: ${msg}${
pos ? ` at ${pos.line}:${pos.col}` : ""
pos ? `\n at ${this.filePath}:${pos.line}:${pos.col}` : ""
}`,
);
}

View File

@ -27,24 +27,31 @@ export class Lexer {
this.step();
}
const keywords = [
"false",
"true",
"null",
"int",
"bool",
"string",
"break",
"return",
"let",
"mut",
"fn",
"loop",
"if",
"else",
"struct",
"import",
"false",
"true",
"null",
"or",
"and",
"not",
"while",
"for",
"in",
"mod",
"pub",
"use",
"type_alias",
];
if (keywords.includes(value)) {
return this.token(value, pos);
@ -70,6 +77,32 @@ export class Lexer {
return { ...this.token("int", pos), intValue: 0 };
}
if (this.test("'")) {
this.step();
let value: string;
if (this.test("\\")) {
this.step();
if (this.done()) {
this.report("malformed character literal", pos);
return this.token("error", pos);
}
value = {
n: "\n",
t: "\t",
"0": "\0",
}[this.current()] ?? this.current();
} else {
value = this.current();
}
this.step();
if (this.done() || !this.test("'") || value.length === 0) {
this.report("malformed character literal", pos);
return this.token("error", pos);
}
this.step();
return { ...this.token("int", pos), intValue: value.charCodeAt(0) };
}
if (this.test('"')) {
this.step();
let value = "";
@ -96,7 +129,7 @@ export class Lexer {
this.step();
return { ...this.token("string", pos), stringValue: value };
}
if (this.test(/[\+\{\};=\-\*\(\)\.,:;\[\]><!0#]/)) {
if (this.test(/[\+\{\};=\-\*\(\)\.,:;\[\]><!0#&]/)) {
const first = this.current();
this.step();
if (first === "=" && !this.done() && this.test("=")) {

View File

@ -1,48 +1,65 @@
import { Builtins, Ops } from "./arch.ts";
import { Expr, Stmt } from "./ast.ts";
import { LocalLeaf, Locals, LocalsFnRoot } from "./lowerer_locals.ts";
import { Assembler, Label } from "./assembler.ts";
import { vtypeToString } from "./vtype.ts";
import { AnnoView, Expr, Stmt } from "./ast.ts";
import { LocalLeaf, Locals, LocalsFnRoot } from "./lowerer_locals.ts";
import { MonoCallNameGenMap, MonoFn, MonoFnsMap } from "./mono.ts";
import { Pos } from "./token.ts";
import { vtypeToString } from "./vtype.ts";
export type FnNamesMap = { [pc: number]: string };
export class Lowerer {
private program = Assembler.newRoot();
private locals: Locals = new LocalsFnRoot();
private fnStmtIdLabelMap: { [stmtId: number]: string } = {};
private fnLabelNameMap: { [name: string]: string } = {};
private returnStack: Label[] = [];
private breakStack: Label[] = [];
public constructor(private lastPos: Pos) {}
public constructor(
private monoFns: MonoFnsMap,
private callMap: MonoCallNameGenMap,
private lastPos: Pos,
) {}
public lower(stmts: Stmt[]) {
public lower(): { program: number[]; fnNames: FnNamesMap } {
const fnLabelNameMap: FnLabelMap = {};
for (const nameGen in this.monoFns) {
fnLabelNameMap[nameGen] = nameGen;
}
this.addPrelimiary();
for (const fn of Object.values(this.monoFns)) {
const fnProgram = new MonoFnLowerer(
fn,
this.program.fork(),
this.callMap,
).lower();
this.program.join(fnProgram);
}
this.addConcluding();
const { program, locs } = this.program.assemble();
const fnNames: FnNamesMap = {};
for (const label in locs) {
if (label in fnLabelNameMap) {
fnNames[locs[label]] = fnLabelNameMap[label];
}
}
return { program, fnNames };
}
private addPrelimiary() {
this.addClearingSourceMap();
this.program.add(Ops.PushPtr, { label: "main" });
this.program.add(Ops.Call, 0);
this.program.add(Ops.PushPtr, { label: "_exit" });
this.program.add(Ops.Jump);
this.scoutFnHeaders(stmts);
for (const stmt of stmts) {
this.lowerStaticStmt(stmt);
}
}
private addConcluding() {
this.program.setLabel({ label: "_exit" });
this.addSourceMap(this.lastPos);
this.program.add(Ops.Pop);
}
public finish(): { program: number[]; fnNames: FnNamesMap } {
const { program, locs } = this.program.assemble();
const fnNames: FnNamesMap = {};
for (const label in locs) {
if (label in this.fnLabelNameMap) {
fnNames[locs[label]] = this.fnLabelNameMap[label];
}
}
return { program, fnNames };
}
private addSourceMap({ index, line, col }: Pos) {
this.program.add(Ops.SourceMap, index, line, col);
}
@ -51,30 +68,84 @@ export class Lowerer {
this.program.add(Ops.SourceMap, 0, 1, 1);
}
private scoutFnHeaders(stmts: Stmt[]) {
for (const stmt of stmts) {
if (stmt.kind.type !== "fn") {
continue;
}
const label = stmt.kind.ident === "main"
? "main"
: `${stmt.kind.ident}_${stmt.id}`;
this.fnStmtIdLabelMap[stmt.id] = label;
}
public printProgram() {
this.program.printProgram();
}
}
type FnLabelMap = { [nameGen: string]: string };
class MonoFnLowerer {
private locals: Locals = new LocalsFnRoot();
private returnStack: Label[] = [];
private breakStack: Label[] = [];
public constructor(
private fn: MonoFn,
private program: Assembler,
private callMap: MonoCallNameGenMap,
) {}
public lower(): Assembler {
this.lowerFnStmt(this.fn.stmt);
return this.program;
}
private lowerStaticStmt(stmt: Stmt) {
switch (stmt.kind.type) {
case "fn":
return this.lowerFnStmt(stmt);
case "error":
case "break":
case "return":
case "let":
case "assign":
case "expr":
private lowerFnStmt(stmt: Stmt) {
if (stmt.kind.type !== "fn") {
throw new Error();
}
throw new Error(`unhandled static statement '${stmt.kind.type}'`);
const label = this.fn.nameGen;
this.program.setLabel({ label });
this.addSourceMap(stmt.pos);
const outerLocals = this.locals;
const fnRoot = new LocalsFnRoot(outerLocals);
const outerProgram = this.program;
const returnLabel = this.program.makeLabel();
this.returnStack.push(returnLabel);
this.program = outerProgram.fork();
this.locals = fnRoot;
for (const { ident } of stmt.kind.params) {
this.locals.allocSym(ident);
}
const annos = new AnnoView(stmt.details);
if (annos.has("builtin")) {
const anno = annos.get("builtin");
if (!anno) {
throw new Error();
}
this.lowerFnBuiltinBody(anno.args);
} else if (annos.has("remainder")) {
this.program.add(Ops.Remainder);
} else {
this.lowerExpr(stmt.kind.body);
}
this.locals = outerLocals;
const localAmount = fnRoot.stackReserved() -
stmt.kind.params.length;
for (let i = 0; i < localAmount; ++i) {
outerProgram.add(Ops.PushNull);
}
this.returnStack.pop();
this.program.setLabel(returnLabel);
this.program.add(Ops.Return);
outerProgram.join(this.program);
this.program = outerProgram;
}
private addSourceMap({ index, line, col }: Pos) {
this.program.add(Ops.SourceMap, index, line, col);
}
private addClearingSourceMap() {
this.program.add(Ops.SourceMap, 0, 1, 1);
}
private lowerStmt(stmt: Stmt) {
@ -107,7 +178,7 @@ export class Lowerer {
switch (stmt.kind.subject.kind.type) {
case "field": {
this.lowerExpr(stmt.kind.subject.kind.subject);
this.program.add(Ops.PushString, stmt.kind.subject.kind.value);
this.program.add(Ops.PushString, stmt.kind.subject.kind.ident);
this.program.add(Ops.Builtin, Builtins.StructSet);
return;
}
@ -153,52 +224,6 @@ export class Lowerer {
this.program.add(Ops.Jump);
}
private lowerFnStmt(stmt: Stmt) {
if (stmt.kind.type !== "fn") {
throw new Error();
}
const label = stmt.kind.ident === "main"
? "main"
: `${stmt.kind.ident}_${stmt.id}`;
this.program.setLabel({ label });
this.fnLabelNameMap[label] = stmt.kind.ident;
this.addSourceMap(stmt.pos);
const outerLocals = this.locals;
const fnRoot = new LocalsFnRoot(outerLocals);
const outerProgram = this.program;
const returnLabel = this.program.makeLabel();
this.returnStack.push(returnLabel);
this.program = outerProgram.fork();
this.locals = fnRoot;
for (const { ident } of stmt.kind.params) {
this.locals.allocSym(ident);
}
if (stmt.kind.anno?.ident === "builtin") {
this.lowerFnBuiltinBody(stmt.kind.anno.values);
} else if (stmt.kind.anno?.ident === "remainder") {
this.program.add(Ops.Remainder);
} else {
this.lowerExpr(stmt.kind.body);
}
this.locals = outerLocals;
const localAmount = fnRoot.stackReserved() -
stmt.kind.params.length;
for (let i = 0; i < localAmount; ++i) {
outerProgram.add(Ops.PushNull);
}
this.returnStack.pop();
this.program.setLabel(returnLabel);
this.program.add(Ops.Return);
outerProgram.join(this.program);
this.program = outerProgram;
}
private lowerFnBuiltinBody(annoArgs: Expr[]) {
if (annoArgs.length !== 1) {
throw new Error("invalid # of arguments to builtin annotation");
@ -209,7 +234,7 @@ export class Lowerer {
`unexpected argument type '${anno.kind.type}' expected 'ident'`,
);
}
const value = anno.kind.value;
const value = anno.kind.ident;
const builtin = Object.entries(Builtins).find((entry) =>
entry[0] === value
)?.[1];
@ -252,11 +277,13 @@ export class Lowerer {
case "group":
return void this.lowerExpr(expr.kind.expr);
case "field":
break;
return this.lowerFieldExpr(expr);
case "index":
return this.lowerIndexExpr(expr);
case "call":
return this.lowerCallExpr(expr);
case "etype_args":
return this.lowerETypeArgsExpr(expr);
case "unary":
return this.lowerUnaryExpr(expr);
case "binary":
@ -271,6 +298,20 @@ export class Lowerer {
throw new Error(`unhandled expr '${expr.kind.type}'`);
}
private lowerFieldExpr(expr: Expr) {
if (expr.kind.type !== "field") {
throw new Error();
}
this.lowerExpr(expr.kind.subject);
this.program.add(Ops.PushString, expr.kind.ident);
if (expr.kind.subject.vtype?.type == "struct") {
this.program.add(Ops.Builtin, Builtins.StructAt);
return;
}
throw new Error(`unhandled field subject type '${expr.kind.subject}'`);
}
private lowerIndexExpr(expr: Expr) {
if (expr.kind.type !== "index") {
throw new Error();
@ -306,8 +347,42 @@ export class Lowerer {
return;
}
if (expr.kind.sym.type === "fn") {
const label = this.fnStmtIdLabelMap[expr.kind.sym.stmt.id];
this.program.add(Ops.PushPtr, { label });
// Is this smart? Well, my presumption is
// that it isn't. The underlying problem, which
// this solutions raison d'être is to solve, is
// that the compiler, as it d'être's currently
// doesn't support checking and infering generic
// fn args all the way down to the sym. Therefore,
// when a sym is checked in a call expr, we can't
// really do anything useful. Instead the actual
// function pointer pointing to the actual
// monomorphized function is emplaced when
// lowering the call expression itself. But what
// should we do then, if the user decides to
// assign a function to a local? You might ask.
// You see, that's where the problem lies.
// My current, very thought out solution, as
// you can read below, is to push a null pointer,
// for it to then be replaced later. This will
// probably cause many hastles in the future
// for myself in particular, when trying to
// decipher the lowerer's output. So if you're
// the unlucky girl, who has tried for ages to
// decipher why a zero value is pushed and then
// later replaced, and then you finally
// stumbled upon this here implementation,
// let me first say, I'm so sorry. At the time
// of writing, I really haven't thought out
// very well, how the generic call system should
// work, and it's therefore a bit flaky, and the
// implementation kinda looks like it was
// implementated by a girl who didn't really
// understand very well what they were
// implementing at the time that they were
// implementing it. Anyway, I just wanted to
// apologize. Happy coding.
// -Your favorite compiler girl.
this.program.add(Ops.PushPtr, 0);
return;
}
throw new Error(`unhandled sym type '${expr.kind.sym.type}'`);
@ -430,6 +505,18 @@ export class Lowerer {
default:
}
}
if (vtype.type === "bool") {
switch (expr.kind.binaryType) {
case "==":
this.program.add(Ops.And);
return;
case "!=":
this.program.add(Ops.And);
this.program.add(Ops.Not);
return;
default:
}
}
if (vtype.type === "string") {
if (expr.kind.binaryType === "+") {
this.program.add(Ops.Builtin, Builtins.StringConcat);
@ -462,9 +549,18 @@ export class Lowerer {
this.lowerExpr(arg);
}
this.lowerExpr(expr.kind.subject);
this.program.add(Ops.Pop);
this.program.add(Ops.PushPtr, { label: this.callMap[expr.id] });
this.program.add(Ops.Call, expr.kind.args.length);
}
private lowerETypeArgsExpr(expr: Expr) {
if (expr.kind.type !== "etype_args") {
throw new Error();
}
this.lowerExpr(expr.kind.subject);
}
private lowerIfExpr(expr: Expr) {
if (expr.kind.type !== "if") {
throw new Error();
@ -528,7 +624,6 @@ export class Lowerer {
}
const outerLocals = this.locals;
this.locals = new LocalLeaf(this.locals);
this.scoutFnHeaders(expr.kind.stmts);
for (const stmt of expr.kind.stmts) {
this.addSourceMap(stmt.pos);
this.lowerStmt(stmt);
@ -541,8 +636,4 @@ export class Lowerer {
}
this.locals = outerLocals;
}
public printProgram() {
this.program.printProgram();
}
}

View 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
View 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]);
}
}

View 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);
}
}
}

View 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);
}
}

View 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();
}
}

View 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
View 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
View 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();

View File

@ -1,5 +1,4 @@
import {
Anno,
AssignType,
AstCreator,
BinaryType,
@ -7,8 +6,11 @@ import {
ETypeKind,
Expr,
ExprKind,
Field,
GenericParam,
Param,
Stmt,
StmtDetails,
StmtKind,
UnaryType,
} from "./ast.ts";
@ -16,6 +18,8 @@ import { printStackTrace, Reporter } from "./info.ts";
import { Lexer } from "./lexer.ts";
import { Pos, Token } from "./token.ts";
type Res<T> = { ok: true; value: T } | { ok: false; pos?: Pos };
export class Parser {
private currentToken: Token | null;
@ -34,26 +38,34 @@ export class Parser {
private parseStmts(): Stmt[] {
const stmts: Stmt[] = [];
while (!this.done()) {
if (this.test("fn")) {
stmts.push(this.parseFn());
} else if (
this.test("let") || this.test("return") || this.test("break")
) {
stmts.push(this.parseSingleLineBlockStmt());
this.eatSemicolon();
} else if (
["{", "if", "loop", "while", "for"].some((tt) => this.test(tt))
) {
const expr = this.parseMultiLineBlockExpr();
stmts.push(this.stmt({ type: "expr", expr }, expr.pos));
} else {
stmts.push(this.parseAssign());
this.eatSemicolon();
}
stmts.push(this.parseStmt());
}
return stmts;
}
private parseStmt(): Stmt {
if (
["#", "pub", "mod", "fn"].some((tt) => this.test(tt))
) {
return this.parseItemStmt();
} else if (
["let", "type_alias", "return", "break"].some((tt) => this.test(tt))
) {
const expr = this.parseSingleLineBlockStmt();
this.eatSemicolon();
return expr;
} else if (
["{", "if", "loop", "while", "for"].some((tt) => this.test(tt))
) {
const expr = this.parseMultiLineBlockExpr();
return (this.stmt({ type: "expr", expr }, expr.pos));
} else {
const expr = this.parseAssign();
this.eatSemicolon();
return expr;
}
}
private parseMultiLineBlockExpr(): Expr {
const pos = this.pos();
if (this.test("{")) {
@ -80,6 +92,9 @@ export class Parser {
if (this.test("let")) {
return this.parseLet();
}
if (this.test("type_alias")) {
return this.parseTypeAlias();
}
if (this.test("return")) {
return this.parseReturn();
}
@ -107,19 +122,21 @@ export class Parser {
private parseBlock(): Expr {
const pos = this.pos();
this.step();
let stmts: Stmt[] = [];
const stmts: Stmt[] = [];
while (!this.done()) {
if (this.test("}")) {
this.step();
return this.expr({ type: "block", stmts }, pos);
} else if (
this.test("return") || this.test("break") || this.test("let")
["#", "pub", "mod", "fn"].some((tt) => this.test(tt))
) {
stmts.push(this.parseItemStmt());
} else if (
["let", "type_alias", "return", "break"]
.some((tt) => this.test(tt))
) {
stmts.push(this.parseSingleLineBlockStmt());
this.eatSemicolon();
} else if (this.test("fn")) {
stmts.push(this.parseSingleLineBlockStmt());
stmts.push(this.parseFn());
} else if (
["{", "if", "loop", "while", "for"].some((tt) => this.test(tt))
) {
@ -163,7 +180,110 @@ export class Parser {
return this.expr({ type: "error" }, pos);
}
private parseFn(): Stmt {
private parseItemStmt(
pos = this.pos(),
details: StmtDetails = {
pub: false,
annos: [],
},
): Stmt {
const spos = this.pos();
if (this.test("#") && !details.pub) {
this.step();
if (!this.test("[")) {
this.report("expected '['");
return this.stmt({ type: "error" }, spos);
}
this.step();
if (!this.test("ident")) {
this.report("expected 'ident'");
return this.stmt({ type: "error" }, spos);
}
const ident = this.current().identValue!;
this.step();
const args: Expr[] = [];
if (this.test("(")) {
this.step();
if (!this.done() && !this.test(")")) {
args.push(this.parseExpr());
while (this.test(",")) {
this.step();
if (this.done() || this.test(")")) {
break;
}
args.push(this.parseExpr());
}
}
if (!this.test(")")) {
this.report("expected ')'");
return this.stmt({ type: "error" }, spos);
}
this.step();
}
if (!this.test("]")) {
this.report("expected ']'");
return this.stmt({ type: "error" }, spos);
}
this.step();
const anno = { ident, args, pos: spos };
return this.parseItemStmt(pos, {
...details,
annos: [...details.annos, anno],
});
} else if (this.test("pub") && !details.pub) {
this.step();
return this.parseItemStmt(pos, { ...details, pub: true });
} else if (this.test("mod")) {
return this.parseMod(details);
} else if (this.test("fn")) {
return this.parseFn(details);
} else {
this.report("expected item statement");
return this.stmt({ type: "error" }, pos);
}
}
private parseMod(details: StmtDetails): Stmt {
const pos = this.pos();
this.step();
if (!this.test("ident")) {
this.report("expected 'ident'");
return this.stmt({ type: "error" }, pos);
}
const ident = this.current().identValue!;
this.step();
if (this.test(";")) {
this.eatSemicolon();
return this.stmt({ type: "mod_file", ident, filePath: ident }, pos);
}
if (this.test("string")) {
const filePath = this.current().stringValue!;
this.step();
this.eatSemicolon();
return this.stmt({ type: "mod_file", ident, filePath }, pos);
}
if (!this.test("{")) {
this.report("expected '{' or 'string'");
return this.stmt({ type: "error" }, pos);
}
this.step();
const stmts: Stmt[] = [];
while (!this.done() && !this.test("}")) {
stmts.push(this.parseStmt());
}
if (!this.test("}")) {
this.report("expected '}'");
return this.stmt({ type: "error" }, pos);
}
this.step();
return this.stmt({ type: "mod_block", ident, stmts }, pos, details);
}
private parseFn(details: StmtDetails): Stmt {
const pos = this.pos();
this.step();
if (!this.test("ident")) {
@ -172,21 +292,21 @@ export class Parser {
}
const ident = this.current().identValue!;
this.step();
let genericParams: GenericParam[] | undefined;
if (this.test("<")) {
genericParams = this.parseFnETypeParams();
}
if (!this.test("(")) {
this.report("expected '('");
return this.stmt({ type: "error" }, pos);
}
const params = this.parseFnParams();
let returnType: EType | null = null;
let returnType: EType | undefined;
if (this.test("->")) {
this.step();
returnType = this.parseEType();
}
let anno: Anno | null = null;
if (this.test("#")) {
anno = this.parseAnno();
}
if (!this.test("{")) {
this.report("expected block");
return this.stmt({ type: "error" }, pos);
@ -196,105 +316,104 @@ export class Parser {
{
type: "fn",
ident,
genericParams,
params,
returnType: returnType !== null ? returnType : undefined,
returnType,
body,
anno: anno != null ? anno : undefined,
},
pos,
details,
);
}
private parseAnnoArgs(): Expr[] {
this.step();
if (!this.test("(")) {
this.report("expected '('");
return [];
}
this.step();
const annoArgs: Expr[] = [];
if (!this.test(")")) {
annoArgs.push(this.parseExpr());
while (this.test(",")) {
this.step();
if (this.test(")")) {
break;
}
annoArgs.push(this.parseExpr());
}
}
if (!this.test(")")) {
this.report("expected ')'");
return [];
}
this.step();
return annoArgs;
private parseFnETypeParams(): GenericParam[] {
return this.parseDelimitedList(this.parseETypeParam, ">", ",");
}
private parseAnno(): Anno | null {
private parseETypeParam(index: number): Res<GenericParam> {
const pos = this.pos();
this.step();
if (!this.test("[")) {
this.report("expected '['");
return null;
if (this.test("ident")) {
const ident = this.current().identValue!;
this.step();
return {
ok: true,
value: this.astCreator.genericParam({ index, ident, pos }),
};
}
this.step();
if (!this.test("ident")) {
this.report("expected identifier");
return null;
}
const ident = this.current().identValue!;
const values = this.parseAnnoArgs();
if (!this.test("]")) {
this.report("expected ']'");
return null;
}
this.step();
return { ident, pos, values };
this.report("expected generic parameter");
return { ok: false };
}
private parseFnParams(): Param[] {
this.step();
if (this.test(")")) {
this.step();
return [];
}
const params: Param[] = [];
const paramResult = this.parseParam();
if (!paramResult.ok) {
return [];
}
params.push(paramResult.value);
while (this.test(",")) {
this.step();
if (this.test(")")) {
break;
}
const paramResult = this.parseParam();
if (!paramResult.ok) {
return [];
}
params.push(paramResult.value);
}
if (!this.test(")")) {
this.report("expected ')'");
return params;
}
this.step();
return params;
return this.parseDelimitedList(this.parseParam, ")", ",");
}
private parseParam(): { ok: true; value: Param } | { ok: false } {
private parseDelimitedList<T>(
parseElem: (this: Parser, index: number) => Res<T>,
endToken: string,
delimiter: string,
): T[] {
this.step();
if (this.test(endToken)) {
this.step();
return [];
}
let i = 0;
const elems: T[] = [];
const elemRes = parseElem.call(this, i);
if (!elemRes.ok) {
return [];
}
elems.push(elemRes.value);
i += 1;
while (this.test(delimiter)) {
this.step();
if (this.test(endToken)) {
break;
}
const elemRes = parseElem.call(this, i);
if (!elemRes.ok) {
return [];
}
elems.push(elemRes.value);
i += 1;
}
if (!this.test(endToken)) {
this.report(`expected '${endToken}'`);
return elems;
}
this.step();
return elems;
}
private parseParam(index?: number): Res<Param> {
const pos = this.pos();
if (this.test("ident")) {
if (this.test("ident") || this.test("mut")) {
let mut = false;
if (this.test("mut")) {
mut = true;
this.step();
}
const ident = this.current().identValue!;
this.step();
if (this.test(":")) {
this.step();
const etype = this.parseEType();
return { ok: true, value: { ident, etype, pos } };
return {
ok: true,
value: this.astCreator.param({
index,
ident,
mut,
etype,
pos,
}),
};
}
return { ok: true, value: { ident, pos } };
return {
ok: true,
value: this.astCreator.param({ index, ident, mut, pos }),
};
}
this.report("expected param");
return { ok: false };
@ -317,6 +436,17 @@ export class Parser {
return this.stmt({ type: "let", param, value }, pos);
}
private parseTypeAlias(): Stmt {
const pos = this.pos();
this.step();
const paramResult = this.parseParam();
if (!paramResult.ok) {
return this.stmt({ type: "error" }, pos);
}
const param = paramResult.value;
return this.stmt({ type: "type_alias", param }, pos);
}
private parseAssign(): Stmt {
const pos = this.pos();
const subject = this.parseExpr();
@ -444,6 +574,80 @@ export class Parser {
return this.expr({ type: "for", decl, cond, incr, body }, pos);
}
private parseArray(): Expr {
const pos = this.pos();
this.step();
const exprs: Expr[] = [];
if (!this.test("]")) {
exprs.push(this.parseExpr());
while (this.test(",")) {
this.step();
if (this.done() || this.test("]")) {
break;
}
exprs.push(this.parseExpr());
}
}
if (!this.test("]")) {
this.report("expected ']'");
return this.expr({ type: "error" }, pos);
}
this.step();
return this.expr({ type: "array", exprs }, pos);
}
private parseStruct(): Expr {
const pos = this.pos();
this.step();
if (!this.test("{")) {
this.report("expected '{'");
return this.expr({ type: "error" }, pos);
}
this.step();
const fields: Field[] = [];
if (!this.test("}")) {
const res = this.parseStructField();
if (!res.ok) {
return this.expr({ type: "error" }, res.pos!);
}
fields.push(res.value);
while (this.test(",")) {
this.step();
if (this.done() || this.test("}")) {
break;
}
const res = this.parseStructField();
if (!res.ok) {
return this.expr({ type: "error" }, res.pos!);
}
fields.push(res.value);
}
}
if (!this.test("}")) {
this.report("expected '}'");
return this.expr({ type: "error" }, pos);
}
this.step();
return this.expr({ type: "struct", fields }, pos);
}
private parseStructField(): Res<Field> {
const pos = this.pos();
if (!this.test("ident")) {
this.report("expected 'ident'");
return { ok: false, pos };
}
const ident = this.current().identValue!;
this.step();
if (!this.test(":")) {
this.report("expected ':'");
return { ok: false, pos };
}
this.step();
const expr = this.parseExpr();
return { ok: true, value: { ident, expr, pos } };
}
private parseIf(): Expr {
const pos = this.pos();
this.step();
@ -586,54 +790,45 @@ export class Parser {
const subject = this.parsePrefix();
return this.expr({ type: "unary", unaryType, subject }, pos);
}
if (this.test("&")) {
this.step();
let type: "ref" | "ref_mut" = "ref";
if (this.test("mut")) {
this.step();
type = "ref_mut";
}
const subject = this.parsePrefix();
return this.expr({ type, subject }, pos);
}
if (this.test("*")) {
this.step();
const subject = this.parsePrefix();
return this.expr({ type: "deref", subject }, pos);
}
return this.parsePostfix();
}
private parsePostfix(): Expr {
let subject = this.parseOperand();
while (true) {
const pos = this.pos();
if (this.test(".")) {
this.step();
if (!this.test("ident")) {
this.report("expected ident");
return this.expr({ type: "error" }, pos);
}
const value = this.current().identValue!;
this.step();
subject = this.expr({ type: "field", subject, value }, pos);
subject = this.parseFieldTail(subject);
continue;
}
if (this.test("[")) {
this.step();
const value = this.parseExpr();
if (!this.test("]")) {
this.report("expected ']'");
return this.expr({ type: "error" }, pos);
}
this.step();
subject = this.expr({ type: "index", subject, value }, pos);
subject = this.parseIndexTail(subject);
continue;
}
if (this.test("(")) {
this.step();
let args: Expr[] = [];
if (!this.test(")")) {
args.push(this.parseExpr());
while (this.test(",")) {
this.step();
if (this.test(")")) {
break;
}
args.push(this.parseExpr());
}
}
if (!this.test(")")) {
this.report("expected ')'");
return this.expr({ type: "error" }, pos);
}
this.step();
subject = this.expr({ type: "call", subject, args }, pos);
subject = this.parseCallTail(subject);
continue;
}
if (this.test("::")) {
subject = this.parsePathTail(subject);
continue;
}
if (this.test("::<")) {
subject = this.parseETypeArgsTail(subject);
continue;
}
break;
@ -641,12 +836,79 @@ export class Parser {
return subject;
}
private parseETypeArgsTail(subject: Expr): Expr {
const pos = this.pos();
const etypeArgs = this.parseDelimitedList(
this.parseETypeArg,
">",
",",
);
return this.expr(
{ type: "etype_args", subject, etypeArgs },
pos,
);
}
private parseFieldTail(subject: Expr): Expr {
const pos = this.pos();
this.step();
if (!this.test("ident")) {
this.report("expected ident");
return this.expr({ type: "error" }, pos);
}
const ident = this.current().identValue!;
this.step();
return this.expr({ type: "field", subject, ident }, pos);
}
private parseIndexTail(subject: Expr): Expr {
const pos = this.pos();
this.step();
const value = this.parseExpr();
if (!this.test("]")) {
this.report("expected ']'");
return this.expr({ type: "error" }, pos);
}
this.step();
return this.expr({ type: "index", subject, value }, pos);
}
private parseCallTail(subject: Expr): Expr {
const pos = this.pos();
const args = this.parseDelimitedList(
this.parseExprArg,
")",
",",
);
return this.expr({ type: "call", subject, args }, pos);
}
private parsePathTail(subject: Expr): Expr {
const pos = this.pos();
this.step();
if (!this.test("ident")) {
this.report("expected ident");
return this.expr({ type: "error" }, pos);
}
const ident = this.current().identValue!;
this.step();
return this.expr({ type: "path", subject, ident }, pos);
}
private parseExprArg(): Res<Expr> {
return { ok: true, value: this.parseExpr() };
}
private parseETypeArg(): Res<EType> {
return { ok: true, value: this.parseEType() };
}
private parseOperand(): Expr {
const pos = this.pos();
if (this.test("ident")) {
const value = this.current().identValue!;
const ident = this.current().identValue!;
this.step();
return this.expr({ type: "ident", value }, pos);
return this.expr({ type: "ident", ident }, pos);
}
if (this.test("int")) {
const value = this.current().intValue!;
@ -680,6 +942,12 @@ export class Parser {
this.step();
return this.expr({ type: "group", expr }, pos);
}
if (this.test("[")) {
return this.parseArray();
}
if (this.test("struct")) {
return this.parseStruct();
}
if (this.test("{")) {
return this.parseBlock();
}
@ -690,27 +958,36 @@ export class Parser {
return this.parseLoop();
}
this.report("expected expr", pos);
this.report(`expected expr, got '${this.current().type}'`, pos);
this.step();
return this.expr({ type: "error" }, pos);
}
private parseEType(): EType {
const pos = this.pos();
if (["null", "int", "bool", "string"].includes(this.current().type)) {
const type = this.current().type as
| "null"
| "int"
| "bool"
| "string";
this.step();
return this.etype({ type }, pos);
}
if (this.test("ident")) {
const ident = this.current().identValue!;
this.step();
return this.etype({ type: "ident", value: ident }, pos);
return this.etype({ type: "ident", ident: ident }, pos);
}
if (this.test("[")) {
this.step();
const inner = this.parseEType();
const subject = this.parseEType();
if (!this.test("]")) {
this.report("expected ']'", pos);
return this.etype({ type: "error" }, pos);
}
this.step();
return this.etype({ type: "array", inner }, pos);
return this.etype({ type: "array", subject }, pos);
}
if (this.test("struct")) {
this.step();
@ -721,6 +998,26 @@ export class Parser {
const fields = this.parseETypeStructFields();
return this.etype({ type: "struct", fields }, pos);
}
if (this.test("&")) {
this.step();
let type: "ref" | "ref_mut" = "ref";
if (this.test("mut")) {
this.step();
type = "ref_mut";
}
const subject = this.parseEType();
return this.etype({ type, subject }, pos);
}
if (this.test("*")) {
this.step();
let type: "ptr" | "ptr_mut" = "ptr";
if (this.test("mut")) {
this.step();
type = "ptr_mut";
}
const subject = this.parseEType();
return this.etype({ type, subject }, pos);
}
this.report("expected type");
return this.etype({ type: "error" }, pos);
}
@ -777,7 +1074,6 @@ export class Parser {
}
private report(msg: string, pos = this.pos()) {
console.log(`Parser: ${msg} at ${pos.line}:${pos.col}`);
this.reporter.reportError({
msg,
pos,
@ -786,8 +1082,8 @@ export class Parser {
printStackTrace();
}
private stmt(kind: StmtKind, pos: Pos): Stmt {
return this.astCreator.stmt(kind, pos);
private stmt(kind: StmtKind, pos: Pos, details?: StmtDetails): Stmt {
return this.astCreator.stmt(kind, pos, details);
}
private expr(kind: ExprKind, pos: Pos): Expr {

View File

@ -1,31 +1,88 @@
import { Expr, Stmt } from "./ast.ts";
import { EType, Expr, Stmt } from "./ast.ts";
import {
AstVisitor,
visitEType,
visitExpr,
visitParam,
VisitRes,
visitStmt,
visitStmts,
} from "./ast_visitor.ts";
import { printStackTrace, Reporter } from "./info.ts";
import {
EntryModSyms,
FnSyms,
GlobalSyms,
LeafSyms,
StaticSyms,
ModSyms,
Syms,
} from "./resolver_syms.ts";
import { Pos } from "./token.ts";
export class Resolver implements AstVisitor<[Syms]> {
private root = new GlobalSyms();
public constructor(private reporter: Reporter) {
}
public resolve(stmts: Stmt[]): VisitRes {
const scopeSyms = new StaticSyms(this.root);
this.scoutFnStmts(stmts, scopeSyms);
visitStmts(stmts, this, scopeSyms);
const syms = new EntryModSyms("root");
this.scout(stmts, syms);
visitStmts(stmts, this, syms);
return "stop";
}
private scout(stmts: Stmt[], syms: Syms) {
for (const stmt of stmts) {
if (stmt.kind.type === "fn") {
if (syms.definedLocally(stmt.kind.ident)) {
this.reportAlreadyDefined(stmt.kind.ident, stmt.pos, syms);
return;
}
const ident = stmt.kind.ident;
stmt.kind.sym = syms.define(ident, {
ident: stmt.kind.ident,
type: "fn",
fullPath: `${syms.pathString()}::${ident}`,
pos: stmt.pos,
stmt,
});
} else if (stmt.kind.type === "type_alias") {
const ident = stmt.kind.param.ident;
if (syms.definedLocally(ident)) {
this.reportAlreadyDefined(ident, stmt.pos, syms);
return;
}
syms.define(ident, {
ident,
type: "type_alias",
fullPath: `${syms.pathString()}::${ident}`,
pos: stmt.kind.param.pos,
stmt,
param: stmt.kind.param,
});
}
}
}
visitModStmt(stmt: Stmt, syms: Syms): VisitRes {
if (stmt.kind.type !== "mod") {
throw new Error("expected let statement");
}
const modSyms = new ModSyms(syms, stmt.kind.ident);
const { mod, ident } = stmt.kind;
this.scout(mod.ast, modSyms);
visitStmts(mod.ast, this, modSyms);
if (syms.definedLocally(ident)) {
this.reportAlreadyDefined(ident, stmt.pos, syms);
return;
}
syms.define(ident, {
type: "mod",
ident,
fullPath: `${syms.pathString()}::${ident}`,
pos: stmt.pos,
syms: modSyms,
});
return "stop";
}
@ -39,9 +96,10 @@ export class Resolver implements AstVisitor<[Syms]> {
this.reportAlreadyDefined(ident, stmt.pos, syms);
return;
}
syms.define(ident, {
stmt.kind.param.sym = syms.define(ident, {
ident,
type: "let",
fullPath: ident,
pos: stmt.kind.param.pos,
stmt,
param: stmt.kind.param,
@ -49,23 +107,11 @@ export class Resolver implements AstVisitor<[Syms]> {
return "stop";
}
private scoutFnStmts(stmts: Stmt[], syms: Syms) {
for (const stmt of stmts) {
if (stmt.kind.type !== "fn") {
continue;
}
if (syms.definedLocally(stmt.kind.ident)) {
this.reportAlreadyDefined(stmt.kind.ident, stmt.pos, syms);
return;
}
const ident = stmt.kind.ident;
syms.define(ident, {
ident: stmt.kind.ident,
type: "fn",
pos: stmt.pos,
stmt,
});
visitTypeAliasStmt(stmt: Stmt, _syms: Syms): VisitRes {
if (stmt.kind.type !== "type_alias") {
throw new Error("expected type_alias statement");
}
// nothing to do here
}
visitFnStmt(stmt: Stmt, syms: Syms): VisitRes {
@ -73,18 +119,37 @@ export class Resolver implements AstVisitor<[Syms]> {
throw new Error("expected fn statement");
}
const fnScopeSyms = new FnSyms(syms);
for (const param of stmt.kind.params) {
for (const param of stmt.kind.genericParams ?? []) {
if (fnScopeSyms.definedLocally(param.ident)) {
this.reportAlreadyDefined(param.ident, param.pos, syms);
continue;
}
fnScopeSyms.define(param.ident, {
ident: param.ident,
type: "generic",
fullPath: param.ident,
pos: param.pos,
stmt,
genericParam: param,
});
}
for (const param of stmt.kind.params) {
if (fnScopeSyms.definedLocally(param.ident)) {
this.reportAlreadyDefined(param.ident, param.pos, syms);
continue;
}
visitParam(param, this, fnScopeSyms);
fnScopeSyms.define(param.ident, {
ident: param.ident,
type: "fn_param",
fullPath: param.ident,
pos: param.pos,
param,
});
}
if (stmt.kind.returnType) {
visitEType(stmt.kind.returnType, this, fnScopeSyms);
}
visitExpr(stmt.kind.body, this, fnScopeSyms);
return "stop";
}
@ -93,18 +158,51 @@ export class Resolver implements AstVisitor<[Syms]> {
if (expr.kind.type !== "ident") {
throw new Error("expected ident");
}
const ident = expr.kind;
const symResult = syms.get(ident.value);
const ident = expr.kind.ident;
const symResult = syms.get(ident);
if (!symResult.ok) {
this.reportUseOfUndefined(ident.value, expr.pos, syms);
this.reportUseOfUndefined(ident, expr.pos, syms);
return;
}
const sym = symResult.sym;
expr.kind = { type: "sym", ident, sym };
return "stop";
}
visitPathExpr(expr: Expr, syms: Syms): VisitRes {
if (expr.kind.type !== "path") {
throw new Error("expected ident");
}
visitExpr(expr.kind.subject, this, syms);
if (expr.kind.subject.kind.type !== "sym") {
throw new Error("this error is not handled properly");
}
const subjectSym = expr.kind.subject.kind.sym;
if (subjectSym.type !== "mod") {
this.reporter.reportError({
reporter: "Resolver",
msg: `path expression are not implemented for '${subjectSym.type}' symbols`,
pos: expr.pos,
});
printStackTrace();
return "stop";
}
const getRes = subjectSym.syms.get(expr.kind.ident);
if (!getRes.ok) {
this.reportUseOfUndefined(
expr.kind.ident,
expr.pos,
subjectSym.syms,
);
return "stop";
}
expr.kind = {
type: "sym",
ident: ident.value,
sym,
ident: expr.kind.ident,
sym: getRes.sym,
};
return "stop";
}
@ -113,7 +211,7 @@ export class Resolver implements AstVisitor<[Syms]> {
throw new Error();
}
const childSyms = new LeafSyms(syms);
this.scoutFnStmts(expr.kind.stmts, childSyms);
this.scout(expr.kind.stmts, childSyms);
visitStmts(expr.kind.stmts, this, childSyms);
if (expr.kind.expr) {
visitExpr(expr.kind.expr, this, childSyms);
@ -134,6 +232,21 @@ export class Resolver implements AstVisitor<[Syms]> {
return "stop";
}
visitIdentEType(etype: EType, syms: Syms): VisitRes {
if (etype.kind.type !== "ident") {
throw new Error();
}
const ident = etype.kind.ident;
const symResult = syms.get(ident);
if (!symResult.ok) {
this.reportUseOfUndefined(ident, etype.pos, syms);
return;
}
const sym = symResult.sym;
etype.kind = { type: "sym", ident, sym };
return "stop";
}
private reportUseOfUndefined(ident: string, pos: Pos, _syms: Syms) {
this.reporter.reportError({
reporter: "Resolver",

View File

@ -1,66 +1,112 @@
import { Sym } from "./ast.ts";
import type { Sym } from "./ast.ts";
export type SymMap = { [ident: string]: Sym };
type GetRes = { ok: true; sym: Sym } | { ok: false };
export interface Syms {
define(ident: string, sym: Sym): void;
define(ident: string, sym: Sym): Sym;
definedLocally(ident: string): boolean;
get(ident: string): { ok: true; sym: Sym } | { ok: false };
get(ident: string): GetRes;
getPub(ident: string): GetRes;
rootMod(): Sym;
pathString(): string;
}
export class GlobalSyms implements Syms {
export class EntryModSyms implements Syms {
private syms: SymMap = {};
public constructor() {}
public constructor(private modName: string) {}
public define(ident: string, sym: Sym) {
public define(ident: string, sym: Sym): Sym {
if (sym.type === "let") {
this.define(ident, {
return this.define(ident, {
...sym,
type: "let_static",
});
return;
}
this.syms[ident] = sym;
return sym;
}
public definedLocally(ident: string): boolean {
return ident in this.syms;
}
public get(ident: string): { ok: true; sym: Sym } | { ok: false } {
public get(ident: string): GetRes {
if (ident in this.syms) {
return { ok: true, sym: this.syms[ident] };
}
return { ok: false };
}
public getPub(ident: string): GetRes {
if (ident in this.syms) {
return { ok: true, sym: this.syms[ident] };
}
return { ok: false };
}
public rootMod(): Sym {
return {
type: "mod",
ident: this.modName,
fullPath: this.modName,
syms: this,
};
}
public pathString(): string {
return this.modName;
}
}
export class StaticSyms implements Syms {
export class ModSyms implements Syms {
private syms: SymMap = {};
public constructor(private parent: GlobalSyms) {}
public constructor(private parent: Syms, private modName: string) {
this.syms["super"] = {
type: "mod",
ident: "super",
fullPath: this.pathString(),
syms: this.parent,
};
}
public define(ident: string, sym: Sym) {
public define(ident: string, sym: Sym): Sym {
if (sym.type === "let") {
this.define(ident, {
return this.define(ident, {
...sym,
type: "let_static",
});
return;
}
this.syms[ident] = sym;
return this.syms[ident] = sym;
}
public definedLocally(ident: string): boolean {
return ident in this.syms;
}
public get(ident: string): { ok: true; sym: Sym } | { ok: false } {
public get(ident: string): GetRes {
if (ident in this.syms) {
return { ok: true, sym: this.syms[ident] };
}
return this.parent.get(ident);
return { ok: false };
}
public getPub(ident: string): GetRes {
if (ident in this.syms) {
return { ok: true, sym: this.syms[ident] };
}
return { ok: false };
}
public rootMod(): Sym {
return this.parent.rootMod();
}
public pathString(): string {
return `${this.parent.pathString()}::${this.modName}`;
}
}
@ -69,28 +115,43 @@ export class FnSyms implements Syms {
public constructor(private parent: Syms) {}
public define(ident: string, sym: Sym) {
public define(ident: string, sym: Sym): Sym {
if (sym.type === "let") {
this.define(ident, {
return this.define(ident, {
...sym,
type: "closure",
inner: sym,
});
return;
}
this.syms[ident] = sym;
return sym;
}
public definedLocally(ident: string): boolean {
return ident in this.syms;
}
public get(ident: string): { ok: true; sym: Sym } | { ok: false } {
public get(ident: string): GetRes {
if (ident in this.syms) {
return { ok: true, sym: this.syms[ident] };
}
return this.parent.get(ident);
}
public getPub(ident: string): GetRes {
if (ident in this.syms) {
return { ok: true, sym: this.syms[ident] };
}
return { ok: false };
}
public rootMod(): Sym {
return this.parent.rootMod();
}
public pathString(): string {
return this.parent.pathString();
}
}
export class LeafSyms implements Syms {
@ -98,18 +159,34 @@ export class LeafSyms implements Syms {
public constructor(private parent: Syms) {}
public define(ident: string, sym: Sym) {
public define(ident: string, sym: Sym): Sym {
this.syms[ident] = sym;
return sym;
}
public definedLocally(ident: string): boolean {
return ident in this.syms;
}
public get(ident: string): { ok: true; sym: Sym } | { ok: false } {
public get(ident: string): GetRes {
if (ident in this.syms) {
return { ok: true, sym: this.syms[ident] };
}
return this.parent.get(ident);
}
public getPub(ident: string): GetRes {
if (ident in this.syms) {
return { ok: true, sym: this.syms[ident] };
}
return { ok: false };
}
public rootMod(): Sym {
return this.parent.rootMod();
}
public pathString(): string {
return this.parent.pathString();
}
}

View File

@ -5,27 +5,82 @@ export type VType =
| { type: "int" }
| { type: "string" }
| { type: "bool" }
| { type: "array"; inner: VType }
| { type: "ref"; subject: VType }
| { type: "ref_mut"; subject: VType }
| { type: "ptr"; subject: VType }
| { type: "ptr_mut"; subject: VType }
| { type: "array"; subject: VType }
| { type: "struct"; fields: VTypeParam[] }
| { type: "fn"; params: VTypeParam[]; returnType: VType };
| {
type: "fn";
genericParams?: VTypeGenericParam[];
params: VTypeParam[];
returnType: VType;
stmtId: number;
}
| { type: "generic"; param: VTypeGenericParam }
| {
type: "generic_spec";
subject: VType;
genericArgs: GenericArgsMap;
};
export type VTypeParam = {
ident: string;
mut: boolean;
vtype: VType;
};
export function vtypesEqual(a: VType, b: VType): boolean {
if (a.type !== b.type) {
return false;
}
export type VTypeGenericParam = {
id: number;
ident: string;
};
export type GenericArgsMap = { [id: number]: VType };
export function vtypesEqual(
a: VType,
b: VType,
generics?: GenericArgsMap,
): boolean {
if (
["error", "unknown", "null", "int", "string", "bool"]
.includes(a.type)
.includes(a.type) && a.type === b.type
) {
return true;
}
if (a.type === "ref" && b.type === "ref") {
return vtypesEqual(a.subject, b.subject, generics);
}
if (a.type === "ref_mut" && b.type === "ref_mut") {
return vtypesEqual(a.subject, b.subject, generics);
}
if (a.type === "ptr" && b.type === "ptr") {
return vtypesEqual(a.subject, b.subject, generics);
}
if (a.type === "ptr_mut" && b.type === "ptr_mut") {
return vtypesEqual(a.subject, b.subject, generics);
}
if (a.type === "array" && b.type === "array") {
return vtypesEqual(a.inner, b.inner);
return vtypesEqual(a.subject, b.subject, generics);
}
if (a.type === "struct" && b.type === "struct") {
if (a.fields.length !== b.fields.length) {
return false;
}
const match = a.fields
.map((af) => ({
ident: af.ident,
af,
bf: b.fields.find((bf) => bf.ident === af.ident),
}));
if (match.some((m) => m.bf === undefined)) {
return false;
}
if (match.some((m) => !vtypesEqual(m.af.vtype, m.bf!.vtype))) {
return false;
}
return true;
}
if (a.type === "fn" && b.type === "fn") {
if (a.params.length !== b.params.length) {
@ -36,11 +91,41 @@ export function vtypesEqual(a: VType, b: VType): boolean {
return false;
}
}
return vtypesEqual(a.returnType, b.returnType);
return vtypesEqual(a.returnType, b.returnType, generics);
}
if (a.type === "generic" && b.type === "generic") {
return a.param.id === b.param.id;
}
if (
(a.type === "generic" || b.type === "generic") &&
generics !== undefined
) {
if (generics === undefined) {
throw new Error();
}
const generic = a.type === "generic" ? a : b;
const concrete = a.type === "generic" ? b : a;
const genericType = extractGenericType(generic, generics);
return vtypesEqual(genericType, concrete, generics);
}
return false;
}
export function extractGenericType(
generic: VType,
generics: GenericArgsMap,
): VType {
if (generic.type !== "generic") {
return generic;
}
if (!(generic.param.id in generics)) {
throw new Error("generic not found (not supposed to happen)");
}
return generics[generic.param.id];
}
export function vtypeToString(vtype: VType): string {
if (
["error", "unknown", "null", "int", "string", "bool"]
@ -48,15 +133,36 @@ export function vtypeToString(vtype: VType): string {
) {
return vtype.type;
}
if (vtype.type === "ref") {
return `&${vtypeToString(vtype.subject)}`;
}
if (vtype.type === "ref_mut") {
return `&mut ${vtypeToString(vtype.subject)}`;
}
if (vtype.type === "ptr") {
return `*${vtypeToString(vtype.subject)}`;
}
if (vtype.type === "ptr_mut") {
return `*mut ${vtypeToString(vtype.subject)}`;
}
if (vtype.type === "array") {
return `[${vtypeToString(vtype.inner)}]`;
return `[${vtypeToString(vtype.subject)}]`;
}
if (vtype.type === "struct") {
const fields = vtype.fields
.map((field) => `${field.ident}: ${vtypeToString(field.vtype)}`)
.join(", ");
return `struct { ${fields} }`;
}
if (vtype.type === "fn") {
const paramString = vtype.params.map((param) =>
`${param.ident}: ${vtypeToString(param.vtype)}`
)
.join(", ");
return `fn (${paramString}) -> ${vtypeToString(vtype.returnType)}`;
return `fn(${paramString}) -> ${vtypeToString(vtype.returnType)}`;
}
if (vtype.type === "generic") {
return `generic`;
}
throw new Error(`unhandled vtype '${vtype.type}'`);
}

View File

@ -7,7 +7,8 @@ if exists("b:current_syntax")
finish
endif
syn keyword Keyword break return let fn loop if else struct import or and not while for in
syn keyword Keyword break return let fn loop if else struct import or and not while for in mod pub
syn keyword Special null
syn keyword Type int string bool
syn keyword Boolean true false
@ -37,6 +38,9 @@ syn match Number '0[0-7]\+'
syn match Number '0x[0-9a-fA-F]\+'
syn match Number '0b[01]\+'
syn match Character "'[^\\]'"
syn match Character "'\\.'"
syn region String start=+"+ skip=+\\"+ end=+"+
syn keyword Todo contained TODO FIXME XXX NOTE
@ -44,11 +48,23 @@ syn match Comment "//.*$" contains=Todo
syn region Comment start=+/\*+ end=+\*/+ contains=Todo
syn match Identifier '[a-z_]\w*'
syn match Type '[A-Z]\w*'
syn match Function '[a-zA-Z_]\w*\ze('
syn match Function '[a-zA-Z_]\w*\ze\s\{-}(.\{-})'
syn match sligePath '[a-zA-Z_]\w*\ze\s\{-}::'
syn match Function '[a-zA-Z_]\w*\ze\s\{-}::<.\{-}>'
syn match Function ' \zs[a-zA-Z_]\w*\ze\s\{-}<.\{-}>\s\{-}(.\{-})'
syn region sligeBlock start="{" end="}" transparent fold
syn region sligeAnno start="#!\?\[" end="]" contains=Identifier,Type
hi def link sligeAnno PreProc
hi def link sligePath Include
let b:current_syntax = "slige"

View File

@ -53,6 +53,17 @@
}
]
},
"chars": {
"name": "string.quoted.double.slige",
"begin": "'",
"end": "'",
"patterns": [
{
"name": "constant.character.escape.slige",
"match": "\\\\."
}
]
},
"numbers": {
"patterns": [
{

View File

@ -1,4 +1,7 @@
fn print(msg: string) #[builtin(print)] {
//
#[builtin(Print)]
fn print(msg: string) {
"hello" + 0
}

View File

@ -4,7 +4,14 @@ fn add(a: int, b: int) -> int {
fn main() -> int {
let result = 0;
let i = 0;
let a = 0;
let b = a;
let c = b;
let d = c;
let i = c;
loop {
if i >= 10 {
break;

View 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))
}
}

View File

@ -56,12 +56,50 @@ fn input(prompt: string) -> string {
//
fn is_prime(n: int) -> bool {
if n == 1 or n == 0{
return false;
fn min(a: int, b: int) -> int {
if b < a { b } else { a }
}
fn max(a: int, b: int) -> int {
if a < b { b } else { b }
}
fn sqrt(n: int) -> int {
let low = min(1, n);
let high = max(1, n);
let mid = 0;
while 100 * low * low < n {
low = low * 10;
}
for (let i = 2; i < n; i += 1) {
while (high * high) / 100 > n {
high = high / 10;
}
for (let i = 0; i < 100; i += 1) {
mid = (low + high) / 2;
if mid * mid == n {
return mid;
}
if mid * mid > n {
high = mid;
} else {
low = mid;
}
}
mid
}
fn is_prime(n: int) -> bool {
if n == 0{
return false;
}
if n == 1 {
return true;
}
let n_root = sqrt(n);
for (let i = 2; i < n_root; i += 1) {
if remainder(n, i) == 0 {
return false;
}
@ -70,7 +108,7 @@ fn is_prime(n: int) -> bool {
}
fn main() {
for (let i = 1; i < 10000; i += 1) {
for (let i = 1; i <= 10000; i += 1) {
if is_prime(i) {
print(int_to_string(i) + " ");
}

7
examples/refs.slg Normal file
View File

@ -0,0 +1,7 @@
//
fn main() {
let a = 5;
let b: &int = &a;
}

View File

@ -1,56 +1,3 @@
fn string_push_char(str: string, value: int) -> string #[builtin(StringPushChar)] {}
fn string_char_at(str: string, index: int) -> int #[builtin(StringCharAt)] {}
fn string_length(str: string) -> int #[builtin(StringLength)] {}
fn string_array_new() -> [string] #[builtin(ArrayNew)] {}
fn string_array_push(array: [string], value: string) #[builtin(ArrayPush)] {}
fn string_array_length(array: [string]) -> int #[builtin(ArrayLength)] {}
fn string_array_at(array: [string], index: int) -> string #[builtin(ArrayAt)] {}
fn int_array_new() -> [int] #[builtin(ArrayNew)] {}
fn int_array_push(array: [int], value: int) #[builtin(ArrayPush)] {}
fn int_array_length(array: [int]) -> int #[builtin(ArrayLength)] {}
fn int_array_at(array: [int], index: int) -> int #[builtin(ArrayAt)] {}
fn file_open(filename: string, mode: string) -> int #[builtin(FileOpen)] {}
fn file_close(file: int) #[builtin(FileClose)] {}
fn file_write_string(file: int, content: string) -> int #[builtin(FileWriteString)] {}
fn file_read_char(file: int) -> int #[builtin(FileReadChar)] {}
fn file_read_to_string(file: int) -> string #[builtin(FileReadToString)] {}
fn file_flush(file: int) #[builtin(FileFlush)] {}
fn file_eof(file: int) -> bool #[builtin(FileEof)] {}
fn stdin() -> int { 0 }
fn stdout() -> int { 1 }
fn stderr() -> int { 2 }
fn file_read_line(file: int) -> string {
let line = "";
loop {
if file_eof(file) {
break;
}
let ch = file_read_char(file);
if ch == "\n"[0] {
break;
}
line = string_push_char(line, ch);
}
line
}
fn print(msg: string) #[builtin(Print)] {}
fn println(msg: string) { print(msg + "\n") }
fn input(prompt: string) -> string {
print("> ");
file_flush(stdout());
file_read_line(stdin())
}
//
fn main() {
let i = 0;
while i < 3 {
@ -63,18 +10,34 @@ fn main() {
for char in chars {
println(string_push_char("", char));
}
let values = array_new::<int>();
array_push(values, 10);
array_push(values, 20);
array_push(values, 30);
let pairs = array_new::<[int]>();
for (let i = 0; i < array_length(values); i += 1) {
let pair = array_new::<int>();
array_push(pair, i);
array_push(pair, values[i]);
array_push(pairs, pair);
}
for pair in pairs {
println("values[" + itos(pair[0]) + "] = " + itos(pair[1]));
}
}
fn string_to_array(value: string) -> [int] {
let result = int_array_new();
let result = array_new::<int>();
let length = string_length(value);
for (let i = 0; i < length; i += 1) {
int_array_push(result, value[i]);
array_push(result, value[i]);
}
result
}

View 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);
}

View File

@ -0,0 +1,6 @@
// mod std;
fn main() {
let a = 5;
let b = a;
}

View File

@ -1,6 +1,7 @@
#include "alloc.hpp"
#include <format>
#include <iostream>
#include <string>
using namespace sliger::heap;
@ -14,3 +15,18 @@ auto Array::at(int32_t index) & -> Value&
}
return values.at(static_cast<size_t>(index));
}
auto Struct::at(const std::string& field) & -> Value&
{
if (this->fields.find(field) == this->fields.end()) {
std::cout << std::format(
"field name not in struct, got: \"{}\"\n", field);
exit(1);
}
return this->fields.at(field);
}
void Struct::assign(const std::string& field, Value&& value)
{
this->fields.insert_or_assign(field, value);
}

View File

@ -19,6 +19,9 @@ struct Array {
struct Struct {
std::unordered_map<std::string, Value> fields;
auto at(const std::string&) & -> Value&;
void assign(const std::string&, Value&& value);
};
enum class AllocType {
@ -202,7 +205,7 @@ private:
}
}
size_t max_size = 4;
size_t max_size = 512;
std::vector<AllocItem> heap_1;
std::vector<AllocItem> heap_2;

View File

@ -41,7 +41,8 @@ enum class Op : uint32_t {
};
enum class Builtin : uint32_t {
IntToString = 0x00,
Exit = 0x00,
IntToString = 0x01,
StringConcat = 0x10,
StringEqual = 0x11,
StringCharAt = 0x12,
@ -53,7 +54,9 @@ enum class Builtin : uint32_t {
ArrayPush = 0x22,
ArrayAt = 0x23,
ArrayLength = 0x24,
StructSet = 0x30,
StructNew = 0x30,
StructSet = 0x31,
StructAt = 0x32,
Print = 0x40,
FileOpen = 0x41,
FileClose = 0x42,

View File

@ -289,6 +289,11 @@ void VM::run_instruction()
this->current_pos = { index, line, col };
break;
}
default:
std::cerr << std::format("unrecognized instruction '{}', pc = {}",
std::to_underlying(op), this->pc);
std::exit(1);
break;
}
this->instruction_counter += 1;
}
@ -300,9 +305,15 @@ void VM::run_builtin(Builtin builtin_id)
maybe_builtin_to_string(static_cast<uint32_t>(builtin_id)));
}
switch (builtin_id) {
case Builtin::Exit: {
assert_stack_has(1);
auto status_code = stack_pop().as_int().value;
std::exit(status_code);
break;
}
case Builtin::IntToString: {
assert_stack_has(1);
auto number = static_cast<int32_t>(stack_pop().as_int().value);
auto number = stack_pop().as_int().value;
auto str = std::to_string(number);
stack_push(String(str));
break;
@ -325,12 +336,11 @@ void VM::run_builtin(Builtin builtin_id)
run_array_builtin(builtin_id);
break;
case Builtin::StructSet: {
assert_stack_has(2);
std::cerr << std::format("not implemented\n");
std::exit(1);
case Builtin::StructNew:
case Builtin::StructSet:
case Builtin::StructAt:
run_struct_builtin(builtin_id);
break;
}
case Builtin::Print:
case Builtin::FileOpen:
@ -401,6 +411,7 @@ void VM::run_string_builtin(Builtin builtin_id)
break;
}
}
void VM::run_array_builtin(Builtin builtin_id)
{
switch (builtin_id) {
@ -450,6 +461,40 @@ void VM::run_array_builtin(Builtin builtin_id)
}
}
void VM::run_struct_builtin(Builtin builtin_id)
{
switch (builtin_id) {
case Builtin::StructNew: {
auto alloc_res = this->heap.alloc<heap::AllocType::Struct>();
stack_push(Ptr(alloc_res.val()));
break;
}
case Builtin::StructSet: {
assert_stack_has(2);
auto field = stack_pop().as_string().value;
auto struct_ptr = stack_pop().as_ptr().value;
auto value = stack_pop();
this->heap.at(struct_ptr)
.val()
->as_struct()
.assign(field, std::move(value));
stack_push(Null());
break;
}
case Builtin::StructAt: {
assert_stack_has(2);
auto field = stack_pop().as_string().value;
auto struct_ptr = stack_pop().as_ptr().value;
stack_push(this->heap.at(struct_ptr).val()->as_struct().at(field));
break;
}
default:
break;
}
}
void VM::run_file_builtin(Builtin builtin_id)
{
switch (builtin_id) {

View File

@ -199,6 +199,7 @@ private:
void run_builtin(Builtin builtin_id);
void run_string_builtin(Builtin builtin_id);
void run_array_builtin(Builtin builtin_id);
void run_struct_builtin(Builtin builtin_id);
void run_file_builtin(Builtin builtin_id);
inline void step() { this->pc += 1; }

View File

@ -3,11 +3,17 @@
set -e
echo Text:
cat $1
if command -v pygmentize 2>&1 >/dev/null
then
pygmentize -l rust -Ostyle="gruvbox-dark",linenos=1 $1
else
cat $1
fi
echo Compiling $1...
deno run --allow-read --allow-write compiler/main.ts $1
deno run --allow-read --allow-write --check compiler/main.ts $1
echo Running out.slgbc...

184
std/lib.slg Normal file
View 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);
}
}

View File

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

View 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);
}

View File

@ -0,0 +1,5 @@
fn inner_fn(a: int) -> int {
a + 32
}

25
tests/modules.slg Normal file
View 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
View 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");
}