Compare commits

..

No commits in common. "main" and "teknikfagseksamen" have entirely different histories.

81 changed files with 1742 additions and 6554 deletions

View File

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

34
compiler/architecture.txt Normal file
View File

@ -0,0 +1,34 @@
Mnemonic Arg Arg description
----------------------------------------
Nop
PushNull
PushInt int initial value
PushString string initial value
PushArray
PushStruct
PushPtr ptr pointer value
Pop
LoadLocal int stack position
StoreLocal int stack position
Call int arg count
Return
Jump
JumpIfNotZero
Add
Subtract
Multiply
Divide
Remainder
Equal
LessThan
And
Or
Xor
Not

147
compiler/ast.ts Normal file
View File

@ -0,0 +1,147 @@
import { Pos } from "./token.ts";
import { VType } from "./vtype.ts";
export type Stmt = {
kind: StmtKind;
pos: Pos;
id: number;
};
export type StmtKind =
| { type: "error" }
| { type: "import"; path: Expr }
| { type: "break"; expr?: Expr }
| { type: "return"; expr?: Expr }
| {
type: "fn";
ident: string;
params: Param[];
returnType?: EType;
body: Expr;
anno?: Anno;
vtype?: VType;
}
| { type: "let"; param: Param; value: Expr }
| { type: "assign"; assignType: AssignType; subject: Expr; value: Expr }
| { type: "expr"; expr: Expr };
export type AssignType = "=" | "+=" | "-=";
export type Expr = {
kind: ExprKind;
pos: Pos;
vtype?: VType;
id: number;
};
export type ExprKind =
| { type: "error" }
| { type: "int"; value: number }
| { type: "string"; value: string }
| { type: "ident"; value: string }
| { type: "group"; expr: Expr }
| { type: "field"; subject: Expr; value: string }
| { type: "index"; subject: Expr; value: Expr }
| { type: "call"; subject: Expr; args: Expr[] }
| { type: "unary"; unaryType: UnaryType; subject: Expr }
| { type: "binary"; binaryType: BinaryType; left: Expr; right: Expr }
| { type: "if"; cond: Expr; truthy: Expr; falsy?: Expr; elsePos?: Pos }
| { type: "bool"; value: boolean }
| { 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 }
| {
type: "for";
decl?: Stmt;
cond?: Expr;
incr?: Stmt;
body: Expr;
};
export type UnaryType = "not" | "-";
export type BinaryType =
| "+"
| "*"
| "=="
| "-"
| "/"
| "!="
| "<"
| ">"
| "<="
| ">="
| "or"
| "and";
export type Param = {
ident: string;
etype?: EType;
pos: Pos;
vtype?: VType;
};
export type Sym = {
ident: string;
pos?: Pos;
} & SymKind;
export type SymKind =
| { type: "let"; stmt: Stmt; param: Param }
| { type: "let_static"; stmt: Stmt; param: Param }
| { type: "fn"; stmt: Stmt }
| { type: "fn_param"; param: Param }
| { type: "closure"; inner: Sym }
| { type: "builtin"; builtinId: number };
export type EType = {
kind: ETypeKind;
pos: Pos;
id: number;
};
export type ETypeKind =
| { type: "error" }
| { type: "ident"; value: string }
| { type: "array"; inner: EType }
| { type: "struct"; fields: Param[] };
export type ETypeParam = {
ident: string;
pos: Pos;
vtype?: VType;
};
export type Anno = {
ident: string;
values: 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 expr(kind: ExprKind, pos: Pos): Expr {
const id = this.nextNodeId;
this.nextNodeId += 1;
return { kind, pos, id };
}
public etype(kind: ETypeKind, pos: Pos): EType {
const id = this.nextNodeId;
this.nextNodeId += 1;
return { kind, pos, id };
}
}

View File

@ -1,153 +0,0 @@
import { Span } from "../diagnostics.ts";
export type File = {
stmts: Stmt[];
};
export type Stmt = {
kind: StmtKind;
span: Span;
};
export type StmtKind =
| { tag: "error" }
| { tag: "item" } & ItemStmt
| { tag: "let" } & LetStmt
| { tag: "return" } & ReturnStmt
| { tag: "break" } & BreakStmt
| { tag: "assign" } & AssignStmt
| { tag: "expr" } & ExprStmt;
export type ItemStmt = { item: Item };
export type LetStmt = {
subject: Pat;
expr?: Expr;
};
export type ReturnStmt = { expr?: Expr };
export type BreakStmt = { expr?: Expr };
export type AssignStmt = {
assignType: AssignType;
subject: Expr;
value: Expr;
};
export type AssignType = "=" | "+=" | "-=";
export type ExprStmt = { expr: Expr };
export type Item = {
kind: ItemKind;
span: Span;
ident: Ident;
pub: boolean;
};
export type ItemKind =
| { tag: "error" }
| { tag: "mod_block" } & ModBlockItem
| { tag: "mod_file" } & ModFileItem
| { tag: "enum" } & EnumItem
| { tag: "struct" } & StructItem
| { tag: "fn" } & FnItem
| { tag: "use" } & UseItem
| { tag: "static" } & StaticItem
| { tag: "type_alias" } & TypeAliasItem;
export type ModBlockItem = {
ident: Ident;
stmts: Stmt[];
};
export type ModFileItem = {
ident: Ident;
filePath: string;
};
export type EnumItem = { variants: Variant[] };
export type StructItem = { data: VariantData };
export type FnItem = { _: 0 };
export type UseItem = { _: 0 };
export type StaticItem = { _: 0 };
export type TypeAliasItem = { _: 0 };
export type Variant = {
ident: Ident;
data: VariantData;
pub: boolean;
span: Span;
};
export type VariantData = {
kind: VariantDataKind;
span: Span;
};
export type VariantDataKind =
| { tag: "error" }
| { tag: "unit" }
| { tag: "tuple" } & TupleVariantData
| { tag: "struct" } & StructVariantData;
export type TupleVariantData = { elems: VariantData[] };
export type StructVariantData = { fields: FieldDef[] };
export type FieldDef = {
ident: Ident;
ty: Ty;
pub: boolean;
span: Span;
};
export type Expr = {
kind: ExprKind;
span: Span;
};
export type ExprKind =
| { tag: "error" }
| { tag: "ident" } & Ident;
export type Pat = {
kind: PatKind;
span: Span;
};
export type PatKind =
| { tag: "error" }
| { tag: "bind" } & BindPat;
export type BindPat = {
ident: Ident;
mut: boolean;
};
export type Ty = {
kind: TyKind;
span: Span;
};
export type TyKind =
| { tag: "error" }
| { tag: "ident" } & Ident
| { tag: "ref" } & RefTy
| { tag: "ptr" } & PtrTy
| { tag: "slice" } & SliceTy
| { tag: "array" } & ArrayTy;
export type RefTy = { ty: Ty; mut: boolean };
export type PtrTy = { ty: Ty; mut: boolean };
export type SliceTy = { ty: Ty };
export type ArrayTy = { ty: Ty; length: Expr };
export type TupleTy = { elems: Ty[] };
export type StructTy = { fields: FieldDef[] };
export type AnonFieldDef = {
ident: Ident;
ty: Ty;
span: Span;
};
export type Ident = { text: string; span: Span };

View File

@ -1,2 +0,0 @@
export * from "./ast.ts";
export * from "./visitor.ts";

View File

@ -1,236 +0,0 @@
import { exhausted } from "../util.ts";
import {
ArrayTy,
AssignStmt,
BindPat,
BreakStmt,
EnumItem,
Expr,
ExprStmt,
File,
FnItem,
Ident,
Item,
ItemStmt,
LetStmt,
ModBlockItem,
ModFileItem,
Pat,
PtrTy,
RefTy,
ReturnStmt,
SliceTy,
StaticItem,
Stmt,
StructItem,
Ty,
TypeAliasItem,
UseItem,
} from "./ast.ts";
export type VisitRes = "stop" | void;
type R = VisitRes;
type PM = unknown[];
export interface Visitor<
P extends PM = [],
> {
visitFile?(file: File, ...p: P): R;
visitStmt?(stmt: Stmt, ...p: P): R;
visitErrorStmt?(stmt: Stmt, ...p: P): R;
visitItemStmt?(stmt: Stmt, kind: ItemStmt, ...p: P): R;
visitLetStmt?(stmt: Stmt, kind: LetStmt, ...p: P): R;
visitReturnStmt?(stmt: Stmt, kind: ReturnStmt, ...p: P): R;
visitBreakStmt?(stmt: Stmt, kind: BreakStmt, ...p: P): R;
visitAssignStmt?(stmt: Stmt, kind: AssignStmt, ...p: P): R;
visitExprStmt?(stmt: Stmt, kind: ExprStmt, ...p: P): R;
visitItem?(item: Item, ...p: P): R;
visitErrorItem?(item: Item, ...p: P): R;
visitModBlockItem?(item: Item, kind: ModBlockItem, ...p: P): R;
visitModFileItem?(item: Item, kind: ModFileItem, ...p: P): R;
visitEnumItem?(item: Item, kind: EnumItem, ...p: P): R;
visitStructItem?(item: Item, kind: StructItem, ...p: P): R;
visitFnItem?(item: Item, kind: FnItem, ...p: P): R;
visitUseItem?(item: Item, kind: UseItem, ...p: P): R;
visitStaticItem?(item: Item, kind: StaticItem, ...p: P): R;
visitTypeAliasItem?(item: Item, kind: TypeAliasItem, ...p: P): R;
visitExpr?(expr: Expr, ...p: P): R;
visitErrorExpr?(expr: Expr, ...p: P): R;
visitIdentExpr?(expr: Expr, kind: Ident, ...p: P): R;
visitPat?(pat: Pat, ...p: P): R;
visitErrorPat?(pat: Pat, ...p: P): R;
visitBindPat?(pat: Pat, kind: BindPat, ...p: P): R;
visitTy?(ty: Ty, ...p: P): R;
visitErrorTy?(ty: Ty, ...p: P): R;
visitIdentTy?(ty: Ty, kind: Ident, ...p: P): R;
visitRefTy?(ty: Ty, kind: RefTy, ...p: P): R;
visitPtrTy?(ty: Ty, kind: PtrTy, ...p: P): R;
visitSliceTy?(ty: Ty, kind: SliceTy, ...p: P): R;
visitArrayTy?(ty: Ty, kind: ArrayTy, ...p: P): R;
visitIdent?(ident: Ident, ...p: P): R;
}
export function visitFile<
P extends PM = [],
>(
v: Visitor<P>,
file: File,
...p: P
) {
if (v.visitFile?.(file, ...p) === "stop") return;
visitStmts(v, file.stmts, ...p);
}
export function visitStmts<
P extends PM = [],
>(
v: Visitor<P>,
stmts: Stmt[],
...p: P
) {
for (const stmt of stmts) {
visitStmt(v, stmt, ...p);
}
}
export function visitStmt<
P extends PM = [],
>(
v: Visitor<P>,
stmt: Stmt,
...p: P
) {
const kind = stmt.kind;
switch (kind.tag) {
case "error":
if (v.visitErrorStmt?.(stmt, ...p) === "stop") return;
return;
case "item":
if (v.visitItemStmt?.(stmt, kind, ...p) === "stop") return;
return;
case "let":
if (v.visitLetStmt?.(stmt, kind, ...p) === "stop") return;
return;
case "return":
if (v.visitReturnStmt?.(stmt, kind, ...p) === "stop") return;
return;
case "break":
if (v.visitBreakStmt?.(stmt, kind, ...p) === "stop") return;
return;
case "assign":
if (v.visitAssignStmt?.(stmt, kind, ...p) === "stop") return;
return;
case "expr":
if (v.visitExprStmt?.(stmt, kind, ...p) === "stop") return;
return;
}
exhausted(kind);
}
export function visitItem<
P extends PM = [],
>(
v: Visitor<P>,
item: Item,
...p: P
) {
const kind = item.kind;
switch (kind.tag) {
case "error":
if (v.visitErrorItem?.(item, ...p) === "stop") return;
return;
case "mod_block":
if (v.visitModBlockItem?.(item, kind, ...p) === "stop") return;
return;
case "mod_file":
if (v.visitModFileItem?.(item, kind, ...p) === "stop") return;
return;
case "enum":
if (v.visitEnumItem?.(item, kind, ...p) === "stop") return;
return;
case "struct":
if (v.visitStructItem?.(item, kind, ...p) === "stop") return;
return;
case "fn":
if (v.visitFnItem?.(item, kind, ...p) === "stop") return;
return;
case "use":
if (v.visitUseItem?.(item, kind, ...p) === "stop") return;
return;
case "static":
if (v.visitStaticItem?.(item, kind, ...p) === "stop") return;
return;
case "type_alias":
if (v.visitTypeAliasItem?.(item, kind, ...p) === "stop") return;
return;
}
exhausted(kind);
}
export function visitExpr<
P extends PM = [],
>(
v: Visitor<P>,
expr: Expr,
...p: P
) {
const kind = expr.kind;
switch (kind.tag) {
case "error":
if (v.visitErrorExpr?.(expr, ...p) === "stop") return;
return;
case "ident":
if (v.visitIdentExpr?.(expr, kind, ...p) === "stop") return;
return;
}
exhausted(kind);
}
export function visitPat<
P extends PM = [],
>(
v: Visitor<P>,
pat: Pat,
...p: P
) {
const kind = pat.kind;
switch (kind.tag) {
case "error":
if (v.visitErrorPat?.(pat, ...p) === "stop") return;
return;
case "bind":
if (v.visitBindPat?.(pat, kind, ...p) === "stop") return;
return;
}
exhausted(kind);
}
export function visitTy<
P extends PM = [],
>(
v: Visitor<P>,
ty: Ty,
...p: P
) {
const kind = ty.kind;
switch (kind.tag) {
case "error":
if (v.visitErrorTy?.(ty, ...p) === "stop") return;
return;
case "ident":
if (v.visitIdentTy?.(ty, kind, ...p) === "stop") return;
return;
}
exhausted(kind);
}
export function visitIdent<
P extends PM = [],
>(
v: Visitor<P>,
ident: Ident,
...p: P
) {
v.visitIdent?.(ident, ...p);
}

View File

@ -1,4 +1,4 @@
import { EType, Expr, Field, Param, Stmt } from "./ast.ts";
import { EType, Expr, Param, Stmt } from "./ast.ts";
export type VisitRes = "stop" | void;
@ -6,14 +6,11 @@ export interface AstVisitor<Args extends unknown[] = []> {
visitStmts?(stmts: Stmt[], ...args: Args): VisitRes;
visitStmt?(stmt: Stmt, ...args: Args): VisitRes;
visitErrorStmt?(stmt: Stmt, ...args: Args): VisitRes;
visitModFileStmt?(stmt: Stmt, ...args: Args): VisitRes;
visitModBlockStmt?(stmt: Stmt, ...args: Args): VisitRes;
visitModStmt?(stmt: Stmt, ...args: Args): VisitRes;
visitImportStmt?(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;
@ -22,16 +19,9 @@ 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;
@ -44,22 +34,11 @@ 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;
}
@ -82,16 +61,9 @@ export function visitStmt<Args extends unknown[] = []>(
case "error":
if (v.visitErrorStmt?.(stmt, ...args) == "stop") return;
break;
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);
case "import":
if (v.visitImportStmt?.(stmt, ...args) == "stop") return;
visitExpr(stmt.kind.path, v, ...args);
break;
case "break":
if (v.visitBreakStmt?.(stmt, ...args) == "stop") return;
@ -114,10 +86,6 @@ 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);
@ -127,12 +95,6 @@ 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`,
);
}
}
@ -159,18 +121,6 @@ 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);
@ -185,15 +135,6 @@ 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);
@ -203,14 +144,6 @@ 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);
@ -253,12 +186,6 @@ 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`,
);
}
}
@ -271,15 +198,6 @@ 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>,
@ -290,58 +208,17 @@ 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;
visitEType(etype.kind.subject, v, ...args);
if (etype.kind.inner) visitEType(etype.kind.inner, 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`,
);
}
}

663
compiler/checker.ts Normal file
View File

@ -0,0 +1,663 @@
import { EType, Expr, Stmt } from "./ast.ts";
import { printStackTrace, Reporter } from "./info.ts";
import { Pos } from "./token.ts";
import { VType, VTypeParam, vtypesEqual, vtypeToString } from "./vtype.ts";
export class Checker {
private fnReturnStack: VType[] = [];
private loopBreakStack: VType[][] = [];
public constructor(private reporter: Reporter) {}
public check(stmts: Stmt[]) {
this.checkFnHeaders(stmts);
for (const stmt of stmts) {
this.checkStmt(stmt);
}
}
private checkFnHeaders(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" };
}
const vtype = this.checkEType(param.etype!);
param.vtype = vtype;
params.push({ ident: param.ident, vtype });
}
stmt.kind.vtype = { type: "fn", params, returnType };
}
}
public checkStmt(stmt: Stmt) {
switch (stmt.kind.type) {
case "error":
return { type: "error" };
case "break":
return this.checkBreakStmt(stmt);
case "return":
return this.checkReturnStmt(stmt);
case "fn":
return this.checkFnStmt(stmt);
case "let":
return this.checkLetStmt(stmt);
case "assign":
return this.checkAssignStmt(stmt);
case "expr":
return this.checkExpr(stmt.kind.expr);
}
}
public checkBreakStmt(stmt: Stmt) {
if (stmt.kind.type !== "break") {
throw new Error();
}
const pos = stmt.pos;
if (this.loopBreakStack.length === 0) {
this.report("cannot break outside loop context", pos);
return;
}
const exprType: VType = stmt.kind.expr
? this.checkExpr(stmt.kind.expr)
: { type: "null" };
const breakTypes = this.loopBreakStack.at(-1)!;
if (breakTypes.length === 0) {
breakTypes.push(exprType);
return;
}
const prevBreakType = breakTypes.at(-1)!;
if (!vtypesEqual(prevBreakType, exprType)) {
this.report(
`incompatible types for break` +
`, got ${exprType}` +
` incompatible with ${prevBreakType}`,
pos,
);
return;
}
breakTypes.push(exprType);
}
public checkReturnStmt(stmt: Stmt) {
if (stmt.kind.type !== "return") {
throw new Error();
}
const pos = stmt.pos;
if (this.fnReturnStack.length === 0) {
this.report("cannot return outside fn context", pos);
return;
}
const exprType: VType = stmt.kind.expr
? this.checkExpr(stmt.kind.expr)
: { type: "null" };
const returnType = this.fnReturnStack.at(-1)!;
if (!vtypesEqual(exprType, returnType)) {
this.report(
`incompatible return type` +
`, got ${exprType}` +
`, expected ${returnType}`,
pos,
);
}
}
public checkFnStmt(stmt: Stmt) {
if (stmt.kind.type !== "fn") {
throw new Error();
}
const pos = stmt.pos;
if (stmt.kind.vtype!.type !== "fn") {
throw new Error();
}
if (
stmt.kind.anno?.ident === "remainder" ||
stmt.kind.anno?.ident === "builtin"
) {
return;
}
const { returnType } = stmt.kind.vtype!;
this.fnReturnStack.push(returnType);
const body = this.checkExpr(stmt.kind.body);
this.fnReturnStack.pop();
if (!vtypesEqual(returnType, body)) {
this.report(
`incompatible return type` +
`, expected '${vtypeToString(returnType)}'` +
`, got '${vtypeToString(body)}'`,
pos,
);
}
}
public checkLetStmt(stmt: Stmt) {
if (stmt.kind.type !== "let") {
throw new Error();
}
const pos = stmt.pos;
const value = this.checkExpr(stmt.kind.value);
if (stmt.kind.param.etype) {
const paramVtype = this.checkEType(stmt.kind.param.etype);
if (!vtypesEqual(value, paramVtype)) {
this.report(
`incompatible value type` +
`, got '${vtypeToString(value)}'` +
`, expected '${vtypeToString(paramVtype)}'`,
pos,
);
return;
}
}
stmt.kind.param.vtype = value;
}
public checkAssignStmt(stmt: Stmt) {
if (stmt.kind.type !== "assign") {
throw new Error();
}
const pos = stmt.pos;
if (stmt.kind.assignType !== "=") {
throw new Error("invalid ast: compound assign should be desugered");
}
const value = this.checkExpr(stmt.kind.value);
switch (stmt.kind.subject.kind.type) {
case "field": {
const subject = this.checkExpr(stmt.kind.subject.kind.subject);
if (subject.type !== "struct") {
this.report("cannot use field on non-struct", pos);
return { type: "error" };
}
const fieldValue = stmt.kind.subject.kind.value;
const found = subject.fields.find((param) =>
param.ident === fieldValue
);
if (!found) {
this.report(
`no field named '${stmt.kind.subject.kind.value}' on struct`,
pos,
);
return { type: "error" };
}
if (!vtypesEqual(found.vtype, value)) {
this.report(
`cannot assign incompatible type to field '${found.ident}'` +
`, got '${vtypeToString(value)}'` +
`, expected '${vtypeToString(found.vtype)}'`,
pos,
);
return;
}
return;
}
case "index": {
const subject = this.checkExpr(stmt.kind.subject.kind.subject);
if (subject.type !== "array" && subject.type !== "string") {
this.report(
`cannot index on non-array, got: ${subject.type}`,
pos,
);
return { type: "error" };
}
const indexValue = this.checkExpr(stmt.kind.subject.kind.value);
if (indexValue.type !== "int") {
this.report("cannot index on array with non-int", pos);
return { type: "error" };
}
if (
subject.type == "array" &&
!vtypesEqual(subject.inner, value)
) {
this.report(
`cannot assign incompatible type to array ` +
`'${vtypeToString(subject)}'` +
`, got '${vtypeToString(value)}'`,
pos,
);
return;
}
return;
}
case "sym": {
if (stmt.kind.subject.kind.sym.type !== "let") {
this.report("cannot only assign to let-symbol", pos);
return { type: "error" };
}
if (
!vtypesEqual(stmt.kind.subject.kind.sym.param.vtype!, value)
) {
this.report(
`cannot assign to incompatible type` +
`, got '${vtypeToString(value)}'` +
`, expected '${
vtypeToString(
stmt.kind.subject.kind.sym.param.vtype!,
)
}'`,
pos,
);
return;
}
return;
}
default:
this.report("unassignable expression", pos);
return;
}
}
public checkExpr(expr: Expr): VType {
const vtype = ((): VType => {
switch (expr.kind.type) {
case "error":
throw new Error("error in AST");
case "ident":
throw new Error("ident expr in AST");
case "sym":
return this.checkSymExpr(expr);
case "null":
return { type: "null" };
case "int":
return { type: "int" };
case "bool":
return { type: "bool" };
case "string":
return { type: "string" };
case "group":
return this.checkExpr(expr.kind.expr);
case "field":
return this.checkFieldExpr(expr);
case "index":
return this.checkIndexExpr(expr);
case "call":
return this.checkCallExpr(expr);
case "unary":
return this.checkUnaryExpr(expr);
case "binary":
return this.checkBinaryExpr(expr);
case "if":
return this.checkIfExpr(expr);
case "loop":
return this.checkLoopExpr(expr);
case "while":
case "for_in":
case "for":
throw new Error(
"invalid ast: special loops should be desugered",
);
case "block":
return this.checkBlockExpr(expr);
}
// throw new Error(`unhandled type ${expr.kind.type}`);
})();
return expr.vtype = vtype;
}
public checkSymExpr(expr: Expr): VType {
if (expr.kind.type !== "sym") {
throw new Error();
}
switch (expr.kind.sym.type) {
case "let":
return expr.kind.sym.param.vtype!;
case "fn": {
const fnStmt = expr.kind.sym.stmt!;
if (fnStmt.kind.type !== "fn") {
throw new Error();
}
const vtype = fnStmt.kind.vtype!;
if (vtype.type !== "fn") {
throw new Error();
}
const { params, returnType } = vtype;
return { type: "fn", params, returnType };
}
case "fn_param":
return expr.kind.sym.param.vtype!;
case "builtin":
case "let_static":
case "closure":
throw new Error(
`not implemented, sym type '${expr.kind.sym.type}'`,
);
}
}
public checkFieldExpr(expr: Expr): VType {
if (expr.kind.type !== "field") {
throw new Error();
}
const pos = expr.pos;
const subject = this.checkExpr(expr.kind.subject);
if (subject.type !== "struct") {
this.report("cannot use field on non-struct", pos);
return { type: "error" };
}
const value = expr.kind.value;
const found = subject.fields.find((param) => param.ident === value);
if (!found) {
this.report(
`no field named '${expr.kind.value}' on struct`,
pos,
);
return { type: "error" };
}
return found.vtype;
}
public checkIndexExpr(expr: Expr): VType {
if (expr.kind.type !== "index") {
throw new Error();
}
const pos = expr.pos;
const subject = this.checkExpr(expr.kind.subject);
if (subject.type !== "array" && subject.type !== "string") {
this.report(`cannot index on non-array, got: ${subject.type}`, pos);
return { type: "error" };
}
const value = this.checkExpr(expr.kind.value);
if (value.type !== "int") {
this.report("cannot index on array with non-int", pos);
return { type: "error" };
}
if (subject.type === "array") {
return subject.inner;
}
return { type: "int" };
}
public checkCallExpr(expr: Expr): VType {
if (expr.kind.type !== "call") {
throw new Error();
}
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}`,
pos,
);
}
for (let i = 0; i < args.length; ++i) {
if (!vtypesEqual(args[i], subject.params[i].vtype)) {
this.report(
`incorrect argument ${i} '${subject.params[i].ident}'` +
`, expected ${vtypeToString(subject.params[i].vtype)}` +
`, got ${vtypeToString(args[i])}`,
pos,
);
break;
}
}
return subject.returnType;
}
public checkUnaryExpr(expr: Expr): VType {
if (expr.kind.type !== "unary") {
throw new Error();
}
const pos = expr.pos;
const subject = this.checkExpr(expr.kind.subject);
for (const operation of simpleUnaryOperations) {
if (operation.unaryType !== expr.kind.unaryType) {
continue;
}
if (!vtypesEqual(operation.operand, subject)) {
continue;
}
return operation.result ?? operation.operand;
}
this.report(
`cannot apply unary operation '${expr.kind.unaryType}' ` +
`on type '${vtypeToString(subject)}'`,
pos,
);
return { type: "error" };
}
public checkBinaryExpr(expr: Expr): VType {
if (expr.kind.type !== "binary") {
throw new Error();
}
const pos = expr.pos;
const left = this.checkExpr(expr.kind.left);
const right = this.checkExpr(expr.kind.right);
for (const operation of simpleBinaryOperations) {
if (operation.binaryType !== expr.kind.binaryType) {
continue;
}
if (!vtypesEqual(operation.operand, left)) {
continue;
}
if (!vtypesEqual(left, right)) {
continue;
}
return operation.result ?? operation.operand;
}
this.report(
`cannot apply binary operation '${expr.kind.binaryType}' ` +
`on types '${vtypeToString(left)}' and '${
vtypeToString(right)
}'`,
pos,
);
return { type: "error" };
}
public checkIfExpr(expr: Expr): VType {
if (expr.kind.type !== "if") {
throw new Error();
}
const pos = expr.pos;
const cond = this.checkExpr(expr.kind.cond);
const truthy = this.checkExpr(expr.kind.truthy);
const falsy = expr.kind.falsy
? this.checkExpr(expr.kind.falsy)
: undefined;
if (cond.type !== "bool") {
this.report(
`if condition should be 'bool', got '${vtypeToString(cond)}'`,
pos,
);
return { type: "error" };
}
if (falsy === undefined && truthy.type !== "null") {
this.report(
`if expressions without false-case must result in type 'null'` +
`, got '${vtypeToString(truthy)}'`,
pos,
);
return { type: "error" };
}
if (falsy !== undefined && !vtypesEqual(truthy, falsy)) {
this.report(
`if cases must be compatible, got incompatible types` +
` '${vtypeToString(truthy)}'` +
` and '${vtypeToString(falsy)}'`,
pos,
);
return { type: "error" };
}
return truthy;
}
public checkLoopExpr(expr: Expr): VType {
if (expr.kind.type !== "loop") {
throw new Error();
}
const pos = expr.pos;
this.loopBreakStack.push([]);
const body = this.checkExpr(expr.kind.body);
if (body.type !== "null") {
this.report(
`loop body must result in type 'null'` +
`, got '${vtypeToString(body)}'`,
pos,
);
return { type: "error" };
}
const loopBreakTypes = this.loopBreakStack.pop()!;
if (loopBreakTypes.length === 0) {
return { type: "null" };
}
const breakType = loopBreakTypes.reduce<[VType, boolean, VType]>(
(acc, curr) => {
const [resulting, isIncompatible, outlier] = acc;
if (isIncompatible) {
return acc;
}
if (!vtypesEqual(resulting, curr)) {
return [resulting, true, curr];
}
return [resulting, false, outlier];
},
[{ type: "null" }, false, { type: "null" }],
);
if (breakType[1]) {
this.report(
`incompatible types in break statements` +
`, got '${vtypeToString(breakType[2])}'` +
` incompatible with ${vtypeToString(breakType[0])}`,
pos,
);
return { type: "error" };
}
return breakType[0];
}
public checkBlockExpr(expr: Expr): VType {
if (expr.kind.type !== "block") {
throw new Error();
}
this.checkFnHeaders(expr.kind.stmts);
for (const stmt of expr.kind.stmts) {
this.checkStmt(stmt);
}
return expr.kind.expr
? this.checkExpr(expr.kind.expr)
: { type: "null" };
}
public checkEType(etype: EType): VType {
const pos = etype.pos;
if (etype.kind.type === "ident") {
if (etype.kind.value === "null") {
return { type: "null" };
}
if (etype.kind.value === "int") {
return { type: "int" };
}
if (etype.kind.value === "bool") {
return { type: "bool" };
}
if (etype.kind.value === "string") {
return { type: "string" };
}
this.report(`undefined type '${etype.kind.value}'`, pos);
return { type: "error" };
}
if (etype.kind.type === "array") {
const inner = this.checkEType(etype.kind.inner);
return { type: "array", inner };
}
if (etype.kind.type === "struct") {
const noTypeTest = etype.kind.fields.reduce(
(acc, param) => [acc[0] || !param.etype, param.ident],
[false, ""],
);
if (noTypeTest[0]) {
this.report(
`field '${noTypeTest[1]}' declared without type`,
pos,
);
return { type: "error" };
}
const declaredTwiceTest = etype.kind.fields.reduce<
[boolean, string[], string]
>(
(acc, curr) => {
if (acc[0]) {
return acc;
}
if (acc[1].includes(curr.ident)) {
return [true, acc[1], curr.ident];
}
return [false, [...acc[1], curr.ident], ""];
},
[false, [], ""],
);
if (
declaredTwiceTest[0]
) {
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!),
}));
return { type: "struct", fields };
}
throw new Error(`unknown explicit type ${etype.kind.type}`);
}
private report(msg: string, pos: Pos) {
this.reporter.reportError({ reporter: "Checker", msg, pos });
printStackTrace();
}
}
const simpleUnaryOperations: {
unaryType: string;
operand: VType;
result?: VType;
}[] = [
{ unaryType: "not", operand: { type: "bool" } },
{ unaryType: "-", operand: { type: "int" } },
];
const simpleBinaryOperations: {
binaryType: string;
operand: VType;
result?: VType;
}[] = [
// arithmetic
{ binaryType: "+", operand: { type: "int" } },
{ binaryType: "+", operand: { type: "string" } },
{ binaryType: "-", operand: { type: "int" } },
{ binaryType: "*", operand: { type: "int" } },
{ binaryType: "/", operand: { type: "int" } },
// logical
{ binaryType: "and", operand: { type: "bool" } },
{ binaryType: "or", operand: { type: "bool" } },
// equality
{ binaryType: "==", operand: { type: "null" }, result: { type: "bool" } },
{ binaryType: "==", operand: { type: "int" }, result: { type: "bool" } },
{ binaryType: "==", operand: { type: "string" }, result: { type: "bool" } },
{ binaryType: "==", operand: { type: "bool" }, result: { type: "bool" } },
{ binaryType: "!=", operand: { type: "null" }, result: { type: "bool" } },
{ binaryType: "!=", operand: { type: "int" }, result: { type: "bool" } },
{ binaryType: "!=", operand: { type: "string" }, result: { type: "bool" } },
{ binaryType: "!=", operand: { type: "bool" }, result: { type: "bool" } },
// comparison
{ binaryType: "<", operand: { type: "int" }, result: { type: "bool" } },
{ binaryType: ">", operand: { type: "int" }, result: { type: "bool" } },
{ binaryType: "<=", operand: { type: "int" }, result: { type: "bool" } },
{ binaryType: ">=", operand: { type: "int" }, result: { type: "bool" } },
];

55
compiler/compiler.ts Normal file
View File

@ -0,0 +1,55 @@
import { AstCreator } from "./ast.ts";
import { Checker } from "./checker.ts";
import { CompoundAssignDesugarer } from "./desugar/compound_assign.ts";
import { SpecialLoopDesugarer } from "./desugar/special_loop.ts";
import { Reporter } from "./info.ts";
import { Lexer } from "./lexer.ts";
import { FnNamesMap, Lowerer } from "./lowerer.ts";
import { Parser } from "./parser.ts";
import { Resolver } from "./resolver.ts";
export type CompiledFile = {
filepath: string;
program: number[];
};
export type CompileResult = {
program: number[];
fnNames: FnNamesMap;
};
export class Compiler {
private astCreator = new AstCreator();
private reporter = new Reporter();
public constructor(private startFilePath: string) {}
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();
new SpecialLoopDesugarer(this.astCreator).desugar(ast);
new Resolver(this.reporter).resolve(ast);
new CompoundAssignDesugarer(this.astCreator).desugar(ast);
new Checker(this.reporter).check(ast);
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();
return { program, fnNames };
}
}

View File

@ -1,107 +0,0 @@
import * as ast from "./ast/mod.ts";
import { Pos, prettyPrintReport, printStackTrace, Report, Span } from "./diagnostics.ts";
export class Ctx {
private fileIds = new Ids();
private files = new Map<Id<File>, FileInfo>();
private reports: Report[] = [];
public fileHasChildWithIdent(file: File, childIdent: string): boolean {
return this.files.get(id(file))!
.subFiles.has(childIdent);
}
public addFile(
ident: string,
absPath: string,
relPath: string,
superFile: File | undefined,
text: string,
): File {
const file = this.fileIds.nextThenStep();
this.files.set(id(file), {
ident,
absPath,
relPath,
superFile,
subFiles: new Map(),
text,
});
if (superFile) {
this.files.get(id(superFile))!
.subFiles.set(ident, file);
}
return file;
}
public addFileAst(file: File, ast: ast.File) {
this.files.get(id(file))!.ast = ast;
}
public fileInfo(file: File): FileInfo {
return this.files.get(id(file))!;
}
public filePosLineText(file: File, pos: Pos): string {
const fileTextLines = this.fileInfo(file).text.split("\n")
return fileTextLines[pos.line-1]
}
public fileSpanText(file: File, span: Span): string {
let result = ""
const fileTextLines = this.fileInfo(file).text.split("\n")
for(let i = 0; i < fileTextLines.length; i++) {
if (i > span.end.line-1) {
break;
}
if (i >= span.begin.line-1) {
result += fileTextLines[i] + "\n";
}
}
return result
}
public report(rep: Report) {
this.reports.push(rep);
this.reportImmediately(rep);
}
public enableReportImmediately = false;
public enableStacktrace = false;
private reportImmediately(rep: Report) {
if (this.enableReportImmediately) {
prettyPrintReport(this, rep);
if (this.enableStacktrace) {
printStackTrace();
}
}
}
}
export type File = IdBase;
export type FileInfo = {
ident: string;
absPath: string;
relPath: string;
superFile?: File;
subFiles: Map<string, File>;
text: string;
ast?: ast.File;
};
export type IdBase = { id: number };
export type Id<IdType extends IdBase> = IdType["id"];
export const id = <IdType extends IdBase>(id: IdType): Id<IdType> => id.id;
export class Ids<IdType extends IdBase> {
private next = 0;
public nextThenStep(): IdType {
const id = this.next;
this.next += 1;
return { id } as IdType;
}
}

6
compiler/deno.lock generated
View File

@ -1,14 +1,8 @@
{
"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

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

View File

@ -1,95 +0,0 @@
import { Ctx, File } from "./ctx.ts";
import { exhausted } from "./util.ts";
export type Span = {
begin: Pos;
end: Pos;
};
export type Pos = {
idx: number;
line: number;
col: number;
};
export type Report = {
severity: "fatal" | "error" | "warning" | "info";
origin?: string;
msg: string;
file?: File;
span?: Span;
pos?: Pos;
};
function severityColor(severity: "fatal" | "error" | "warning" | "info") {
switch (severity) {
case "fatal":
return "\x1b[1m\x1b[31m";
case "error":
return "\x1b[1m\x1b[31m";
case "warning":
return "\x1b[1m\x1b[33m";
case "info":
return "\x1b[1m\x1b[34m";
}
exhausted(severity)
}
export function prettyPrintReport(ctx: Ctx, rep: Report) {
const { severity, msg } = rep;
const origin = rep.origin ? `\x1b[1m${rep.origin}:\x1b[0m ` : "";
console.error(`${origin}${severityColor(severity)}${severity}:\x1b[0m \x1b[37m${msg}\x1b[0m`);
if (rep.file && (rep.span || rep.pos)) {
const errorLineOffset = 2
const { absPath: path } = ctx.fileInfo(rep.file);
const { line, col } = rep.span?.begin ?? rep.pos!;
console.error(` --> ./${path}:${line}:${col}`);
if (rep.span) {
const spanLines = ctx.fileSpanText(rep.file, rep.span).split("\n");
spanLines.pop()
if (spanLines.length == 1) {
console.error(`${rep.span.begin.line.toString().padStart(4, ' ')}| ${spanLines[0]}`);
console.error(` | ${severityColor(severity)}${" ".repeat(rep.span.begin.col)}${"~".repeat(rep.span.end.col-rep.span.begin.col)}\x1b[0m`)
return
}
for (let i = 0; i < spanLines.length; i++) {
console.error(`${(rep.span.begin.line+i).toString().padStart(4, ' ')}| ${spanLines[i]}`);
if (i == 0) {
console.error(` | ${" ".repeat(rep.span.begin.col-1)}${severityColor(severity)}${"~".repeat(spanLines[i].length-(rep.span.begin.col-1))}\x1b[0m`)
}
else if (i == spanLines.length-1) {
console.error(` | ${severityColor(severity)}${"~".repeat(rep.span.end.col)}\x1b[0m`)
}
else {
console.error(` | ${severityColor(severity)}${"~".repeat(spanLines[i].length)}\x1b[0m`)
}
}
}
else if (rep.pos) {
console.error(`${rep.pos.line.toString().padStart(4, ' ')}| ${ctx.filePosLineText(rep.file, rep.pos)}`);
console.error(` | ${severityColor(severity)}${" ".repeat(rep.pos.col)}^\x1b[0m`)
}
}
}
export function printStackTrace() {
class StackTracer extends Error {
constructor() {
super("StackTracer");
}
}
try {
throw new StackTracer();
} catch (error) {
if (!(error instanceof StackTracer)) {
throw error;
}
console.log(
error.stack?.replace(
"Error: StackTracer",
"Stack trace:",
) ??
error,
);
}
}

View File

@ -1,7 +1,7 @@
import { Pos } from "./token.ts";
export type Report = {
type: "error" | "warning" | "note";
type: "error" | "note";
reporter: string;
pos?: Pos;
msg: string;
@ -11,27 +11,16 @@ 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 ? `\n at ${this.filePath}:${pos.line}:${pos.col}` : ""
pos ? ` at ${pos.line}:${pos.col}` : ""
}`,
);
}

View File

@ -27,31 +27,24 @@ 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);
@ -77,32 +70,6 @@ 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 = "";
@ -129,7 +96,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,143 +1,46 @@
import { Builtins, Ops } from "./arch.ts";
import { Assembler, Label } from "./assembler.ts";
import { AnnoView, Expr, Stmt } from "./ast.ts";
import { 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 { Assembler, Label } from "./assembler.ts";
import { vtypeToString } from "./vtype.ts";
import { Pos } from "./token.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 monoFns: MonoFnsMap,
private callMap: MonoCallNameGenMap,
private lastPos: Pos,
) {}
public constructor(private lastPos: Pos) {}
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() {
public lower(stmts: Stmt[]) {
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);
}
private addConcluding() {
this.scoutFnHeaders(stmts);
for (const stmt of stmts) {
this.lowerStaticStmt(stmt);
}
this.program.setLabel({ label: "_exit" });
this.addSourceMap(this.lastPos);
this.program.add(Ops.Pop);
}
private addSourceMap({ index, line, col }: Pos) {
this.program.add(Ops.SourceMap, index, line, col);
}
private addClearingSourceMap() {
this.program.add(Ops.SourceMap, 0, 1, 1);
}
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 lowerFnStmt(stmt: Stmt) {
if (stmt.kind.type !== "fn") {
throw new Error();
}
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();
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];
}
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;
return { program, fnNames };
}
private addSourceMap({ index, line, col }: Pos) {
@ -148,6 +51,32 @@ class MonoFnLowerer {
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;
}
}
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":
}
throw new Error(`unhandled static statement '${stmt.kind.type}'`);
}
private lowerStmt(stmt: Stmt) {
switch (stmt.kind.type) {
case "error":
@ -178,7 +107,7 @@ class MonoFnLowerer {
switch (stmt.kind.subject.kind.type) {
case "field": {
this.lowerExpr(stmt.kind.subject.kind.subject);
this.program.add(Ops.PushString, stmt.kind.subject.kind.ident);
this.program.add(Ops.PushString, stmt.kind.subject.kind.value);
this.program.add(Ops.Builtin, Builtins.StructSet);
return;
}
@ -224,6 +153,52 @@ class MonoFnLowerer {
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");
@ -234,7 +209,7 @@ class MonoFnLowerer {
`unexpected argument type '${anno.kind.type}' expected 'ident'`,
);
}
const value = anno.kind.ident;
const value = anno.kind.value;
const builtin = Object.entries(Builtins).find((entry) =>
entry[0] === value
)?.[1];
@ -277,13 +252,11 @@ class MonoFnLowerer {
case "group":
return void this.lowerExpr(expr.kind.expr);
case "field":
return this.lowerFieldExpr(expr);
break;
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":
@ -298,20 +271,6 @@ class MonoFnLowerer {
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();
@ -347,42 +306,8 @@ class MonoFnLowerer {
return;
}
if (expr.kind.sym.type === "fn") {
// 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);
const label = this.fnStmtIdLabelMap[expr.kind.sym.stmt.id];
this.program.add(Ops.PushPtr, { label });
return;
}
throw new Error(`unhandled sym type '${expr.kind.sym.type}'`);
@ -505,18 +430,6 @@ class MonoFnLowerer {
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);
@ -549,18 +462,9 @@ class MonoFnLowerer {
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();
@ -624,6 +528,7 @@ class MonoFnLowerer {
}
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);
@ -636,4 +541,8 @@ class MonoFnLowerer {
}
this.locals = outerLocals;
}
public printProgram() {
this.program.printProgram();
}
}

View File

@ -1,101 +1,5 @@
import * as path from "jsr:@std/path";
import { Parser } from "./parser/parser.ts";
import * as ast from "./ast/mod.ts";
import { Ctx } from "./ctx.ts";
import { File } from "./ctx.ts";
import { Compiler } from "./compiler.ts";
export type Pack = {
rootMod: Mod;
};
const { program } = await new Compiler(Deno.args[0]).compile();
export type Mod = null;
export interface PackEmitter {
emit(pack: Pack): void;
}
export class PackCompiler {
private ctx = new Ctx();
public constructor(
private entryFilePath: string,
private emitter: PackEmitter,
) {}
public compile() {
FileTreeAstCollector
.fromEntryFile(this.ctx, this.entryFilePath)
.collect();
}
}
type _P = { file: File };
export class FileTreeAstCollector implements ast.Visitor<[_P]> {
private subFilePromise = Promise.resolve();
private constructor(
private ctx: Ctx,
private superFile: File | undefined,
private ident: string,
private absPath: string,
private relPath: string,
) {}
public static fromEntryFile(
ctx: Ctx,
entryFilePath: string,
): FileTreeAstCollector {
return new FileTreeAstCollector(
ctx,
undefined,
"root",
entryFilePath,
entryFilePath,
);
}
public async collect(): Promise<void> {
const text = await Deno.readTextFile(this.absPath);
const file = this.ctx.addFile(
this.ident,
this.absPath,
this.relPath,
this.superFile,
text,
);
const fileAst = new Parser(file, text).parse();
this.ctx.addFileAst(file, fileAst);
ast.visitFile(this, fileAst, { file });
await this.subFilePromise;
}
visitModFileItem(
item: ast.Item,
kind: ast.ModFileItem,
{ file }: _P,
): ast.VisitRes {
const { ident: { text: ident }, filePath: relPath } = kind;
const absPath = path.join(path.dirname(this.absPath), relPath);
this.subFilePromise = this.subFilePromise
.then(() => {
if (this.ctx.fileHasChildWithIdent(file, ident)) {
this.ctx.report({
severity: "fatal",
msg: `module '${ident}' already declared`,
file,
span: item.span,
});
Deno.exit(1);
}
return new FileTreeAstCollector(
this.ctx,
file,
ident,
absPath,
relPath,
)
.collect();
});
return "stop";
}
}
await Deno.writeTextFile("out.slgbc", JSON.stringify(program));

View File

@ -1,241 +0,0 @@
import type { Syms } from "./resolver_syms.ts";
import { Pos } from "./token.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: "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 }
| 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;
vtype?: VType;
id: number;
};
export type ExprKind =
| { type: "error" }
| { type: "int"; value: number }
| { type: "string"; value: string }
| { type: "ident"; ident: string }
| {
type: "sym";
ident: string;
sym: Sym;
}
| { type: "group"; expr: Expr }
| { 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[];
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 }
| { type: "bool"; value: boolean }
| { type: "null" }
| { type: "loop"; body: Expr }
| { type: "block"; stmts: Stmt[]; expr?: Expr }
| { type: "while"; cond: Expr; body: Expr }
| { type: "for_in"; param: Param; value: Expr; body: Expr }
| {
type: "for";
decl?: Stmt;
cond?: Expr;
incr?: Stmt;
body: Expr;
};
export type UnaryType = "not" | "-";
export type BinaryType =
| "+"
| "*"
| "=="
| "-"
| "/"
| "!="
| "<"
| ">"
| "<="
| ">="
| "or"
| "and";
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: "generic"; stmt: Stmt; genericParam: GenericParam }
| { type: "mod"; syms: Syms };
export type EType = {
kind: ETypeKind;
pos: Pos;
id: number;
};
export type ETypeKind =
| { type: "error" }
| { 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 GenericParam = {
id: number;
index: number;
ident: string;
pos: Pos;
vtype?: VType;
};
export type Anno = {
ident: string;
args: Expr[];
pos: Pos;
};
export class AstCreator {
private nextNodeId = 0;
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.genId();
return { kind, pos, id };
}
public etype(kind: ETypeKind, pos: Pos): EType {
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 };
}

File diff suppressed because it is too large Load Diff

View File

@ -1,183 +0,0 @@
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";
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[];
fnNames: FnNamesMap;
};
export class Compiler {
private astCreator = new AstCreator();
private reporter;
public constructor(private startFilePath: string) {
this.reporter = new Reporter(this.startFilePath);
}
public async compile(): Promise<CompileResult> {
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);
new CompoundAssignDesugarer(this.astCreator).desugar(ast);
new Checker(this.reporter).check(ast);
//const mir = lowerAst(ast);
//
//console.log("Before optimizations:");
//printMir(mir);
//const mirHistory = [mirOpCount(mir)];
//for (let i = 0; i < 1; ++i) {
// eliminateUnusedLocals(mir, this.reporter, mirHistory.length === 1);
// eliminateOnlyChildsBlocks(mir);
// eliminateUnreachableBlocks(mir);
// eliminateTransientVals(mir);
//
// const opCount = mirOpCount(mir);
// const histOccurence = mirHistory
// .filter((v) => v === opCount).length;
// if (histOccurence >= 2) {
// break;
// }
// mirHistory.push(opCount);
//}
//
//console.log("After optimizations:");
//printMir(mir);
if (this.reporter.errorOccured()) {
console.error("Errors occurred, stopping compilation.");
Deno.exit(1);
}
const mir = lowerAst(ast);
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,94 +0,0 @@
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

@ -1,101 +0,0 @@
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,5 +0,0 @@
import { Compiler } from "./compiler.ts";
const { program } = await new Compiler(Deno.args[0]).compile();
await Deno.writeTextFile("out.slgbc", JSON.stringify(program));

View File

@ -1,237 +0,0 @@
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 !== "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,
});
}
}
}

View File

@ -1,107 +0,0 @@
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

@ -1,59 +0,0 @@
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

@ -1,81 +0,0 @@
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 !== "copy" && local.type !== "move") {
return;
}
this.locals = this.locals.filter((lid) => lid !== local.id);
}
}

View File

@ -1,60 +0,0 @@
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(this.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 !== "move" || src.id !== this.local.id) {
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":
return true;
case "ref":
case "ref_mut":
return false;
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

@ -1,513 +0,0 @@
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: "move", id };
}

View File

@ -1,388 +0,0 @@
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: "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: "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":
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":
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 "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 "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;
}
}
}

View File

@ -1,315 +0,0 @@
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,279 +0,0 @@
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,
LeafSyms,
ModSyms,
Syms,
} from "./resolver_syms.ts";
import { Pos } from "./token.ts";
export class Resolver implements AstVisitor<[Syms]> {
public constructor(private reporter: Reporter) {
}
public resolve(stmts: Stmt[]): VisitRes {
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";
}
visitLetStmt(stmt: Stmt, syms: Syms): VisitRes {
if (stmt.kind.type !== "let") {
throw new Error("expected let statement");
}
visitExpr(stmt.kind.value, this, syms);
const ident = stmt.kind.param.ident;
if (syms.definedLocally(ident)) {
this.reportAlreadyDefined(ident, stmt.pos, syms);
return;
}
stmt.kind.param.sym = syms.define(ident, {
ident,
type: "let",
fullPath: ident,
pos: stmt.kind.param.pos,
stmt,
param: stmt.kind.param,
});
return "stop";
}
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 {
if (stmt.kind.type !== "fn") {
throw new Error("expected fn statement");
}
const fnScopeSyms = new FnSyms(syms);
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";
}
visitIdentExpr(expr: Expr, syms: Syms): VisitRes {
if (expr.kind.type !== "ident") {
throw new Error("expected ident");
}
const ident = expr.kind.ident;
const symResult = syms.get(ident);
if (!symResult.ok) {
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: expr.kind.ident,
sym: getRes.sym,
};
return "stop";
}
visitBlockExpr(expr: Expr, syms: Syms): VisitRes {
if (expr.kind.type !== "block") {
throw new Error();
}
const childSyms = new LeafSyms(syms);
this.scout(expr.kind.stmts, childSyms);
visitStmts(expr.kind.stmts, this, childSyms);
if (expr.kind.expr) {
visitExpr(expr.kind.expr, this, childSyms);
}
return "stop";
}
visitForExpr(expr: Expr, syms: Syms): VisitRes {
if (expr.kind.type !== "for") {
throw new Error();
}
const childSyms = new LeafSyms(syms);
if (expr.kind.decl) visitStmt(expr.kind.decl, this, syms);
if (expr.kind.cond) visitExpr(expr.kind.cond, this, syms);
if (expr.kind.incr) visitStmt(expr.kind.incr, this, syms);
visitExpr(expr.kind.body, this, childSyms);
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",
msg: `use of undefined symbol '${ident}'`,
pos,
});
printStackTrace();
}
private reportAlreadyDefined(ident: string, pos: Pos, syms: Syms) {
this.reporter.reportError({
reporter: "Resolver",
msg: `symbol already defined '${ident}'`,
pos,
});
const prev = syms.get(ident);
if (!prev.ok) {
throw new Error("expected to be defined");
}
if (!prev.sym.pos) {
return;
}
this.reporter.addNote({
reporter: "Resolver",
msg: `previous definition of '${ident}'`,
pos: prev.sym.pos,
});
printStackTrace();
}
}

View File

@ -1,192 +0,0 @@
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): Sym;
definedLocally(ident: string): boolean;
get(ident: string): GetRes;
getPub(ident: string): GetRes;
rootMod(): Sym;
pathString(): string;
}
export class EntryModSyms implements Syms {
private syms: SymMap = {};
public constructor(private modName: string) {}
public define(ident: string, sym: Sym): Sym {
if (sym.type === "let") {
return this.define(ident, {
...sym,
type: "let_static",
});
}
this.syms[ident] = sym;
return sym;
}
public definedLocally(ident: string): boolean {
return ident in this.syms;
}
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 ModSyms implements Syms {
private syms: SymMap = {};
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): Sym {
if (sym.type === "let") {
return this.define(ident, {
...sym,
type: "let_static",
});
}
return this.syms[ident] = sym;
}
public definedLocally(ident: string): boolean {
return ident in this.syms;
}
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 this.parent.rootMod();
}
public pathString(): string {
return `${this.parent.pathString()}::${this.modName}`;
}
}
export class FnSyms implements Syms {
private syms: SymMap = {};
public constructor(private parent: Syms) {}
public define(ident: string, sym: Sym): Sym {
if (sym.type === "let") {
return this.define(ident, {
...sym,
type: "closure",
inner: sym,
});
}
this.syms[ident] = sym;
return sym;
}
public definedLocally(ident: string): boolean {
return ident in this.syms;
}
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 {
private syms: SymMap = {};
public constructor(private parent: Syms) {}
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): 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

@ -1,168 +0,0 @@
export type VType =
| { type: "error" }
| { type: "unknown" }
| { type: "null" }
| { type: "int" }
| { type: "string" }
| { type: "bool" }
| { 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";
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 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) && 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.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) {
return false;
}
for (let i = 0; i < a.params.length; ++i) {
if (!vtypesEqual(a.params[i].vtype, b.params[i].vtype)) {
return false;
}
}
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"]
.includes(vtype.type)
) {
return vtype.type;
}
if (vtype.type === "ref") {
return `&${vtypeToString(vtype.subject)}`;
}
if (vtype.type === "ref_mut") {
return `&mut ${vtypeToString(vtype.subject)}`;
}
if (vtype.type === "ptr") {
return `*${vtypeToString(vtype.subject)}`;
}
if (vtype.type === "ptr_mut") {
return `*mut ${vtypeToString(vtype.subject)}`;
}
if (vtype.type === "array") {
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)}`;
}
if (vtype.type === "generic") {
return `generic`;
}
throw new Error(`unhandled vtype '${vtype.type}'`);
}

View File

@ -1,4 +1,5 @@
import {
Anno,
AssignType,
AstCreator,
BinaryType,
@ -6,11 +7,8 @@ import {
ETypeKind,
Expr,
ExprKind,
Field,
GenericParam,
Param,
Stmt,
StmtDetails,
StmtKind,
UnaryType,
} from "./ast.ts";
@ -18,8 +16,6 @@ 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;
@ -38,34 +34,26 @@ export class Parser {
private parseStmts(): Stmt[] {
const stmts: Stmt[] = [];
while (!this.done()) {
stmts.push(this.parseStmt());
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();
}
}
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("{")) {
@ -92,9 +80,6 @@ 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();
}
@ -122,21 +107,19 @@ export class Parser {
private parseBlock(): Expr {
const pos = this.pos();
this.step();
const stmts: Stmt[] = [];
let stmts: Stmt[] = [];
while (!this.done()) {
if (this.test("}")) {
this.step();
return this.expr({ type: "block", stmts }, pos);
} else if (
["#", "pub", "mod", "fn"].some((tt) => this.test(tt))
) {
stmts.push(this.parseItemStmt());
} else if (
["let", "type_alias", "return", "break"]
.some((tt) => this.test(tt))
this.test("return") || this.test("break") || this.test("let")
) {
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))
) {
@ -180,110 +163,7 @@ export class Parser {
return this.expr({ type: "error" }, pos);
}
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 {
private parseFn(): Stmt {
const pos = this.pos();
this.step();
if (!this.test("ident")) {
@ -292,21 +172,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 | undefined;
let returnType: EType | null = null;
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);
@ -316,104 +196,105 @@ export class Parser {
{
type: "fn",
ident,
genericParams,
params,
returnType,
returnType: returnType !== null ? returnType : undefined,
body,
anno: anno != null ? anno : undefined,
},
pos,
details,
);
}
private parseFnETypeParams(): GenericParam[] {
return this.parseDelimitedList(this.parseETypeParam, ">", ",");
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 parseETypeParam(index: number): Res<GenericParam> {
private parseAnno(): Anno | null {
const pos = this.pos();
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("[")) {
this.report("expected '['");
return null;
}
this.report("expected generic parameter");
return { ok: false };
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 };
}
private parseFnParams(): Param[] {
return this.parseDelimitedList(this.parseParam, ")", ",");
}
private parseDelimitedList<T>(
parseElem: (this: Parser, index: number) => Res<T>,
endToken: string,
delimiter: string,
): T[] {
this.step();
if (this.test(endToken)) {
if (this.test(")")) {
this.step();
return [];
}
let i = 0;
const elems: T[] = [];
const elemRes = parseElem.call(this, i);
if (!elemRes.ok) {
const params: Param[] = [];
const paramResult = this.parseParam();
if (!paramResult.ok) {
return [];
}
elems.push(elemRes.value);
i += 1;
while (this.test(delimiter)) {
params.push(paramResult.value);
while (this.test(",")) {
this.step();
if (this.test(endToken)) {
if (this.test(")")) {
break;
}
const elemRes = parseElem.call(this, i);
if (!elemRes.ok) {
const paramResult = this.parseParam();
if (!paramResult.ok) {
return [];
}
elems.push(elemRes.value);
i += 1;
params.push(paramResult.value);
}
if (!this.test(endToken)) {
this.report(`expected '${endToken}'`);
return elems;
if (!this.test(")")) {
this.report("expected ')'");
return params;
}
this.step();
return elems;
return params;
}
private parseParam(index?: number): Res<Param> {
private parseParam(): { ok: true; value: Param } | { ok: false } {
const pos = this.pos();
if (this.test("ident") || this.test("mut")) {
let mut = false;
if (this.test("mut")) {
mut = true;
this.step();
}
if (this.test("ident")) {
const ident = this.current().identValue!;
this.step();
if (this.test(":")) {
this.step();
const etype = this.parseEType();
return {
ok: true,
value: this.astCreator.param({
index,
ident,
mut,
etype,
pos,
}),
};
return { ok: true, value: { ident, etype, pos } };
}
return {
ok: true,
value: this.astCreator.param({ index, ident, mut, pos }),
};
return { ok: true, value: { ident, pos } };
}
this.report("expected param");
return { ok: false };
@ -436,17 +317,6 @@ 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();
@ -574,80 +444,6 @@ 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();
@ -790,45 +586,54 @@ 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(".")) {
subject = this.parseFieldTail(subject);
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);
continue;
}
if (this.test("[")) {
subject = this.parseIndexTail(subject);
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);
continue;
}
if (this.test("(")) {
subject = this.parseCallTail(subject);
continue;
}
if (this.test("::")) {
subject = this.parsePathTail(subject);
continue;
}
if (this.test("::<")) {
subject = this.parseETypeArgsTail(subject);
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);
continue;
}
break;
@ -836,79 +641,12 @@ 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 ident = this.current().identValue!;
const value = this.current().identValue!;
this.step();
return this.expr({ type: "ident", ident }, pos);
return this.expr({ type: "ident", value }, pos);
}
if (this.test("int")) {
const value = this.current().intValue!;
@ -942,12 +680,6 @@ 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();
}
@ -958,36 +690,27 @@ export class Parser {
return this.parseLoop();
}
this.report(`expected expr, got '${this.current().type}'`, pos);
this.report("expected expr", 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", ident: ident }, pos);
return this.etype({ type: "ident", value: ident }, pos);
}
if (this.test("[")) {
this.step();
const subject = this.parseEType();
const inner = this.parseEType();
if (!this.test("]")) {
this.report("expected ']'", pos);
return this.etype({ type: "error" }, pos);
}
this.step();
return this.etype({ type: "array", subject }, pos);
return this.etype({ type: "array", inner }, pos);
}
if (this.test("struct")) {
this.step();
@ -998,26 +721,6 @@ 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);
}
@ -1074,6 +777,7 @@ export class Parser {
}
private report(msg: string, pos = this.pos()) {
console.log(`Parser: ${msg} at ${pos.line}:${pos.col}`);
this.reporter.reportError({
msg,
pos,
@ -1082,8 +786,8 @@ export class Parser {
printStackTrace();
}
private stmt(kind: StmtKind, pos: Pos, details?: StmtDetails): Stmt {
return this.astCreator.stmt(kind, pos, details);
private stmt(kind: StmtKind, pos: Pos): Stmt {
return this.astCreator.stmt(kind, pos);
}
private expr(kind: ExprKind, pos: Pos): Expr {

View File

@ -1,14 +0,0 @@
import { File } from "../ast/ast.ts";
import { File as CtxFile } from "../ctx.ts";
import { todo } from "../util.ts";
export class Parser {
public constructor(
private file: CtxFile,
private text: string,
) {}
public parse(): File {
return todo();
}
}

166
compiler/resolver.ts Normal file
View File

@ -0,0 +1,166 @@
import { Expr, Stmt } from "./ast.ts";
import {
AstVisitor,
visitExpr,
VisitRes,
visitStmt,
visitStmts,
} from "./ast_visitor.ts";
import { printStackTrace, Reporter } from "./info.ts";
import {
FnSyms,
GlobalSyms,
LeafSyms,
StaticSyms,
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);
return "stop";
}
visitLetStmt(stmt: Stmt, syms: Syms): VisitRes {
if (stmt.kind.type !== "let") {
throw new Error("expected let statement");
}
visitExpr(stmt.kind.value, this, syms);
const ident = stmt.kind.param.ident;
if (syms.definedLocally(ident)) {
this.reportAlreadyDefined(ident, stmt.pos, syms);
return;
}
syms.define(ident, {
ident,
type: "let",
pos: stmt.kind.param.pos,
stmt,
param: stmt.kind.param,
});
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,
});
}
}
visitFnStmt(stmt: Stmt, syms: Syms): VisitRes {
if (stmt.kind.type !== "fn") {
throw new Error("expected fn statement");
}
const fnScopeSyms = new FnSyms(syms);
for (const param of stmt.kind.params) {
if (fnScopeSyms.definedLocally(param.ident)) {
this.reportAlreadyDefined(param.ident, param.pos, syms);
continue;
}
fnScopeSyms.define(param.ident, {
ident: param.ident,
type: "fn_param",
pos: param.pos,
param,
});
}
visitExpr(stmt.kind.body, this, fnScopeSyms);
return "stop";
}
visitIdentExpr(expr: Expr, syms: Syms): VisitRes {
if (expr.kind.type !== "ident") {
throw new Error("expected ident");
}
const ident = expr.kind;
const symResult = syms.get(ident.value);
if (!symResult.ok) {
this.reportUseOfUndefined(ident.value, expr.pos, syms);
return;
}
const sym = symResult.sym;
expr.kind = {
type: "sym",
ident: ident.value,
sym,
};
return "stop";
}
visitBlockExpr(expr: Expr, syms: Syms): VisitRes {
if (expr.kind.type !== "block") {
throw new Error();
}
const childSyms = new LeafSyms(syms);
this.scoutFnStmts(expr.kind.stmts, childSyms);
visitStmts(expr.kind.stmts, this, childSyms);
if (expr.kind.expr) {
visitExpr(expr.kind.expr, this, childSyms);
}
return "stop";
}
visitForExpr(expr: Expr, syms: Syms): VisitRes {
if (expr.kind.type !== "for") {
throw new Error();
}
const childSyms = new LeafSyms(syms);
if (expr.kind.decl) visitStmt(expr.kind.decl, this, syms);
if (expr.kind.cond) visitExpr(expr.kind.cond, this, syms);
if (expr.kind.incr) visitStmt(expr.kind.incr, this, syms);
visitExpr(expr.kind.body, this, childSyms);
return "stop";
}
private reportUseOfUndefined(ident: string, pos: Pos, _syms: Syms) {
this.reporter.reportError({
reporter: "Resolver",
msg: `use of undefined symbol '${ident}'`,
pos,
});
printStackTrace();
}
private reportAlreadyDefined(ident: string, pos: Pos, syms: Syms) {
this.reporter.reportError({
reporter: "Resolver",
msg: `symbol already defined '${ident}'`,
pos,
});
const prev = syms.get(ident);
if (!prev.ok) {
throw new Error("expected to be defined");
}
if (!prev.sym.pos) {
return;
}
this.reporter.addNote({
reporter: "Resolver",
msg: `previous definition of '${ident}'`,
pos: prev.sym.pos,
});
printStackTrace();
}
}

115
compiler/resolver_syms.ts Normal file
View File

@ -0,0 +1,115 @@
import { Sym } from "./ast.ts";
export type SymMap = { [ident: string]: Sym };
export interface Syms {
define(ident: string, sym: Sym): void;
definedLocally(ident: string): boolean;
get(ident: string): { ok: true; sym: Sym } | { ok: false };
}
export class GlobalSyms implements Syms {
private syms: SymMap = {};
public constructor() {}
public define(ident: string, sym: Sym) {
if (sym.type === "let") {
this.define(ident, {
...sym,
type: "let_static",
});
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 } {
if (ident in this.syms) {
return { ok: true, sym: this.syms[ident] };
}
return { ok: false };
}
}
export class StaticSyms implements Syms {
private syms: SymMap = {};
public constructor(private parent: GlobalSyms) {}
public define(ident: string, sym: Sym) {
if (sym.type === "let") {
this.define(ident, {
...sym,
type: "let_static",
});
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 } {
if (ident in this.syms) {
return { ok: true, sym: this.syms[ident] };
}
return this.parent.get(ident);
}
}
export class FnSyms implements Syms {
private syms: SymMap = {};
public constructor(private parent: Syms) {}
public define(ident: string, sym: Sym) {
if (sym.type === "let") {
this.define(ident, {
...sym,
type: "closure",
inner: 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 } {
if (ident in this.syms) {
return { ok: true, sym: this.syms[ident] };
}
return this.parent.get(ident);
}
}
export class LeafSyms implements Syms {
private syms: SymMap = {};
public constructor(private parent: Syms) {}
public define(ident: string, sym: Sym) {
this.syms[ident] = sym;
}
public definedLocally(ident: string): boolean {
return ident in this.syms;
}
public get(ident: string): { ok: true; sym: Sym } | { ok: false } {
if (ident in this.syms) {
return { ok: true, sym: this.syms[ident] };
}
return this.parent.get(ident);
}
}

View File

@ -1,64 +0,0 @@
import { Ctx } from "./ctx.ts";
import { prettyPrintReport } from "./diagnostics.ts";
const ctx = new Ctx();
const text = `
make an error here
`;
const biggerText = `
dont make error here
not here but start error here
and here
also here but not here
or here
`
const file = ctx.addFile(
"root",
"path/file.ts",
"path/file.ts",
undefined,
text,
);
const biggerFile = ctx.addFile(
"root",
"path/file.ts",
"path/file.ts",
undefined,
biggerText,
);
prettyPrintReport(ctx, {
file,
msg: "an error",
severity: "fatal",
origin: "compiler",
span: {
begin: { idx: 5, line: 2, col: 5 },
end: { idx: 13, line: 2, col: 13 },
},
});
prettyPrintReport(ctx, {
file: biggerFile,
msg: "an error",
severity: "error",
origin: "compiler",
span: {
begin: { idx: 6, line: 3, col: 14 },
end: { idx: 13, line: 5, col: 13 },
},
});
prettyPrintReport(ctx, {
file,
msg: "an error",
severity: "warning",
origin: "compiler",
pos: {
idx: 6, line: 2, col: 8
},
});

View File

@ -1,9 +0,0 @@
export function todo<T>(msg?: string): T {
class NotImplemented extends Error {}
throw new NotImplemented(msg);
}
export function exhausted(_: never) {
class Unexhausted extends Error {}
throw new Unexhausted();
}

62
compiler/vtype.ts Normal file
View File

@ -0,0 +1,62 @@
export type VType =
| { type: "error" }
| { type: "unknown" }
| { type: "null" }
| { type: "int" }
| { type: "string" }
| { type: "bool" }
| { type: "array"; inner: VType }
| { type: "struct"; fields: VTypeParam[] }
| { type: "fn"; params: VTypeParam[]; returnType: VType };
export type VTypeParam = {
ident: string;
vtype: VType;
};
export function vtypesEqual(a: VType, b: VType): boolean {
if (a.type !== b.type) {
return false;
}
if (
["error", "unknown", "null", "int", "string", "bool"]
.includes(a.type)
) {
return true;
}
if (a.type === "array" && b.type === "array") {
return vtypesEqual(a.inner, b.inner);
}
if (a.type === "fn" && b.type === "fn") {
if (a.params.length !== b.params.length) {
return false;
}
for (let i = 0; i < a.params.length; ++i) {
if (!vtypesEqual(a.params[i].vtype, b.params[i].vtype)) {
return false;
}
}
return vtypesEqual(a.returnType, b.returnType);
}
return false;
}
export function vtypeToString(vtype: VType): string {
if (
["error", "unknown", "null", "int", "string", "bool"]
.includes(vtype.type)
) {
return vtype.type;
}
if (vtype.type === "array") {
return `[${vtypeToString(vtype.inner)}]`;
}
if (vtype.type === "fn") {
const paramString = vtype.params.map((param) =>
`${param.ident}: ${vtypeToString(param.vtype)}`
)
.join(", ");
return `fn (${paramString}) -> ${vtypeToString(vtype.returnType)}`;
}
throw new Error(`unhandled vtype '${vtype.type}'`);
}

4
dev-env/Dockerfile Normal file
View File

@ -0,0 +1,4 @@
FROM archlinux
RUN pacman -Syu git base-devel deno --noconfirm
WORKDIR /workspace
ENTRYPOINT ["/bin/bash"]

3
dev-env/run.sh Normal file
View File

@ -0,0 +1,3 @@
#!/bin/sh
docker build -t slige-dev-env dev-env
docker run --name dev-env --rm -it --mount type=bind,source="$(pwd)"/,target=/workspace slige-dev-env

View File

@ -7,8 +7,7 @@ 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 mod pub
syn keyword Keyword break return let fn loop if else struct import or and not while for in
syn keyword Special null
syn keyword Type int string bool
syn keyword Boolean true false
@ -38,9 +37,6 @@ 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
@ -48,23 +44,11 @@ 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\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 match Function '[a-zA-Z_]\w*\ze('
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,17 +53,6 @@
}
]
},
"chars": {
"name": "string.quoted.double.slige",
"begin": "'",
"end": "'",
"patterns": [
{
"name": "constant.character.escape.slige",
"match": "\\\\."
}
]
},
"numbers": {
"patterns": [
{

View File

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

View File

@ -1,164 +0,0 @@
mod std;
type_alias Tok: struct {
type: string,
value: string,
};
fn lex(text: string) -> [Tok] {
let i = 0;
let len = std::string_length(text);
let toks = std::array_new::<Tok>();
while i < len {
if std::string_contains(" \t\n", text[i]) {
i += 1;
if i >= len {
break;
}
}
if text[i] >= '1' and text[i] <= '9' {
let value = std::ctos(text[i]);
i += 1;
while i < len and text[i] >= '0' and text[i] <= '9' {
value = std::string_push_char(value, text[i]);
i += 1;
}
let tok = struct { type: "int", value: value };
std::array_push(toks, tok);
} else if text[i] == '0' {
i += 1;
let tok = struct { type: "int", value: "0" };
std::array_push(toks, tok);
} else if text[i] == '+' {
i += 1;
let tok = struct { type: "+", value: "+" };
std::array_push(toks, tok);
} else if text[i] == '-' {
i += 1;
let tok = struct { type: "-", value: "-" };
std::array_push(toks, tok);
} else if text[i] == '*' {
i += 1;
let tok = struct { type: "*", value: "*" };
std::array_push(toks, tok);
} else if text[i] == '/' {
i += 1;
let tok = struct { type: "/", value: "/" };
std::array_push(toks, tok);
} else if text[i] == '(' {
i += 1;
let tok = struct { type: "(", value: "(" };
std::array_push(toks, tok);
} else if text[i] == ')' {
i += 1;
let tok = struct { type: ")", value: ")" };
std::array_push(toks, tok);
} else {
std::println("error: illegal character '" + std::ctos(text[i]) + "'");
i += 1;
}
}
toks
}
type_alias Calc: struct {
toks: [Tok],
toks_len: int,
i: int,
};
fn calc_new(text: string) -> Calc {
let toks = lex(text);
let toks_len = std::array_length(toks);
struct { toks: toks, toks_len: toks_len, i: 0 }
}
fn calc_expr(self: Calc) -> int {
calc_add_sub(self)
}
fn calc_add_sub(self: Calc) -> int {
let left = calc_mul_div(self);
loop {
if self.toks[self.i].type == "+" {
self.i += 1;
let right = calc_mul_div(self);
left = left + right;
} else if self.toks[self.i].type == "-" {
self.i += 1;
let right = calc_mul_div(self);
left = left - right;
} else {
break;
}
}
left
}
fn calc_mul_div(self: Calc) -> int {
let left = calc_unary(self);
loop {
if self.toks[self.i].type == "*" {
self.i += 1;
let right = calc_unary(self);
left = left * right;
} else if self.toks[self.i].type == "/" {
self.i += 1;
let right = calc_unary(self);
left = left / right;
} else {
break;
}
}
left
}
fn calc_unary(self: Calc) -> int {
if self.toks[self.i].type == "-" {
self.i += 1;
let subject = calc_unary(self);
-subject
} else {
calc_operand(self)
}
}
fn calc_operand(self: Calc) -> int {
if self.i >= self.toks_len {
std::println("error: expected expr");
0
} else if self.toks[self.i].type == "int" {
let val = std::stoi(self.toks[self.i].value);
self.i += 1;
val
} else if self.toks[self.i].type == "(" {
self.i += 1;
let val = calc_expr(self);
if self.i >= self.toks_len or self.toks[self.i].type != ")" {
std::println("error: missing ')'");
return 0;
}
self.i += 1;
val
} else {
std::println("error: expected expr");
self.i += 1;
0
}
}
fn main() {
loop {
let line = std::input("> ");
if line == "exit" {
break;
}
let calc = calc_new(line);
let val = calc_expr(calc);
std::println(line);
}
}

View File

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

View File

@ -1,21 +0,0 @@
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,50 +56,12 @@ fn input(prompt: string) -> string {
//
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;
}
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{
if n == 1 or n == 0{
return false;
}
if n == 1 {
return true;
}
let n_root = sqrt(n);
for (let i = 2; i < n_root; i += 1) {
for (let i = 2; i < n; i += 1) {
if remainder(n, i) == 0 {
return false;
}
@ -108,7 +70,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) + " ");
}

View File

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

View File

@ -1,3 +1,56 @@
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 {
@ -10,34 +63,18 @@ 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 = array_new::<int>();
let result = int_array_new();
let length = string_length(value);
for (let i = 0; i < length; i += 1) {
array_push(result, value[i]);
int_array_push(result, value[i]);
}
result
}

View File

@ -1,22 +0,0 @@
// 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

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

View File

@ -1,7 +1,6 @@
#include "alloc.hpp"
#include <format>
#include <iostream>
#include <string>
using namespace sliger::heap;
@ -15,18 +14,3 @@ 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,9 +19,6 @@ 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 {
@ -205,7 +202,7 @@ private:
}
}
size_t max_size = 512;
size_t max_size = 4;
std::vector<AllocItem> heap_1;
std::vector<AllocItem> heap_2;

View File

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

View File

@ -289,11 +289,6 @@ 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;
}
@ -305,15 +300,9 @@ 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 = stack_pop().as_int().value;
auto number = static_cast<int32_t>(stack_pop().as_int().value);
auto str = std::to_string(number);
stack_push(String(str));
break;
@ -336,11 +325,12 @@ void VM::run_builtin(Builtin builtin_id)
run_array_builtin(builtin_id);
break;
case Builtin::StructNew:
case Builtin::StructSet:
case Builtin::StructAt:
run_struct_builtin(builtin_id);
case Builtin::StructSet: {
assert_stack_has(2);
std::cerr << std::format("not implemented\n");
std::exit(1);
break;
}
case Builtin::Print:
case Builtin::FileOpen:
@ -411,7 +401,6 @@ void VM::run_string_builtin(Builtin builtin_id)
break;
}
}
void VM::run_array_builtin(Builtin builtin_id)
{
switch (builtin_id) {
@ -461,40 +450,6 @@ 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,7 +199,6 @@ 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,17 +3,11 @@
set -e
echo Text:
if command -v pygmentize 2>&1 >/dev/null
then
pygmentize -l rust -Ostyle="gruvbox-dark",linenos=1 $1
else
cat $1
fi
cat $1
echo Compiling $1...
deno run --allow-read --allow-write --check compiler/main.ts $1
deno run --allow-read --allow-write compiler/main.ts $1
echo Running out.slgbc...

View File

@ -1,184 +0,0 @@
// stdlib.slg
#[builtin(Exit)]
pub fn exit(status_code: int) {}
#[builtin(Print)]
pub fn print(msg: string) {}
pub fn println(msg: string) { print(msg + "\n") }
#[builtin(IntToString)]
pub fn int_to_string(number: int) -> string {}
#[builtin(StringPushChar)]
pub fn string_push_char(str: string, value: int) -> string {}
#[builtin(StringCharAt)]
pub fn string_char_at(str: string, index: int) -> int {}
#[builtin(StringLength)]
pub fn string_length(str: string) -> int {}
#[builtin(StringToInt)]
pub fn string_to_int(str: string) -> int {}
#[builtin(ArrayNew)]
pub fn array_new<T>() -> [T] {}
#[builtin(ArrayPush)]
pub fn array_push<T>(array: [T], value: T) {}
#[builtin(ArrayLength)]
pub fn array_length<T>(array: [T]) -> int {}
#[builtin(ArrayAt)]
pub fn array_at<T>(array: [T], index: int) -> T {}
#[builtin(StructNew)]
pub fn struct_new<S>() -> S {}
#[builtin(StructSet)]
pub fn struct_set<S, T>(subject: S, value: T) {}
#[builtin(FileOpen)]
pub fn file_open(filename: string, mode: string) -> int {}
#[builtin(FileClose)]
pub fn file_close(file: int) {}
#[builtin(FileWriteString)]
pub fn file_write_string(file: int, content: string) -> int {}
#[builtin(FileReadChar)]
pub fn file_read_char(file: int) -> int {}
#[builtin(FileReadToString)]
pub fn file_read_to_string(file: int) -> string {}
#[builtin(FileFlush)]
pub fn file_flush(file: int) {}
#[builtin(FileEof)]
pub fn file_eof(file: int) -> bool {}
#[builtin(IntToString)]
pub fn itos(number: int) -> string {}
#[builtin(StringToInt)]
pub fn stoi(str: string) -> int {}
pub fn ctos(ch: int) -> string { string_push_char("", ch) }
pub fn stdin() -> int { 0 }
pub fn stdout() -> int { 1 }
pub fn stderr() -> int { 2 }
pub fn file_read_line(file: int) -> string {
let line = "";
loop {
if file_eof(file) {
break;
}
let ch = file_read_char(file);
if ch == "\n"[0] {
break;
}
line = string_push_char(line, ch);
}
line
}
pub fn read_text_file(filename: string) -> string {
let file = file_open(filename, "r");
let text = file_read_to_string(file);
file_close(file);
text
}
pub fn input(prompt: string) -> string {
print(prompt);
file_flush(stdout());
file_read_line(stdin())
}
pub fn string_abs(number: int) -> int {
let result = number;
if number < 0 {
result = number - (number * 2);
}
result
}
pub fn string_split(str: string, seperator: int) -> [string] {
let result = array_new::<string>();
let i = 0;
let current_str = "";
loop {
if i >= string_length(str) {
break;
}
let char = str[i];
if char == seperator {
array_push(result, current_str);
current_str = "";
} else {
current_str = string_push_char(current_str, char);
}
i = i + 1;
}
array_push(result, current_str);
result
}
pub fn string_slice(str: string, from: int, to: int) -> string {
let result = "";
let i = from;
loop {
if i >= string_length(str) {
break;
}
if i >= to {
break;
}
result = string_push_char(result, str[i]);
i = i + 1;
}
result
}
pub fn string_contains(str: string, ch: int) -> bool {
let len = string_length(str);
for (let i = 0; i < len; i += 1) {
if str[i] == ch {
return true;
}
}
false
}
pub fn array_clone<T>(array: [T]) -> [T] {
let len = array_length(array);
let result = array_new::<T>();
let i = 0;
loop {
if i >= len { break; }
array_push(result, array[i]);
i = 1 + 1;
}
result
}
pub fn array_sort_mut(array: [int]) {
let len = array_length(array);
for (let i = 0; i < len; i += 1) {
for (let j = i + 1; j < len; j += 1) {
if array[j] < array[i] {
let tmp = array[j];
array[j] = array[i];
array[i] = tmp;
}
}
}
}
pub fn array_to_sorted(array: [int]) -> [int] {
let cloned = array_clone(array);
array_sort_mut(array);
cloned
}
pub fn assert(value: bool, msg: string) {
if not value {
println("assertion failed: " + msg);
exit(1);
}
}

151
stdlib.slg Normal file
View File

@ -0,0 +1,151 @@
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
}

View File

@ -1,11 +0,0 @@
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");
}

View File

@ -1,10 +0,0 @@
fn exit(status_code: int) #[builtin(Exit)] {}
fn main() {
if 'A' != 65 {
exit(1);
}
exit(0);
}

View File

@ -1,23 +0,0 @@
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

@ -1,19 +0,0 @@
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

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

View File

@ -1,25 +0,0 @@
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);
}

View File

@ -1,20 +0,0 @@
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");
}