mirror of
https://github.com/Mercantec-GHC/h4-projekt-gruppe-0-sm.git
synced 2025-04-28 00:34:06 +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