add slige compiler

This commit is contained in:
SimonFJ20 2025-02-04 15:09:00 +01:00
parent 3113e771e0
commit 2d1dde8d60
24 changed files with 4194 additions and 0 deletions

269
slige/compiler/ast/ast.ts Normal file
View File

@ -0,0 +1,269 @@
import { AstId, File as CtxFile, IdentId } from "../ids.ts";
import { Span } from "../diagnostics.ts";
export type File = {
stmts: Stmt[];
file: CtxFile;
};
export type Stmt = {
id: AstId;
kind: StmtKind;
span: Span;
};
export type StmtKind =
| { tag: "error" }
| { tag: "item" } & ItemStmt
| { tag: "let" } & LetStmt
| { tag: "return" } & ReturnStmt
| { tag: "break" } & BreakStmt
| { tag: "continue" }
| { tag: "assign" } & AssignStmt
| { tag: "expr" } & ExprStmt;
export type ItemStmt = { item: Item };
export type LetStmt = {
pat: Pat;
ty?: Ty;
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 = {
id: AstId;
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: "type_alias" } & TypeAliasItem;
export type ModBlockItem = { block: Block };
export type ModFileItem = { filePath: string; file?: CtxFile; ast?: File };
export type EnumItem = { variants: Variant[] };
export type StructItem = { data: VariantData };
export type FnItem = {
generics?: Generics;
params: Param[];
returnTy?: Ty;
body?: Block;
};
export type UseItem = { _: 0 };
export type TypeAliasItem = { ty: Ty };
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 Param = {
pat: Pat;
ty: Ty;
span: Span;
};
export type Generics = {
params: GenericParam[];
};
export type GenericParam = {
ident: Ident;
span: Span;
};
export type Expr = {
id: AstId;
kind: ExprKind;
span: Span;
};
export type ExprKind =
| { tag: "error" }
| { tag: "path" } & PathExpr
| { tag: "null" }
| { tag: "int" } & IntExpr
| { tag: "bool" } & BoolExpr
| { tag: "str" } & StringExpr
| { tag: "group" } & GroupExpr
| { tag: "array" } & ArrayExpr
| { tag: "repeat" } & RepeatExpr
| { tag: "struct" } & StructExpr
| { tag: "ref" } & RefExpr
| { tag: "deref" } & DerefExpr
| { tag: "elem" } & ElemExpr
| { tag: "field" } & FieldExpr
| { tag: "index" } & IndexExpr
| { tag: "call" } & CallExpr
| { tag: "unary" } & UnaryExpr
| { tag: "binary" } & BinaryExpr
| { tag: "block" } & BlockExpr
| { tag: "if" } & IfExpr
| { tag: "loop" } & LoopExpr
| { tag: "while" } & WhileExpr
| { tag: "for" } & ForExpr
| { tag: "c_for" } & CForExpr;
export type PathExpr = { qty?: Ty; path: Path };
export type IntExpr = { value: number };
export type BoolExpr = { value: boolean };
export type StringExpr = { value: string };
export type GroupExpr = { expr: Expr };
export type ArrayExpr = { exprs: Expr[] };
export type RepeatExpr = { expr: Expr; length: Expr };
export type StructExpr = { path?: Path; fields: ExprField[] };
export type RefExpr = { expr: Expr; refType: RefType; mut: boolean };
export type DerefExpr = { expr: Expr };
export type ElemExpr = { expr: Expr; elem: number };
export type FieldExpr = { expr: Expr; ident: Ident };
export type IndexExpr = { expr: Expr; index: Expr };
export type CallExpr = { expr: Expr; args: Expr[] };
export type UnaryExpr = { unaryType: UnaryType; expr: Expr };
export type BinaryExpr = { binaryType: BinaryType; left: Expr; right: Expr };
export type BlockExpr = { block: Block };
export type IfExpr = { cond: Expr; truthy: Expr; falsy?: Expr };
export type LoopExpr = { body: Expr };
export type WhileExpr = { cond: Expr; body: Expr };
export type ForExpr = { pat: Pat; expr: Expr; body: Expr };
export type CForExpr = { decl?: Stmt; cond?: Expr; incr?: Stmt; body: Expr };
export type RefType = "ref" | "ptr";
export type UnaryType = "not" | "-";
export type BinaryType =
| "+"
| "*"
| "=="
| "-"
| "/"
| "!="
| "<"
| ">"
| "<="
| ">="
| "or"
| "and";
export type ExprField = {
ident: Ident;
expr: Expr;
span: Span;
};
export type Block = {
stmts: Stmt[];
expr?: Expr;
span: Span;
};
export type Pat = {
id: AstId;
kind: PatKind;
span: Span;
};
export type PatKind =
| { tag: "error" }
| { tag: "bind" } & BindPat
| { tag: "path" } & PathPat;
export type BindPat = { ident: Ident; mut: boolean };
export type PathPat = { qty?: Ty; path: Path };
export type Ty = {
id: AstId;
kind: TyKind;
span: Span;
};
export type TyKind =
| { tag: "error" }
| { tag: "null" }
| { tag: "int" }
| { tag: "bool" }
| { tag: "str" }
| { tag: "path" } & PathTy
| { tag: "ref" } & RefTy
| { tag: "ptr" } & PtrTy
| { tag: "slice" } & SliceTy
| { tag: "array" } & ArrayTy
| { tag: "anon_struct" } & AnonStructTy;
export type PathTy = { qty?: Ty; path: Path };
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 AnonStructTy = { fields: AnonFieldDef[] };
export type AnonFieldDef = {
ident: Ident;
ty: Ty;
span: Span;
};
export type Path = {
segments: PathSegment[];
span: Span;
};
export type PathSegment = {
ident: Ident;
genericArgs?: Ty[];
span: Span;
};
export type Ident = {
id: IdentId;
text: string;
span: Span;
};

53
slige/compiler/ast/cx.ts Normal file
View File

@ -0,0 +1,53 @@
import { Span } from "../diagnostics.ts";
import { AstId, Ids } from "../ids.ts";
import {
Expr,
ExprKind,
Ident,
Item,
ItemKind,
Pat,
PatKind,
Stmt,
StmtKind,
Ty,
TyKind,
} from "./ast.ts";
export class Cx {
private astIdGen = new Ids<AstId>();
private id(): AstId {
return this.astIdGen.nextThenStep();
}
public stmt(kind: StmtKind, span: Span): Stmt {
const id = this.id();
return { id, kind, span };
}
public item(
kind: ItemKind,
span: Span,
ident: Ident,
pub: boolean,
): Item {
const id = this.id();
return { id, kind, span, ident, pub };
}
public expr(kind: ExprKind, span: Span): Expr {
const id = this.id();
return { id, kind, span };
}
public pat(kind: PatKind, span: Span): Pat {
const id = this.id();
return { id, kind, span };
}
public ty(kind: TyKind, span: Span): Ty {
const id = this.id();
return { id, kind, span };
}
}

View File

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

View File

@ -0,0 +1,33 @@
import { Ctx } from "../ctx.ts";
import { exhausted, todo } from "../util.ts";
import { Block, Item } from "./ast.ts";
export function itemToString(ctx: Ctx, item: Item): string {
const ident = ctx.identText(item.ident.id);
const k = item.kind;
switch (k.tag) {
case "error":
return `<error>`;
case "mod_block": {
const block = blockToString(ctx, k.block);
return `mod ${item} ${block}`;
}
case "mod_file":
return todo();
case "enum":
return todo();
case "struct":
return todo();
case "fn":
return todo();
case "use":
return todo();
case "type_alias":
return todo();
}
return exhausted(k);
}
export function blockToString(ctx: Ctx, block: Block): string {
return todo();
}

View File

@ -0,0 +1,581 @@
import { exhausted } from "../util.ts";
import {
AnonStructTy,
ArrayExpr,
ArrayTy,
AssignStmt,
BinaryExpr,
BindPat,
Block,
BlockExpr,
BoolExpr,
BreakStmt,
CallExpr,
CForExpr,
DerefExpr,
ElemExpr,
EnumItem,
Expr,
ExprStmt,
FieldExpr,
File,
FnItem,
ForExpr,
Generics,
GroupExpr,
Ident,
IfExpr,
IndexExpr,
IntExpr,
Item,
ItemStmt,
LetStmt,
LoopExpr,
ModBlockItem,
ModFileItem,
Param,
Pat,
Path,
PathExpr,
PathPat,
PathTy,
PtrTy,
RefExpr,
RefTy,
RepeatExpr,
ReturnStmt,
SliceTy,
Stmt,
StringExpr,
StructExpr,
StructItem,
TupleTy,
Ty,
TypeAliasItem,
UnaryExpr,
UseItem,
Variant,
VariantData,
WhileExpr,
} 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;
visitContinueStmt?(stmt: Stmt, ...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;
visitTypeAliasItem?(item: Item, kind: TypeAliasItem, ...p: P): R;
visitVariant?(variant: Variant, ...p: P): R;
visitExpr?(expr: Expr, ...p: P): R;
visitErrorExpr?(expr: Expr, ...p: P): R;
visitPathExpr?(expr: Expr, kind: PathExpr, ...p: P): R;
visitNullExpr?(expr: Expr, ...p: P): R;
visitIntExpr?(expr: Expr, kind: IntExpr, ...p: P): R;
visitBoolExpr?(expr: Expr, kind: BoolExpr, ...p: P): R;
visitStringExpr?(expr: Expr, kind: StringExpr, ...p: P): R;
visitGroupExpr?(expr: Expr, kind: GroupExpr, ...p: P): R;
visitArrayExpr?(expr: Expr, kind: ArrayExpr, ...p: P): R;
visitRepeatExpr?(expr: Expr, kind: RepeatExpr, ...p: P): R;
visitStructExpr?(expr: Expr, kind: StructExpr, ...p: P): R;
visitRefExpr?(expr: Expr, kind: RefExpr, ...p: P): R;
visitDerefExpr?(expr: Expr, kind: DerefExpr, ...p: P): R;
visitElemExpr?(expr: Expr, kind: ElemExpr, ...p: P): R;
visitFieldExpr?(expr: Expr, kind: FieldExpr, ...p: P): R;
visitIndexExpr?(expr: Expr, kind: IndexExpr, ...p: P): R;
visitCallExpr?(expr: Expr, kind: CallExpr, ...p: P): R;
visitUnaryExpr?(expr: Expr, kind: UnaryExpr, ...p: P): R;
visitBinaryExpr?(expr: Expr, kind: BinaryExpr, ...p: P): R;
visitBlockExpr?(expr: Expr, kind: BlockExpr, ...p: P): R;
visitIfExpr?(expr: Expr, kind: IfExpr, ...p: P): R;
visitLoopExpr?(expr: Expr, kind: LoopExpr, ...p: P): R;
visitWhileExpr?(expr: Expr, kind: WhileExpr, ...p: P): R;
visitForExpr?(expr: Expr, kind: ForExpr, ...p: P): R;
visitCForExpr?(expr: Expr, kind: CForExpr, ...p: P): R;
visitPat?(pat: Pat, ...p: P): R;
visitErrorPat?(pat: Pat, ...p: P): R;
visitBindPat?(pat: Pat, kind: BindPat, ...p: P): R;
visitPathPat?(pat: Pat, kind: PathPat, ...p: P): R;
visitTy?(ty: Ty, ...p: P): R;
visitErrorTy?(ty: Ty, ...p: P): R;
visitNullTy?(ty: Ty, ...p: P): R;
visitIntTy?(ty: Ty, ...p: P): R;
visitBoolTy?(ty: Ty, ...p: P): R;
visitStrTy?(ty: Ty, ...p: P): R;
visitPathTy?(ty: Ty, kind: PathTy, ...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;
visitTupleTy?(ty: Ty, kind: TupleTy, ...p: P): R;
visitAnonStructTy?(ty: Ty, kind: AnonStructTy, ...p: P): R;
visitBlock?(block: Block, ...p: P): R;
visitPath?(path: Path, ...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;
visitItem(v, kind.item, ...p);
return;
case "let":
if (v.visitLetStmt?.(stmt, kind, ...p) === "stop") return;
visitPat(v, kind.pat, ...p);
if (kind.ty) {
visitTy(v, kind.ty, ...p);
}
if (kind.expr) {
visitExpr(v, kind.expr, ...p);
}
return;
case "return":
if (v.visitReturnStmt?.(stmt, kind, ...p) === "stop") return;
if (kind.expr) {
visitExpr(v, kind.expr, ...p);
}
return;
case "break":
if (v.visitBreakStmt?.(stmt, kind, ...p) === "stop") return;
kind.expr && visitExpr(v, kind.expr, ...p);
return;
case "continue":
if (v.visitContinueStmt?.(stmt, ...p) === "stop") return;
return;
case "assign":
if (v.visitAssignStmt?.(stmt, kind, ...p) === "stop") return;
visitExpr(v, kind.subject, ...p);
visitExpr(v, kind.value, ...p);
return;
case "expr":
if (v.visitExprStmt?.(stmt, kind, ...p) === "stop") return;
visitExpr(v, kind.expr, ...p);
return;
}
exhausted(kind);
}
export function visitItem<
P extends PM = [],
>(
v: Visitor<P>,
item: Item,
...p: P
) {
visitIdent(v, item.ident, ...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;
visitBlock(v, kind.block, ...p);
return;
case "mod_file":
if (v.visitModFileItem?.(item, kind, ...p) === "stop") return;
return;
case "enum":
if (v.visitEnumItem?.(item, kind, ...p) === "stop") return;
for (const variant of kind.variants) {
visitVariant(v, variant, ...p);
}
return;
case "struct":
if (v.visitStructItem?.(item, kind, ...p) === "stop") return;
visitVariantData(v, kind.data, ...p);
return;
case "fn":
if (v.visitFnItem?.(item, kind, ...p) === "stop") return;
for (const param of kind.params) {
visitParam(v, param, ...p);
}
kind.returnTy && visitTy(v, kind.returnTy, ...p);
return;
case "use":
if (v.visitUseItem?.(item, kind, ...p) === "stop") return;
return;
case "type_alias":
if (v.visitTypeAliasItem?.(item, kind, ...p) === "stop") return;
visitTy(v, kind.ty, ...p);
return;
}
exhausted(kind);
}
export function visitVariant<
P extends PM = [],
>(
v: Visitor<P>,
variant: Variant,
...p: P
) {
if (v.visitVariant?.(variant, ...p) === "stop") return;
visitIdent(v, variant.ident, ...p);
visitVariantData(v, variant.data, ...p);
}
export function visitVariantData<
P extends PM = [],
>(
v: Visitor<P>,
data: VariantData,
...p: P
) {
const dk = data.kind;
switch (dk.tag) {
case "error":
return;
case "unit":
return;
case "tuple":
for (const elem of dk.elems) {
visitVariantData(v, elem, ...p);
}
return;
case "struct":
for (const field of dk.fields) {
visitIdent(v, field.ident, ...p);
visitTy(v, field.ty, ...p);
}
return;
}
exhausted(dk);
}
export function visitGenerics<
P extends PM = [],
>(
v: Visitor<P>,
generics: Generics,
...p: P
) {
for (const param of generics.params) {
visitIdent(v, param.ident, ...p);
}
}
export function visitParam<
P extends PM = [],
>(
v: Visitor<P>,
param: Param,
...p: P
) {
visitPat(v, param.pat, ...p);
visitTy(v, param.ty, ...p);
}
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 "path":
if (v.visitPathExpr?.(expr, kind, ...p) === "stop") return;
visitPath(v, kind.path, ...p);
return;
case "null":
if (v.visitNullExpr?.(expr, ...p) === "stop") return;
return;
case "int":
if (v.visitIntExpr?.(expr, kind, ...p) === "stop") return;
return;
case "str":
if (v.visitStringExpr?.(expr, kind, ...p) === "stop") return;
return;
case "bool":
if (v.visitBoolExpr?.(expr, kind, ...p) === "stop") return;
return;
case "group":
if (v.visitGroupExpr?.(expr, kind, ...p) === "stop") return;
visitExpr(v, kind.expr, ...p);
return;
case "array":
if (v.visitArrayExpr?.(expr, kind, ...p) === "stop") return;
for (const expr of kind.exprs) {
visitExpr(v, expr, ...p);
}
return;
case "repeat":
if (v.visitRepeatExpr?.(expr, kind, ...p) === "stop") return;
visitExpr(v, kind.expr, ...p);
visitExpr(v, kind.length, ...p);
return;
case "struct":
if (v.visitStructExpr?.(expr, kind, ...p) === "stop") return;
if (kind.path) {
visitPath(v, kind.path, ...p);
}
for (const field of kind.fields) {
visitIdent(v, field.ident, ...p);
visitExpr(v, field.expr, ...p);
}
return;
case "ref":
if (v.visitRefExpr?.(expr, kind, ...p) === "stop") return;
visitExpr(v, kind.expr, ...p);
return;
case "deref":
if (v.visitDerefExpr?.(expr, kind, ...p) === "stop") return;
visitExpr(v, kind.expr, ...p);
return;
case "elem":
if (v.visitElemExpr?.(expr, kind, ...p) === "stop") return;
visitExpr(v, kind.expr, ...p);
return;
case "field":
if (v.visitFieldExpr?.(expr, kind, ...p) === "stop") return;
v.visitExpr?.(kind.expr, ...p);
v.visitIdent?.(kind.ident, ...p);
return;
case "index":
if (v.visitIndexExpr?.(expr, kind, ...p) === "stop") return;
visitExpr(v, kind.expr, ...p);
visitExpr(v, kind.index, ...p);
return;
case "call":
if (v.visitCallExpr?.(expr, kind, ...p) === "stop") return;
visitExpr(v, kind.expr, ...p);
for (const expr of kind.args) {
visitExpr(v, expr, ...p);
}
return;
case "unary":
if (v.visitUnaryExpr?.(expr, kind, ...p) === "stop") return;
visitExpr(v, kind.expr, ...p);
return;
case "binary":
if (v.visitBinaryExpr?.(expr, kind, ...p) === "stop") return;
visitExpr(v, kind.left, ...p);
visitExpr(v, kind.right, ...p);
return;
case "block":
if (v.visitBlockExpr?.(expr, kind, ...p) === "stop") return;
visitBlock(v, kind.block, ...p);
return;
case "if":
if (v.visitIfExpr?.(expr, kind, ...p) === "stop") return;
visitExpr(v, kind.cond, ...p);
visitExpr(v, kind.truthy, ...p);
if (kind.falsy) {
visitExpr(v, kind.falsy, ...p);
}
return;
case "loop":
if (v.visitLoopExpr?.(expr, kind, ...p) === "stop") return;
visitExpr(v, kind.body, ...p);
return;
case "while":
if (v.visitWhileExpr?.(expr, kind, ...p) === "stop") return;
visitExpr(v, kind.cond, ...p);
visitExpr(v, kind.body, ...p);
return;
case "for":
if (v.visitForExpr?.(expr, kind, ...p) === "stop") return;
visitPat(v, kind.pat, ...p);
visitExpr(v, kind.expr, ...p);
visitExpr(v, kind.body, ...p);
return;
case "c_for":
if (v.visitCForExpr?.(expr, kind, ...p) === "stop") return;
if (kind.decl) {
visitStmt(v, kind.decl, ...p);
}
if (kind.cond) {
visitExpr(v, kind.cond, ...p);
}
if (kind.incr) {
visitStmt(v, kind.incr, ...p);
}
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;
visitIdent(v, kind.ident, ...p);
return;
case "path":
if (v.visitPathPat?.(pat, kind, ...p) === "stop") return;
visitPath(v, kind.path, ...p);
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 "null":
if (v.visitNullTy?.(ty, ...p) === "stop") return;
return;
case "int":
if (v.visitIntTy?.(ty, ...p) === "stop") return;
return;
case "bool":
if (v.visitBoolTy?.(ty, ...p) === "stop") return;
return;
case "str":
if (v.visitStrTy?.(ty, ...p) === "stop") return;
return;
case "path":
if (v.visitPathTy?.(ty, kind, ...p) === "stop") return;
v.visitPath?.(kind.path, ...p);
return;
case "ref":
if (v.visitRefTy?.(ty, kind, ...p) === "stop") return;
v.visitTy?.(kind.ty, ...p);
return;
case "ptr":
if (v.visitPtrTy?.(ty, kind, ...p) === "stop") return;
v.visitTy?.(kind.ty, ...p);
return;
case "slice":
if (v.visitSliceTy?.(ty, kind, ...p) === "stop") return;
v.visitTy?.(kind.ty, ...p);
return;
case "array":
if (v.visitArrayTy?.(ty, kind, ...p) === "stop") return;
v.visitTy?.(kind.ty, ...p);
v.visitExpr?.(kind.length, ...p);
return;
case "anon_struct":
if (v.visitAnonStructTy?.(ty, kind, ...p) === "stop") return;
for (const field of kind.fields) {
v.visitIdent?.(field.ident, ...p);
v.visitTy?.(field.ty, ...p);
}
return;
}
exhausted(kind);
}
export function visitBlock<
P extends PM = [],
>(
v: Visitor<P>,
block: Block,
...p: P
) {
if (v.visitBlock?.(block, ...p) === "stop") return;
for (const stmt of block.stmts) {
visitStmt(v, stmt, ...p);
}
if (block.expr) {
visitExpr(v, block.expr, ...p);
}
}
export function visitPath<
P extends PM = [],
>(
v: Visitor<P>,
path: Path,
...p: P
) {
if (v.visitPath?.(path, ...p) === "stop") return;
for (const seg of path.segments) {
visitIdent(v, seg.ident, ...p);
}
}
export function visitIdent<
P extends PM = [],
>(
v: Visitor<P>,
ident: Ident,
...p: P
) {
v.visitIdent?.(ident, ...p);
}

View File

@ -0,0 +1,209 @@
import * as ast from "../ast/mod.ts";
import { Ctx, File } from "../ctx.ts";
import { Span } from "../diagnostics.ts";
import { AstId, IdMap } from "../ids.ts";
import { Resols } from "../resolve/resolver.ts";
import { tyToString } from "../ty/to_string.ts";
import { Ty } from "../ty/ty.ts";
import { exhausted, Res, todo } from "../util.ts";
export class Checker {
private itemTys = new IdMap<AstId, Ty>();
private exprTys = new IdMap<AstId, Ty>();
private tyTys = new IdMap<AstId, Ty>();
private currentFile: File;
public constructor(
private ctx: Ctx,
private entryFileAst: ast.File,
private resols: Resols,
) {
this.currentFile = ctx.entryFile();
}
private checkBlock(block: ast.Block, expected: Ty): Ty {
this.checkStmts(block.stmts);
return block.expr &&
this.checkExpr(block.expr, expected) ||
Ty({ tag: "null" });
}
private checkStmts(stmts: ast.Stmt[]) {
}
public fnItemTy(item: ast.Item, kind: ast.FnItem): Ty {
return this.itemTys.get(item.id) || this.checkFnItem(item, kind);
}
private checkFnItem(item: ast.Item, kind: ast.FnItem): Ty {
const params = kind.params.map((param) => this.tyTy(param.ty));
const returnTy = kind.returnTy && this.tyTy(kind.returnTy) ||
Ty({ tag: "null" });
return Ty({ tag: "fn", item, kind, params, returnTy });
}
public exprTy(expr: ast.Expr): Ty {
return this.exprTys.get(expr.id) ||
this.checkExpr(expr, Ty({ tag: "unknown" }));
}
private checkExpr(expr: ast.Expr, expected: Ty): Ty {
const k = expr.kind;
switch (k.tag) {
case "error":
return Ty({ tag: "error" });
case "path":
return this.checkPathExpr(expr, k, expected);
case "null":
return todo();
case "int":
return todo();
case "bool":
return todo();
case "str":
return todo();
case "group":
return todo();
case "array":
return todo();
case "repeat":
return todo();
case "struct":
return todo();
case "ref":
return todo();
case "deref":
return todo();
case "elem":
return todo();
case "field":
return todo();
case "index":
return todo();
case "call":
return todo();
case "unary":
return todo();
case "binary":
return todo();
case "block":
return todo();
case "if":
return todo();
case "loop":
return todo();
case "while":
return todo();
case "for":
return todo();
case "c_for":
return todo();
}
exhausted(k);
}
private checkPathExpr(
expr: ast.Expr,
kind: ast.PathExpr,
expected: Ty,
): Ty {
const res = this.resols.exprRes(expr.id);
switch (res.kind.tag) {
case "error":
return Ty({ tag: "error" });
case "fn": {
const fn = res.kind.item;
const ty = this.fnItemTy(fn, res.kind.kind);
const resu = this.resolveTys(ty, expected);
if (!resu.ok) {
this.report(resu.val, expr.span);
return Ty({ tag: "error" });
}
return resu.val;
}
case "local": {
const ty = this.exprTy(expr);
const resu = this.resolveTys(ty, expected);
if (!resu.ok) {
this.report(resu.val, expr.span);
return Ty({ tag: "error" });
}
return resu.val;
}
}
exhausted(res.kind);
}
private tyTy(ty: ast.Ty): Ty {
return this.tyTys.get(ty.id) ||
this.checkTy(ty);
}
private checkTy(ty: ast.Ty): Ty {
const k = ty.kind;
switch (k.tag) {
case "error":
return Ty({ tag: "error" });
case "null":
case "int":
return Ty({ tag: "int" });
case "bool":
case "str":
case "path":
case "ref":
case "ptr":
case "slice":
case "array":
case "anon_struct":
return todo(k);
}
exhausted(k);
}
private report(msg: string, span: Span) {
this.ctx.report({
severity: "error",
file: this.currentFile,
span,
msg,
});
}
private resolveTys(a: Ty, b: Ty): Res<Ty, string> {
const as = tyToString(this.ctx, a);
const bs = tyToString(this.ctx, b);
const incompat = () =>
Res.Err(
`type '${as}' not compatible with type '${bs}'`,
);
switch (a.kind.tag) {
case "error":
return Res.Ok(b);
case "unknown":
return Res.Ok(b);
case "null": {
if (b.kind.tag !== "null") {
return incompat();
}
return Res.Ok(a);
}
case "int": {
if (b.kind.tag !== "int") {
return incompat();
}
return Res.Ok(a);
}
case "fn": {
if (b.kind.tag !== "fn") {
return incompat();
}
if (b.kind.item.id === a.kind.item.id) {
return incompat();
}
return Res.Ok(a);
}
}
exhausted(a.kind);
}
}

141
slige/compiler/ctx.ts Normal file
View File

@ -0,0 +1,141 @@
import * as ast from "./ast/mod.ts";
import {
Pos,
prettyPrintReport,
printStackTrace,
Report,
Span,
} from "./diagnostics.ts";
import { DefId, File, IdentId, IdMap, Ids } from "./ids.ts";
export { type File } from "./ids.ts";
export class Ctx {
private fileIds = new Ids<File>();
private files = new IdMap<File, FileInfo>();
private _entryFile?: File;
private reports: Report[] = [];
public fileHasChildWithIdent(file: File, childIdent: string): boolean {
return this.files.get(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(file, {
ident,
absPath,
relPath,
superFile,
subFiles: new Map(),
text,
});
this._entryFile = this._entryFile ?? file;
if (superFile) {
this.files.get(superFile)!
.subFiles.set(ident, file);
}
return file;
}
public addFileAst(file: File, ast: ast.File) {
this.files.get(file)!.ast = ast;
}
public fileInfo(file: File): FileInfo {
return this.files.get(file)!;
}
public entryFile(): File {
if (!this._entryFile) {
throw new Error();
}
return this._entryFile;
}
public iterFiles(): IteratorObject<File> {
return this.files.keys();
}
//
private identIds = new Ids<IdentId>();
private identStringToId = new Map<string, IdentId>();
private identIdToString = new IdMap<IdentId, string>();
public internIdent(ident: string): IdentId {
if (this.identStringToId.has(ident)) {
return this.identStringToId.get(ident)!;
}
const id = this.identIds.nextThenStep();
this.identStringToId.set(ident, id);
this.identIdToString.set(id, ident);
return id;
}
public identText(ident: IdentId): string {
return this.identIdToString.get(ident)!;
}
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();
}
}
}
public printAsts() {
for (const [_, info] of this.files) {
console.log(`${info.absPath}:`);
console.log(JSON.stringify(info.ast!, null, 2));
}
}
}
export type FileInfo = {
ident: string;
absPath: string;
relPath: string;
superFile?: File;
subFiles: Map<string, File>;
text: string;
ast?: ast.File;
};

View File

@ -0,0 +1,5 @@
{
"fmt": {
"indentWidth": 4
}
}

23
slige/compiler/deno.lock generated Normal file
View File

@ -0,0 +1,23 @@
{
"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==",
"dependencies": [
"undici-types"
]
},
"undici-types@6.19.8": {
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
}
}
}

View File

@ -0,0 +1,138 @@
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 const Span = {
fromto: ({ begin }: Span, { end }: Span): Span => ({ begin, end }),
} as const;
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,
);
}
}

91
slige/compiler/ids.ts Normal file
View File

@ -0,0 +1,91 @@
//
export type File = IdBase & { readonly _: unique symbol };
export type IdentId = IdBase & { readonly _: unique symbol };
export type AstId = IdBase & { readonly _: unique symbol };
export type DefId = IdBase & { readonly _: unique symbol };
//
export type IdBase = { rawId: number };
export type IdRaw<IdType extends IdBase> = IdType["rawId"];
export const idRaw = <IdType extends IdBase>(id: IdType): IdRaw<IdType> =>
id.rawId;
export const idFromRaw = <IdType extends IdBase>(
rawId: IdRaw<IdType>,
): IdType => ({ rawId } as IdType);
export class Ids<IdType extends IdBase> {
private next = 0;
public nextThenStep(): IdType {
const rawId = this.next;
this.next += 1;
return idFromRaw(rawId);
}
}
export class IdMap<Id extends IdBase, V> implements Map<Id, V> {
private map = new Map<IdRaw<Id>, V>();
set(id: Id, val: V) {
this.map.set(idRaw(id), val);
return this;
}
get(id: Id): V | undefined {
return this.map.get(idRaw(id));
}
has(id: Id): boolean {
return this.map.has(idRaw(id));
}
keys(): MapIterator<Id> {
return this.map.keys()
.map((rawId) => idFromRaw(rawId));
}
clear(): void {
this.map.clear();
}
delete(id: Id): boolean {
return this.map.delete(idRaw(id));
}
forEach(
callbackfn: (value: V, key: Id, map: Map<Id, V>) => void,
thisArg?: unknown,
): void {
this.map.forEach(
(value, key, _map) => callbackfn(value, idFromRaw(key), this),
thisArg,
);
}
get size(): number {
return this.map.size;
}
entries(): MapIterator<[Id, V]> {
return this.map.entries()
.map(([rawId, v]) => [idFromRaw(rawId), v]);
}
values(): MapIterator<V> {
return this.map.values();
}
[Symbol.iterator](): MapIterator<[Id, V]> {
return this.map[Symbol.iterator]()
.map(([rawId, v]) => [idFromRaw(rawId), v]);
}
get [Symbol.toStringTag](): string {
return this.map[Symbol.toStringTag];
}
}

134
slige/compiler/main.ts Normal file
View File

@ -0,0 +1,134 @@
import * as path from "jsr:@std/path";
import { Parser } from "./parse/parser.ts";
import * as ast from "./ast/mod.ts";
import { Ctx, File } from "./ctx.ts";
import { Resolver } from "./resolve/resolver.ts";
import { Checker } from "./check/checker.ts";
import { AstLowerer } from "./middle/ast_lower.ts";
async function main() {
const filePath = Deno.args[0];
const compiler = new PackCompiler(filePath, new NullEmitter());
compiler.enableDebug();
await compiler.compile();
}
export type Pack = {
rootMod: Mod;
};
export type Mod = null;
export interface PackEmitter {
emit(pack: Pack): void;
}
export class NullEmitter implements PackEmitter {
emit(pack: Pack): void {
}
}
export class PackCompiler {
private ctx = new Ctx();
private astCx = new ast.Cx();
public constructor(
private entryFilePath: string,
private emitter: PackEmitter,
) {}
public async compile() {
const [entryFile, entryFileAst] = await FileTreeAstCollector
.fromEntryFile(this.ctx, this.astCx, this.entryFilePath)
.collect();
const resols = new Resolver(this.ctx, entryFileAst).resolve();
const checker = new Checker(this.ctx, entryFileAst, resols);
new AstLowerer(this.ctx, resols, checker, entryFileAst).lower();
}
public enableDebug() {
this.ctx.enableReportImmediately = true;
this.ctx.enableStacktrace = true;
}
}
type _P = { file: File };
export class FileTreeAstCollector implements ast.Visitor<[_P]> {
private subFilePromise = Promise.resolve();
private constructor(
private ctx: Ctx,
private astCx: ast.Cx,
private superFile: File | undefined,
private ident: string,
private absPath: string,
private relPath: string,
) {}
public static fromEntryFile(
ctx: Ctx,
astCx: ast.Cx,
entryFilePath: string,
): FileTreeAstCollector {
return new FileTreeAstCollector(
ctx,
astCx,
undefined,
"root",
entryFilePath,
entryFilePath,
);
}
public async collect(): Promise<[File, ast.File]> {
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(this.ctx, this.astCx, file).parse();
this.ctx.addFileAst(file, fileAst);
ast.visitFile(this, fileAst, { file });
await this.subFilePromise;
return [file, fileAst];
}
visitModFileItem(
item: ast.Item,
kind: ast.ModFileItem,
{ file }: _P,
): ast.VisitRes {
const ident = this.ctx.identText(item.ident.id);
const { filePath: relPath } = kind;
const absPath = path.join(path.dirname(this.absPath), relPath);
this.subFilePromise = this.subFilePromise
.then(async () => {
if (this.ctx.fileHasChildWithIdent(file, ident)) {
this.ctx.report({
severity: "fatal",
msg: `module '${ident}' already declared`,
file,
span: item.span,
});
Deno.exit(1);
}
const [modFile, modAst] = await new FileTreeAstCollector(
this.ctx,
this.astCx,
file,
ident,
absPath,
relPath,
)
.collect();
kind.file = modFile;
kind.ast = modAst;
});
return "stop";
}
}
main();

View File

@ -0,0 +1,245 @@
import * as ast from "../ast/mod.ts";
import { Checker } from "../check/checker.ts";
import { Ctx } from "../ctx.ts";
import { IdMap, Ids } from "../ids.ts";
import { LocalId as ReLocalId, Resols } from "../resolve/resolver.ts";
import { Ty } from "../ty/ty.ts";
import { exhausted, Res, todo } from "../util.ts";
import { BinaryType, Operand, StmtKind, TerKind } from "./mir.ts";
import { Block, BlockId, Fn, Local, LocalId, RVal, Stmt, Ter } from "./mir.ts";
export class AstLowerer implements ast.Visitor {
public constructor(
private ctx: Ctx,
private re: Resols,
private ch: Checker,
private ast: ast.File,
) {}
public lower() {
ast.visitFile(this, this.ast);
}
visitFnItem(item: ast.Item, kind: ast.FnItem): ast.VisitRes {
new FnLowerer(this.ctx, this.re, this.ch, item, kind).lower();
}
}
export class FnLowerer {
private localIds = new Ids<LocalId>();
private locals = new IdMap<LocalId, Local>();
private blockIds = new Ids<BlockId>();
private blocks = new IdMap<BlockId, Block>();
private currentBlock?: Block;
private reLocals = new IdMap<ReLocalId, LocalId>();
public constructor(
private ctx: Ctx,
private re: Resols,
private ch: Checker,
private item: ast.Item,
private kind: ast.FnItem,
) {}
public lower(): Res<Fn, string> {
const entry = this.pushBlock();
const fnTy = this.ch.fnItemTy(this.item, this.kind);
const returnPlace = this.local(fnTy);
const returnVal = this.lowerBlock(this.kind.body!);
this.addStmt({
tag: "assign",
place: { local: returnPlace, proj: [] },
rval: returnVal,
});
this.setTer({ tag: "return" });
return Res.Ok({
label: this.ctx.identText(this.item.ident.id),
locals: this.locals,
blocks: this.blocks,
entry,
});
}
private lowerBlock(block: ast.Block): RVal {
for (const stmt of block.stmts) {
this.lowerStmt(stmt);
}
return block.expr && this.lowerExpr(block.expr) || {
tag: "use",
operand: { tag: "const", val: { tag: "null" } },
};
}
private lowerStmt(stmt: ast.Stmt) {
const k = stmt.kind;
switch (k.tag) {
case "error":
return { tag: "error" };
case "item":
case "let":
case "return":
case "break":
case "continue":
case "assign":
case "expr":
return todo();
}
exhausted(k);
}
private lowerExpr(expr: ast.Expr): RVal {
const k = expr.kind;
switch (k.tag) {
case "error":
return { tag: "error" };
case "path":
return this.lowerPathExpr(expr, k);
case "null":
case "int":
case "bool":
case "str":
case "group":
case "array":
case "repeat":
case "struct":
case "ref":
case "deref":
case "elem":
case "field":
case "index":
case "call":
case "unary":
return todo(k.tag);
case "binary":
return this.lowerBinaryExpr(expr, k);
case "block":
case "if":
case "loop":
case "while":
case "for":
case "c_for":
return todo(k.tag);
}
exhausted(k);
}
private lowerPathExpr(expr: ast.Expr, kind: ast.PathExpr): RVal {
const re = this.re.exprRes(expr.id);
switch (re.kind.tag) {
case "error":
return { tag: "error" };
case "fn":
return todo();
case "local": {
const ty = this.ch.exprTy(expr);
const local = this.local(ty);
this.reLocals.set(re.kind.id, local);
const isCopyable = (() => {
switch (ty.kind.tag) {
case "error":
case "unknown":
return false;
case "null":
case "int":
return true;
case "fn":
return false;
}
exhausted(ty.kind);
})();
return {
tag: "use",
operand: {
tag: isCopyable ? "copy" : "move",
place: { local, proj: [] },
},
};
}
}
exhausted(re.kind);
}
private lowerBinaryExpr(expr: ast.Expr, kind: ast.BinaryExpr): RVal {
const left = this.rvalAsOperand(
this.lowerExpr(kind.left),
this.ch.exprTy(kind.left),
);
const right = this.rvalAsOperand(
this.lowerExpr(kind.right),
this.ch.exprTy(kind.right),
);
const binaryType = ((kind): BinaryType => {
switch (kind.binaryType) {
case "+":
return "add";
case "-":
return "sub";
case "*":
return "mul";
case "/":
return "div";
case "==":
return "eq";
case "!=":
return "ne";
case "<":
return "lt";
case ">":
return "lte";
case "<=":
return "lte";
case ">=":
return "gte";
case "or":
return "or";
case "and":
return "and";
}
return todo(kind.binaryType);
})(kind);
return { tag: "binary", binaryType, left, right };
}
private rvalAsOperand(rval: RVal, ty: Ty): Operand {
const local = this.local(ty);
this.addStmt({ tag: "assign", place: { local, proj: [] }, rval });
return { tag: "move", place: { local, proj: [] } };
}
private local(ty: Ty): LocalId {
const id = this.localIds.nextThenStep();
this.locals.set(id, { id, ty });
return id;
}
private pushBlock(): BlockId {
const id = this.blockIds.nextThenStep();
const block: Block = {
id,
stmts: [],
terminator: { kind: { tag: "unset" } },
};
this.blocks.set(id, block);
this.currentBlock = block;
return id;
}
private setTer(kind: TerKind) {
this.currentBlock!.terminator = { kind };
}
private addStmt(kind: StmtKind) {
this.currentBlock!.stmts.push({ kind });
}
}
function unpack(blocks: Block[], val: [Block[], RVal]): [Block[], RVal] {
return [[...blocks, ...val[0]], val[1]];
}

View File

@ -0,0 +1,115 @@
import { Span } from "../diagnostics.ts";
import { IdBase, IdMap } from "../ids.ts";
import { Ty } from "../ty/ty.ts";
export type Fn = {
label: string;
locals: IdMap<LocalId, Local>;
blocks: IdMap<BlockId, Block>;
entry: BlockId;
};
export type LocalId = IdBase & { readonly _: unique symbol };
export type Local = {
id: LocalId;
ty: Ty;
};
export type BlockId = IdBase & { readonly _: unique symbol };
export type Block = {
id: BlockId;
stmts: Stmt[];
terminator: Ter;
};
export type Stmt = {
kind: StmtKind;
};
export type StmtKind =
| { tag: "error" }
| { tag: "assign"; place: Place; rval: RVal }
| { tag: "fake_read"; place: Place }
| { tag: "deinit"; place: Place }
| { tag: "live"; local: LocalId }
| { tag: "dead"; local: LocalId }
| { tag: "mention"; place: Place };
export type Ter = {
kind: TerKind;
};
export type TerKind =
| { tag: "unset" }
| { tag: "goto"; target: BlockId }
| {
tag: "switch";
discr: Operand;
targets: SwitchTarget[];
otherwise: BlockId;
}
| { tag: "return" }
| { tag: "unreachable" }
| { tag: "drop"; place: Place; target: BlockId }
| { tag: "call"; func: Operand; args: Operand[]; dest: Operand };
export type SwitchTarget = {
value: number;
target: BlockId;
};
export type Place = {
local: LocalId;
proj: ProjElem[];
};
// https://doc.rust-lang.org/beta/nightly-rustc/rustc_middle/mir/type.PlaceElem.html
export type ProjElem =
| { tag: "deref" }
| { tag: "repeat" }
| { tag: "field"; fieldIdx: number }
| { tag: "index"; local: LocalId }
| { tag: "downcast"; variantIdx: number };
// https://doc.rust-lang.org/beta/nightly-rustc/rustc_middle/mir/enum.Rvalue.html
export type RVal =
| { tag: "error" }
| { tag: "use"; operand: Operand }
| { tag: "repeat"; operand: Operand; length: Const }
| { tag: "ref"; place: Place; mut: boolean }
| { tag: "ptr"; place: Place; mut: boolean }
| { tag: "binary"; binaryType: BinaryType; left: Operand; right: Operand }
| { tag: "unary"; unaryType: UnaryType; operand: Operand };
export type BinaryType =
| "add"
| "sub"
| "mul"
| "div"
| "rem"
| "xor"
| "and"
| "or"
| "shl"
| "shr"
| "eq"
| "ne"
| "lt"
| "lte"
| "gt"
| "gte";
export type UnaryType = "not" | "neg";
export type Operand =
| { tag: "copy"; place: Place }
| { tag: "move"; place: Place }
| { tag: "const"; val: Const };
export type Const =
| { tag: "null" }
| { tag: "int"; value: number }
| { tag: "bool"; value: boolean }
| { tag: "string"; value: string };

View File

@ -0,0 +1,339 @@
import { Ctx, File } from "../ctx.ts";
import { Pos, Span } from "../diagnostics.ts";
import { ControlFlow, range } from "../util.ts";
import { Token, TokenIter } from "./token.ts";
export class Lexer implements TokenIter {
private idx = 0;
private line = 1;
private col = 1;
private text: string;
public constructor(
private ctx: Ctx,
private file: File,
) {
this.text = ctx.fileInfo(file).text;
}
next(): Token | null {
if (this.done()) {
return null;
}
let cf: ControlFlow<Token>;
if (
cf = this.lexWithTail(
(span) => this.token("whitespace", span),
/[ \t\r\n]/,
), cf.break
) {
return cf.val;
}
if (
cf = this.lexWithTail(
(span, val) => {
return keywords.has(val)
? this.token(val, span)
: this.token("ident", span, {
type: "ident",
identId: this.ctx.internIdent(val),
identText: val,
});
},
/[a-zA-Z_]/,
/[a-zA-Z0-9_]/,
), cf.break
) {
return cf.val;
}
if (
cf = this.lexWithTail(
(span, val) =>
this.token("int", span, {
type: "int",
intValue: parseInt(val),
}),
/[1-9]/,
/[0-9]/,
), cf.break
) {
return cf.val;
}
const begin = this.pos();
let end = begin;
const pos = begin;
if (this.test("0")) {
this.step();
if (!this.done() && this.test(/[0-9]/)) {
this.report("invalid number", pos);
return this.token("error", { begin, end });
}
return this.token("int", { begin, end }, {
type: "int",
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", { begin, end });
}
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", { begin, end });
}
this.step();
return this.token("int", { begin, end }, {
type: "int",
intValue: value.charCodeAt(0),
});
}
if (this.test('"')) {
this.step();
let value = "";
while (!this.done() && !this.test('"')) {
if (this.test("\\")) {
this.step();
if (this.done()) {
break;
}
value += {
n: "\n",
t: "\t",
"0": "\0",
}[this.current()] ?? this.current();
} else {
value += this.current();
}
this.step();
}
if (this.done() || !this.test('"')) {
this.report("unclosed/malformed string", pos);
return this.token("error", { begin, end });
}
this.step();
return this.token("str", { begin, end }, {
type: "str",
stringValue: value,
});
}
if (this.test("/")) {
this.step();
if (this.test("/")) {
while (!this.done() && !this.test("\n")) {
end = this.pos();
this.step();
}
return this.token("comment", { begin, end });
}
if (this.test("*")) {
end = this.pos();
this.step();
let depth = 1;
let last: string | undefined = undefined;
while (!this.done() && depth > 0) {
if (last === "*" && this.current() === "/") {
depth -= 1;
last = undefined;
} else if (last === "/" && this.current() === "*") {
depth += 1;
last = undefined;
} else {
last = this.current();
}
end = this.pos();
this.step();
}
if (depth !== 0) {
this.report("unclosed/malformed multiline comment", pos);
return this.token("comment", { begin, end });
}
}
return this.token("/", { begin, end });
}
const match = this.text.slice(this.idx).match(
new RegExp(`^(${
staticTokenRes
.map((tok) => tok.length > 1 ? `(?:${tok})` : tok)
.join("|")
})`),
);
if (match) {
for (const _ of range(match[1].length)) {
end = this.pos();
this.step();
}
return this.token(match[1], { begin, end });
}
this.report(`illegal character '${this.current()}'`, pos);
this.step();
return this.next();
}
private lexWithTail<R>(
builder: (span: Span, val: string) => R,
startPat: RegExp,
tailPat = startPat,
): ControlFlow<R> {
const begin = this.pos();
if (!this.test(startPat)) {
return ControlFlow.Continue(undefined);
}
let end = begin;
let val = this.current();
this.step();
while (this.test(tailPat)) {
end = begin;
val += this.current();
this.step();
}
return ControlFlow.Break(builder({ begin, end }, val));
}
private done(): boolean {
return this.idx >= this.text.length;
}
private current(): string {
return this.text[this.idx];
}
private step() {
if (this.done()) {
return;
}
if (this.current() === "\n") {
this.line += 1;
this.col = 1;
} else {
this.col += 1;
}
this.idx += 1;
}
private pos(): Pos {
return {
idx: this.idx,
line: this.line,
col: this.col,
};
}
private token(type: string, span: Span, token?: Partial<Token>): Token {
const length = span.end.idx - span.begin.idx + 1;
return { type, span, length, ...token };
}
private test(pattern: RegExp | string): boolean {
if (this.done()) {
return false;
}
if (typeof pattern === "string") {
return this.current() === pattern;
} else if (pattern.source.startsWith("^")) {
return pattern.test(this.text.slice(this.idx));
} else {
return pattern.test(this.current());
}
}
private report(msg: string, pos: Pos) {
this.ctx.report({
severity: "error",
origin: "parser",
file: this.file,
msg,
pos,
});
}
}
const keywords = new Set([
"false",
"true",
"null",
"int",
"bool",
"str",
"return",
"break",
"continue",
"let",
"mut",
"fn",
"loop",
"if",
"else",
"struct",
"enum",
"or",
"and",
"not",
"while",
"for",
"in",
"mod",
"pub",
"use",
"type_alias",
]);
const staticTokens = [
"=",
"==",
"<",
"<=",
">",
">=",
"-",
"->",
"!",
"!=",
"+",
"+=",
"-=",
":",
"::",
"::<",
"(",
")",
"{",
"}",
"[",
"]",
"<",
">",
".",
",",
":",
";",
"#",
"&",
"0",
] as const;
const staticTokenRes = staticTokens
.toSorted((a, b) => b.length - a.length)
.map((tok) => tok.split("").map((c) => `\\${c}`).join(""));

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,31 @@
import { Span } from "../diagnostics.ts";
import { IdentId } from "../ids.ts";
export type Token = {
type: string;
span: Span;
length: number;
identId?: IdentId;
identText?: string;
intValue?: number;
stringValue?: string;
};
export interface TokenIter {
next(): Token | null;
}
export class SigFilter implements TokenIter {
public constructor(private iter: TokenIter) {}
next(): Token | null {
const token = this.iter.next();
if (token === null) {
return token;
}
if (token?.type === "whitespace" || token?.type === "comment") {
return this.next();
}
return token;
}
}

View File

@ -0,0 +1,12 @@
fn add(lhs: int, rhs: int) -> int {
lhs + rhs
}
fn main() {
let a = 5;
let b = 7;
let c = add(a, b);
}

View File

@ -0,0 +1,131 @@
import * as ast from "../ast/mod.ts";
import { IdBase, IdentId, IdMap } from "../ids.ts";
import { Res } from "../util.ts";
export interface Syms {
getVal(ident: ast.Ident): Resolve;
getTy(ident: ast.Ident): Resolve;
defVal(ident: ast.Ident, kind: ResolveKind): Res<void, Redef>;
defTy(ident: ast.Ident, kind: ResolveKind): Res<void, Redef>;
}
export type Resolve = {
ident: ast.Ident;
kind: ResolveKind;
};
export type LocalId = IdBase & { readonly _: unique symbol };
export type ResolveKind =
| { tag: "error" }
| { tag: "fn"; item: ast.Item; kind: ast.FnItem }
| { tag: "local"; id: LocalId };
export const ResolveError = (ident: ast.Ident): Resolve => ({
ident,
kind: { tag: "error" },
});
export type Redef = {
ident: ast.Ident;
};
export class SymsOneNsTab {
private defs = new IdMap<IdentId, Resolve>();
public get(ident: ast.Ident): Resolve | undefined {
return this.defs.get(ident.id)!;
}
public def(ident: ast.Ident, kind: ResolveKind): Res<void, Redef> {
if (this.defs.has(ident.id)) {
return Res.Err({ ident: this.defs.get(ident.id)!.ident });
}
this.defs.set(ident.id, { ident, kind });
return Res.Ok(undefined);
}
}
export class SymsNsTab {
private vals = new SymsOneNsTab();
private tys = new SymsOneNsTab();
public getVal(ident: ast.Ident): Resolve | undefined {
return this.vals.get(ident);
}
public getTy(ident: ast.Ident): Resolve | undefined {
return this.tys.get(ident);
}
public defVal(ident: ast.Ident, kind: ResolveKind): Res<void, Redef> {
return this.vals.def(ident, kind);
}
public defTy(ident: ast.Ident, kind: ResolveKind): Res<void, Redef> {
return this.tys.def(ident, kind);
}
}
export class RootSyms implements Syms {
private syms = new SymsNsTab();
getVal(ident: ast.Ident): Resolve {
return this.syms.getVal(ident) || ResolveError(ident);
}
getTy(ident: ast.Ident): Resolve {
return this.syms.getTy(ident) || ResolveError(ident);
}
defVal(ident: ast.Ident, kind: ResolveKind): Res<void, Redef> {
return this.syms.defVal(ident, kind);
}
defTy(ident: ast.Ident, kind: ResolveKind): Res<void, Redef> {
return this.syms.defTy(ident, kind);
}
}
export class FnSyms implements Syms {
private syms = new SymsNsTab();
public constructor(
private parent: Syms,
) {}
getVal(ident: ast.Ident): Resolve {
const res = this.syms.getVal(ident) || this.parent.getVal(ident);
if (res.kind.tag === "local") {
return ResolveError(ident);
}
return res;
}
getTy(ident: ast.Ident): Resolve {
return this.syms.getTy(ident) || this.parent.getTy(ident);
}
defVal(ident: ast.Ident, kind: ResolveKind): Res<void, Redef> {
return this.syms.defVal(ident, kind);
}
defTy(ident: ast.Ident, kind: ResolveKind): Res<void, Redef> {
return this.syms.defTy(ident, kind);
}
}
export class LocalSyms implements Syms {
private syms = new SymsNsTab();
public constructor(
private parent: Syms,
) {}
getVal(ident: ast.Ident): Resolve {
return this.syms.getVal(ident) || this.parent.getVal(ident);
}
getTy(ident: ast.Ident): Resolve {
return this.syms.getTy(ident) || this.parent.getTy(ident);
}
defVal(ident: ast.Ident, kind: ResolveKind): Res<void, Redef> {
return this.syms.defVal(ident, kind);
}
defTy(ident: ast.Ident, kind: ResolveKind): Res<void, Redef> {
return this.syms.defTy(ident, kind);
}
}

View File

@ -0,0 +1,197 @@
import * as ast from "../ast/mod.ts";
import { Ctx, File } from "../ctx.ts";
import { AstId, IdMap, Ids } from "../ids.ts";
import { exhausted, todo } from "../util.ts";
import {
FnSyms,
LocalId,
LocalSyms,
Resolve,
ResolveError,
RootSyms,
Syms,
} from "./cx.ts";
export { type LocalId } from "./cx.ts";
export class Resols {
public constructor(
private exprResols: IdMap<AstId, Resolve>,
) {}
public exprRes(id: AstId): Resolve {
if (!this.exprResols.has(id)) {
throw new Error();
}
return this.exprResols.get(id)!;
}
}
export class Resolver implements ast.Visitor {
private currentFile!: File;
private rootSyms = new RootSyms();
private syms: Syms = this.rootSyms;
private exprResols = new IdMap<AstId, Resolve>();
private localIds = new Ids<LocalId>();
public constructor(
private ctx: Ctx,
private entryFileAst: ast.File,
) {}
public resolve(): Resols {
ast.visitFile(this, this.entryFileAst);
return new Resols(
this.exprResols,
);
}
visitFile(file: ast.File): ast.VisitRes {
this.currentFile = file.file;
ast.visitStmts(this, file.stmts);
this.visitFnBodies();
return "stop";
}
visitLetStmt(stmt: ast.Stmt, kind: ast.LetStmt): ast.VisitRes {
kind.ty && ast.visitTy(this, kind.ty);
kind.expr && ast.visitExpr(this, kind.expr);
this.syms = new LocalSyms(this.syms);
ast.visitPat(this, kind.pat);
return "stop";
}
visitModBlockItem(item: ast.Item, kind: ast.ModBlockItem): ast.VisitRes {
todo();
}
visitModFileItem(item: ast.Item, kind: ast.ModFileItem): ast.VisitRes {
ast.visitFile(this, kind.ast!);
todo();
}
visitEnumItem(item: ast.Item, kind: ast.EnumItem): ast.VisitRes {
todo();
}
visitStructItem(item: ast.Item, kind: ast.StructItem): ast.VisitRes {
todo();
}
private fnBodiesToCheck: [ast.Item, ast.FnItem][] = [];
visitFnItem(item: ast.Item, kind: ast.FnItem): ast.VisitRes {
this.syms.defVal(item.ident, { tag: "fn", item, kind });
this.fnBodiesToCheck.push([item, kind]);
return "stop";
}
private visitFnBodies() {
for (const [_item, kind] of this.fnBodiesToCheck) {
const outerSyms = this.syms;
this.syms = new FnSyms(this.syms);
this.syms = new LocalSyms(this.syms);
for (const param of kind.params) {
ast.visitParam(this, param);
}
this.syms = outerSyms;
}
this.fnBodiesToCheck = [];
}
visitPathExpr(expr: ast.Expr, kind: ast.PathExpr): ast.VisitRes {
if (kind.path.segments.length === 1) {
const res = this.syms.getVal(kind.path.segments[0].ident);
switch (res.kind.tag) {
case "error":
return "stop";
case "fn":
this.exprResols.set(expr.id, res);
return "stop";
case "local":
this.exprResols.set(expr.id, res);
return "stop";
}
exhausted(res.kind);
}
const pathRes = this.resolveInnerPath(kind.path);
switch (pathRes.kind.tag) {
case "error":
todo();
return "stop";
case "fn":
todo();
return "stop";
case "local":
todo();
return "stop";
}
exhausted(pathRes.kind);
}
visitUseItem(item: ast.Item, kind: ast.UseItem): ast.VisitRes {
todo();
}
visitTypeAliasItem(item: ast.Item, kind: ast.TypeAliasItem): ast.VisitRes {
todo();
}
visitBindPat(pat: ast.Pat, kind: ast.BindPat): ast.VisitRes {
const res = this.syms.defVal(kind.ident, {
tag: "local",
id: this.localIds.nextThenStep(),
});
if (!res.ok) {
const text = this.ctx.identText(kind.ident.id);
this.ctx.report({
severity: "error",
file: this.currentFile,
span: kind.ident.span,
msg: `redefinition of value '${text}'`,
});
}
return "stop";
}
visitPathPat(pat: ast.Pat, kind: ast.PathPat): ast.VisitRes {
todo();
}
visitBlock(block: ast.Block): ast.VisitRes {
ast.visitStmts(this, block.stmts);
this.visitFnBodies();
block.expr && ast.visitExpr(this, block.expr);
return "stop";
}
private resolveInnerPath(path: ast.Path): Resolve {
const res = path.segments.slice(1, path.segments.length)
.reduce((innerRes, seg) => {
const k = innerRes.kind;
switch (k.tag) {
case "error":
return innerRes;
case "fn":
this.ctx.report({
severity: "error",
file: this.currentFile,
span: seg.ident.span,
msg: "function, not pathable",
});
return ResolveError(seg.ident);
case "local":
this.ctx.report({
severity: "error",
file: this.currentFile,
span: seg.ident.span,
msg: "local variable, not pathable",
});
return ResolveError(seg.ident);
}
exhausted(k);
}, this.syms.getTy(path.segments[0].ident));
return res;
}
}

View File

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

@ -0,0 +1,26 @@
import { Ctx } from "../ctx.ts";
import { exhausted } from "../util.ts";
import { Ty } from "./ty.ts";
export function tyToString(ctx: Ctx, ty: Ty): string {
const k = ty.kind;
switch (k.tag) {
case "error":
return `<error>`;
case "unknown":
return `<unknown>`;
case "null":
return `null`;
case "int":
return `int`;
case "fn": {
const identText = ctx.identText(k.item.ident.id);
const params = k.params
.map((param) => tyToString(ctx, param))
.join(", ");
const reTy = tyToString(ctx, k.returnTy);
return `fn ${identText}(${params}) -> ${reTy}`;
}
}
exhausted(k);
}

20
slige/compiler/ty/ty.ts Normal file
View File

@ -0,0 +1,20 @@
import * as ast from "../ast/mod.ts";
export type Ty = {
kind: TyKind;
};
export const Ty = (kind: TyKind): Ty => ({ kind });
export type TyKind =
| { tag: "error" }
| { tag: "unknown" }
| { tag: "null" }
| { tag: "int" }
| {
tag: "fn";
item: ast.Item;
kind: ast.FnItem;
params: Ty[];
returnTy: Ty;
};

63
slige/compiler/util.ts Normal file
View File

@ -0,0 +1,63 @@
export function todo<T>(...args: unknown[]): T {
const argsStr = args.map((a) => JSON.stringify(a)).join(", ");
class NotImplemented extends Error {
constructor() {
super(`todo(${argsStr})`);
this.name = "NotImplemented";
}
}
throw new NotImplemented();
}
export function exhausted<T>(...args: never[]): T {
const argsStr = args.map((a) => JSON.stringify(a)).join(", ");
class Unexhausted extends Error {
constructor() {
super(`exhausted(${argsStr})`);
this.name = "Unexhausted";
}
}
throw new Unexhausted();
}
export type Res<V, E> = Ok<V> | Err<E>;
export type Ok<V> = { ok: true; val: V };
export type Err<E> = { ok: false; val: E };
export const Ok = <V>(val: V): Ok<V> => ({ ok: true, val });
export const Err = <E>(val: E): Err<E> => ({ ok: false, val });
export const Res = { Ok, Err } as const;
export type ControlFlow<
R = undefined,
V = undefined,
> = Break<R> | Continue<V>;
export type Break<R> = { break: true; val: R };
export type Continue<V> = { break: false; val: V };
export const ControlFlow = {
Break: <R>(val: R): Break<R> => ({ break: true, val }),
Continue: <V>(val: V): Continue<V> => ({ break: false, val }),
} as const;
export const range = (length: number) => (new Array(length).fill(0));
export const strictEq = <T>(a: T, b: T): boolean => a === b;
export function arrayEq<T>(
a: T[],
b: T[],
elemCmp: (a: T, b: T) => boolean = strictEq,
): boolean {
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; ++i) {
if (!elemCmp(a[i], b[i])) {
return false;
}
}
return true;
}