mirror of
https://github.com/Mercantec-GHC/h4-projekt-gruppe-0-sm.git
synced 2025-04-27 16:24:07 +02:00
add slige compiler
This commit is contained in:
parent
3113e771e0
commit
2d1dde8d60
269
slige/compiler/ast/ast.ts
Normal file
269
slige/compiler/ast/ast.ts
Normal 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
53
slige/compiler/ast/cx.ts
Normal 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 };
|
||||
}
|
||||
}
|
3
slige/compiler/ast/mod.ts
Normal file
3
slige/compiler/ast/mod.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from "./ast.ts";
|
||||
export * from "./visitor.ts";
|
||||
export * from "./cx.ts";
|
33
slige/compiler/ast/to_string.ts
Normal file
33
slige/compiler/ast/to_string.ts
Normal 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();
|
||||
}
|
581
slige/compiler/ast/visitor.ts
Normal file
581
slige/compiler/ast/visitor.ts
Normal 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);
|
||||
}
|
209
slige/compiler/check/checker.ts
Normal file
209
slige/compiler/check/checker.ts
Normal 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
141
slige/compiler/ctx.ts
Normal 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;
|
||||
};
|
5
slige/compiler/deno.jsonc
Normal file
5
slige/compiler/deno.jsonc
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"fmt": {
|
||||
"indentWidth": 4
|
||||
}
|
||||
}
|
23
slige/compiler/deno.lock
generated
Normal file
23
slige/compiler/deno.lock
generated
Normal 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=="
|
||||
}
|
||||
}
|
||||
}
|
138
slige/compiler/diagnostics.ts
Normal file
138
slige/compiler/diagnostics.ts
Normal 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
91
slige/compiler/ids.ts
Normal 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
134
slige/compiler/main.ts
Normal 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();
|
245
slige/compiler/middle/ast_lower.ts
Normal file
245
slige/compiler/middle/ast_lower.ts
Normal 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]];
|
||||
}
|
115
slige/compiler/middle/mir.ts
Normal file
115
slige/compiler/middle/mir.ts
Normal 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 };
|
339
slige/compiler/parse/lexer.ts
Normal file
339
slige/compiler/parse/lexer.ts
Normal 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(""));
|
1271
slige/compiler/parse/parser.ts
Normal file
1271
slige/compiler/parse/parser.ts
Normal file
File diff suppressed because it is too large
Load Diff
31
slige/compiler/parse/token.ts
Normal file
31
slige/compiler/parse/token.ts
Normal 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;
|
||||
}
|
||||
}
|
12
slige/compiler/program.slg
Normal file
12
slige/compiler/program.slg
Normal 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);
|
||||
}
|
||||
|
||||
|
131
slige/compiler/resolve/cx.ts
Normal file
131
slige/compiler/resolve/cx.ts
Normal 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);
|
||||
}
|
||||
}
|
197
slige/compiler/resolve/resolver.ts
Normal file
197
slige/compiler/resolve/resolver.ts
Normal 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;
|
||||
}
|
||||
}
|
64
slige/compiler/test_diagnostics.ts
Normal file
64
slige/compiler/test_diagnostics.ts
Normal 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
|
||||
},
|
||||
});
|
26
slige/compiler/ty/to_string.ts
Normal file
26
slige/compiler/ty/to_string.ts
Normal 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
20
slige/compiler/ty/ty.ts
Normal 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
63
slige/compiler/util.ts
Normal 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;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user