Compare commits
10 Commits
9cb145d97e
...
7ca864d5a9
Author | SHA1 | Date | |
---|---|---|---|
|
7ca864d5a9 | ||
|
ab5a830f66 | ||
6a5fce18fe | |||
|
7766c88512 | ||
|
cd087392b9 | ||
|
93dd4c32c8 | ||
|
e8cfd059cc | ||
|
00298d6d83 | ||
|
09c3c3277b | ||
|
d83ade33d2 |
@ -1,34 +0,0 @@
|
||||
|
||||
|
||||
|
||||
Mnemonic Arg Arg description
|
||||
----------------------------------------
|
||||
Nop
|
||||
PushNull
|
||||
PushInt int initial value
|
||||
PushString string initial value
|
||||
PushArray
|
||||
PushStruct
|
||||
PushPtr ptr pointer value
|
||||
Pop
|
||||
LoadLocal int stack position
|
||||
StoreLocal int stack position
|
||||
Call int arg count
|
||||
Return
|
||||
Jump
|
||||
JumpIfNotZero
|
||||
Add
|
||||
Subtract
|
||||
Multiply
|
||||
Divide
|
||||
Remainder
|
||||
Equal
|
||||
LessThan
|
||||
And
|
||||
Or
|
||||
Xor
|
||||
Not
|
||||
|
||||
|
||||
|
||||
|
230
compiler/ast/ast.ts
Normal file
230
compiler/ast/ast.ts
Normal file
@ -0,0 +1,230 @@
|
||||
import { Span } from "../diagnostics.ts";
|
||||
|
||||
export type File = {
|
||||
stmts: Stmt[];
|
||||
};
|
||||
|
||||
export type Stmt = {
|
||||
kind: StmtKind;
|
||||
span: Span;
|
||||
};
|
||||
|
||||
export type StmtKind =
|
||||
| { tag: "error" }
|
||||
| { tag: "item" } & ItemStmt
|
||||
| { tag: "let" } & LetStmt
|
||||
| { tag: "return" } & ReturnStmt
|
||||
| { tag: "break" } & BreakStmt
|
||||
| { tag: "continue" }
|
||||
| { tag: "assign" } & AssignStmt
|
||||
| { tag: "expr" } & ExprStmt;
|
||||
|
||||
export type ItemStmt = { item: Item };
|
||||
|
||||
export type LetStmt = {
|
||||
subject: Pat;
|
||||
expr?: Expr;
|
||||
};
|
||||
|
||||
export type ReturnStmt = { expr?: Expr };
|
||||
export type BreakStmt = { expr?: Expr };
|
||||
|
||||
export type AssignStmt = {
|
||||
assignType: AssignType;
|
||||
subject: Expr;
|
||||
value: Expr;
|
||||
};
|
||||
|
||||
export type AssignType = "=" | "+=" | "-=";
|
||||
|
||||
export type ExprStmt = { expr: Expr };
|
||||
|
||||
export type Item = {
|
||||
kind: ItemKind;
|
||||
span: Span;
|
||||
ident: Ident;
|
||||
pub: boolean;
|
||||
};
|
||||
|
||||
export type ItemKind =
|
||||
| { tag: "error" }
|
||||
| { tag: "mod_block" } & ModBlockItem
|
||||
| { tag: "mod_file" } & ModFileItem
|
||||
| { tag: "enum" } & EnumItem
|
||||
| { tag: "struct" } & StructItem
|
||||
| { tag: "fn" } & FnItem
|
||||
| { tag: "use" } & UseItem
|
||||
| { tag: "type_alias" } & TypeAliasItem;
|
||||
|
||||
export type ModBlockItem = { ident: Ident; stmts: Stmt[] };
|
||||
export type ModFileItem = { ident: Ident; filePath: string };
|
||||
export type EnumItem = { variants: Variant[] };
|
||||
export type StructItem = { data: VariantData };
|
||||
export type FnItem = { _: 0 };
|
||||
export type UseItem = { _: 0 };
|
||||
export type 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 Expr = {
|
||||
kind: ExprKind;
|
||||
span: Span;
|
||||
};
|
||||
|
||||
export type ExprKind =
|
||||
| { tag: "error" }
|
||||
| { tag: "path" } & Path
|
||||
| { tag: "null" }
|
||||
| { tag: "int" } & IntExpr
|
||||
| { tag: "bool" } & BoolExpr
|
||||
| { tag: "string" } & 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" } & Block
|
||||
| { tag: "if" } & IfExpr
|
||||
| { tag: "loop" } & LoopExpr
|
||||
| { tag: "while" } & WhileExpr
|
||||
| { tag: "for" } & ForExpr
|
||||
| { tag: "c_for" } & CForExpr;
|
||||
|
||||
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; field: 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 = { unaryType: UnaryType; left: Expr; right: Expr };
|
||||
export type IfExpr = { cond: Expr; truthy: Block; falsy?: Block };
|
||||
export type LoopExpr = { body: Block };
|
||||
export type WhileExpr = { cond: Expr; body: Block };
|
||||
export type ForExpr = { pat: Pat; expr: Expr; body: Block };
|
||||
export type CForExpr = { decl?: Stmt; cond?: Expr; incr?: Stmt; body: Block };
|
||||
|
||||
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 = {
|
||||
kind: PatKind;
|
||||
span: Span;
|
||||
};
|
||||
|
||||
export type PatKind =
|
||||
| { tag: "error" }
|
||||
| { tag: "bind" } & BindPat;
|
||||
|
||||
export type BindPat = {
|
||||
ident: Ident;
|
||||
mut: boolean;
|
||||
};
|
||||
|
||||
export type Ty = {
|
||||
kind: TyKind;
|
||||
span: Span;
|
||||
};
|
||||
|
||||
export type TyKind =
|
||||
| { tag: "error" }
|
||||
| { tag: "path" } & Path
|
||||
| { tag: "ref" } & RefTy
|
||||
| { tag: "ptr" } & PtrTy
|
||||
| { tag: "slice" } & SliceTy
|
||||
| { tag: "array" } & ArrayTy
|
||||
| { tag: "anon_struct" } & AnonStructTy;
|
||||
|
||||
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 = {
|
||||
text: string;
|
||||
span: Span;
|
||||
};
|
2
compiler/ast/mod.ts
Normal file
2
compiler/ast/mod.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./ast.ts";
|
||||
export * from "./visitor.ts";
|
288
compiler/ast/visitor.ts
Normal file
288
compiler/ast/visitor.ts
Normal file
@ -0,0 +1,288 @@
|
||||
import { exhausted } from "../util.ts";
|
||||
import {
|
||||
AnonStructTy,
|
||||
ArrayExpr,
|
||||
ArrayTy,
|
||||
AssignStmt,
|
||||
BinaryExpr,
|
||||
BindPat,
|
||||
BoolExpr,
|
||||
BreakStmt,
|
||||
CallExpr,
|
||||
CForExpr,
|
||||
DerefExpr,
|
||||
ElemExpr,
|
||||
EnumItem,
|
||||
Expr,
|
||||
ExprStmt,
|
||||
FieldExpr,
|
||||
File,
|
||||
FnItem,
|
||||
ForExpr,
|
||||
GroupExpr,
|
||||
Ident,
|
||||
IfExpr,
|
||||
IndexExpr,
|
||||
IntExpr,
|
||||
Item,
|
||||
ItemStmt,
|
||||
LetStmt,
|
||||
LoopExpr,
|
||||
ModBlockItem,
|
||||
ModFileItem,
|
||||
Pat,
|
||||
Path,
|
||||
PtrTy,
|
||||
RefExpr,
|
||||
RefTy,
|
||||
RepeatExpr,
|
||||
ReturnStmt,
|
||||
SliceTy,
|
||||
Stmt,
|
||||
StringExpr,
|
||||
StructExpr,
|
||||
StructItem,
|
||||
TupleTy,
|
||||
Ty,
|
||||
TypeAliasItem,
|
||||
UnaryExpr,
|
||||
UseItem,
|
||||
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;
|
||||
|
||||
visitExpr?(expr: Expr, ...p: P): R;
|
||||
visitErrorExpr?(expr: Expr, ...p: P): R;
|
||||
visitPathExpr?(expr: Expr, kind: Path, ...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;
|
||||
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;
|
||||
|
||||
visitTy?(ty: Ty, ...p: P): R;
|
||||
visitErrorTy?(ty: Ty, ...p: P): R;
|
||||
visitPathTy?(ty: Ty, kind: Path, ...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;
|
||||
|
||||
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;
|
||||
return;
|
||||
case "let":
|
||||
if (v.visitLetStmt?.(stmt, kind, ...p) === "stop") return;
|
||||
return;
|
||||
case "return":
|
||||
if (v.visitReturnStmt?.(stmt, kind, ...p) === "stop") return;
|
||||
return;
|
||||
case "break":
|
||||
if (v.visitBreakStmt?.(stmt, kind, ...p) === "stop") return;
|
||||
return;
|
||||
case "continue":
|
||||
if (v.visitContinueStmt?.(stmt, ...p) === "stop") return;
|
||||
return;
|
||||
case "assign":
|
||||
if (v.visitAssignStmt?.(stmt, kind, ...p) === "stop") return;
|
||||
return;
|
||||
case "expr":
|
||||
if (v.visitExprStmt?.(stmt, kind, ...p) === "stop") return;
|
||||
return;
|
||||
}
|
||||
exhausted(kind);
|
||||
}
|
||||
|
||||
export function visitItem<
|
||||
P extends PM = [],
|
||||
>(
|
||||
v: Visitor<P>,
|
||||
item: Item,
|
||||
...p: P
|
||||
) {
|
||||
const kind = item.kind;
|
||||
switch (kind.tag) {
|
||||
case "error":
|
||||
if (v.visitErrorItem?.(item, ...p) === "stop") return;
|
||||
return;
|
||||
case "mod_block":
|
||||
if (v.visitModBlockItem?.(item, kind, ...p) === "stop") return;
|
||||
return;
|
||||
case "mod_file":
|
||||
if (v.visitModFileItem?.(item, kind, ...p) === "stop") return;
|
||||
return;
|
||||
case "enum":
|
||||
if (v.visitEnumItem?.(item, kind, ...p) === "stop") return;
|
||||
return;
|
||||
case "struct":
|
||||
if (v.visitStructItem?.(item, kind, ...p) === "stop") return;
|
||||
return;
|
||||
case "fn":
|
||||
if (v.visitFnItem?.(item, kind, ...p) === "stop") return;
|
||||
return;
|
||||
case "use":
|
||||
if (v.visitUseItem?.(item, kind, ...p) === "stop") return;
|
||||
return;
|
||||
case "type_alias":
|
||||
if (v.visitTypeAliasItem?.(item, kind, ...p) === "stop") return;
|
||||
return;
|
||||
}
|
||||
exhausted(kind);
|
||||
}
|
||||
|
||||
export function visitExpr<
|
||||
P extends PM = [],
|
||||
>(
|
||||
v: Visitor<P>,
|
||||
expr: Expr,
|
||||
...p: P
|
||||
) {
|
||||
const kind = expr.kind;
|
||||
switch (kind.tag) {
|
||||
case "error":
|
||||
if (v.visitErrorExpr?.(expr, ...p) === "stop") return;
|
||||
return;
|
||||
case "ident":
|
||||
if (v.visitIdentExpr?.(expr, kind, ...p) === "stop") return;
|
||||
return;
|
||||
}
|
||||
exhausted(kind);
|
||||
}
|
||||
|
||||
export function visitPat<
|
||||
P extends PM = [],
|
||||
>(
|
||||
v: Visitor<P>,
|
||||
pat: Pat,
|
||||
...p: P
|
||||
) {
|
||||
const kind = pat.kind;
|
||||
switch (kind.tag) {
|
||||
case "error":
|
||||
if (v.visitErrorPat?.(pat, ...p) === "stop") return;
|
||||
return;
|
||||
case "bind":
|
||||
if (v.visitBindPat?.(pat, kind, ...p) === "stop") return;
|
||||
return;
|
||||
}
|
||||
exhausted(kind);
|
||||
}
|
||||
|
||||
export function visitTy<
|
||||
P extends PM = [],
|
||||
>(
|
||||
v: Visitor<P>,
|
||||
ty: Ty,
|
||||
...p: P
|
||||
) {
|
||||
const kind = ty.kind;
|
||||
switch (kind.tag) {
|
||||
case "error":
|
||||
if (v.visitErrorTy?.(ty, ...p) === "stop") return;
|
||||
return;
|
||||
case "ident":
|
||||
if (v.visitIdentTy?.(ty, kind, ...p) === "stop") return;
|
||||
return;
|
||||
}
|
||||
exhausted(kind);
|
||||
}
|
||||
|
||||
export function visitIdent<
|
||||
P extends PM = [],
|
||||
>(
|
||||
v: Visitor<P>,
|
||||
ident: Ident,
|
||||
...p: P
|
||||
) {
|
||||
v.visitIdent?.(ident, ...p);
|
||||
}
|
107
compiler/ctx.ts
Normal file
107
compiler/ctx.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import * as ast from "./ast/mod.ts";
|
||||
import { Pos, prettyPrintReport, printStackTrace, Report, Span } from "./diagnostics.ts";
|
||||
|
||||
export class Ctx {
|
||||
private fileIds = new Ids();
|
||||
private files = new Map<Id<File>, FileInfo>();
|
||||
|
||||
private reports: Report[] = [];
|
||||
|
||||
public fileHasChildWithIdent(file: File, childIdent: string): boolean {
|
||||
return this.files.get(id(file))!
|
||||
.subFiles.has(childIdent);
|
||||
}
|
||||
|
||||
public addFile(
|
||||
ident: string,
|
||||
absPath: string,
|
||||
relPath: string,
|
||||
superFile: File | undefined,
|
||||
text: string,
|
||||
): File {
|
||||
const file = this.fileIds.nextThenStep();
|
||||
this.files.set(id(file), {
|
||||
ident,
|
||||
absPath,
|
||||
relPath,
|
||||
superFile,
|
||||
subFiles: new Map(),
|
||||
text,
|
||||
});
|
||||
if (superFile) {
|
||||
this.files.get(id(superFile))!
|
||||
.subFiles.set(ident, file);
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
public addFileAst(file: File, ast: ast.File) {
|
||||
this.files.get(id(file))!.ast = ast;
|
||||
}
|
||||
|
||||
public fileInfo(file: File): FileInfo {
|
||||
return this.files.get(id(file))!;
|
||||
}
|
||||
|
||||
public filePosLineText(file: File, pos: Pos): string {
|
||||
const fileTextLines = this.fileInfo(file).text.split("\n")
|
||||
return fileTextLines[pos.line-1]
|
||||
}
|
||||
|
||||
public fileSpanText(file: File, span: Span): string {
|
||||
let result = ""
|
||||
const fileTextLines = this.fileInfo(file).text.split("\n")
|
||||
|
||||
for(let i = 0; i < fileTextLines.length; i++) {
|
||||
if (i > span.end.line-1) {
|
||||
break;
|
||||
}
|
||||
if (i >= span.begin.line-1) {
|
||||
result += fileTextLines[i] + "\n";
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
public report(rep: Report) {
|
||||
this.reports.push(rep);
|
||||
this.reportImmediately(rep);
|
||||
}
|
||||
|
||||
public enableReportImmediately = false;
|
||||
public enableStacktrace = false;
|
||||
private reportImmediately(rep: Report) {
|
||||
if (this.enableReportImmediately) {
|
||||
prettyPrintReport(this, rep);
|
||||
if (this.enableStacktrace) {
|
||||
printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type File = IdBase;
|
||||
|
||||
export type FileInfo = {
|
||||
ident: string;
|
||||
absPath: string;
|
||||
relPath: string;
|
||||
superFile?: File;
|
||||
subFiles: Map<string, File>;
|
||||
text: string;
|
||||
ast?: ast.File;
|
||||
};
|
||||
|
||||
export type IdBase = { id: number };
|
||||
|
||||
export type Id<IdType extends IdBase> = IdType["id"];
|
||||
export const id = <IdType extends IdBase>(id: IdType): Id<IdType> => id.id;
|
||||
|
||||
export class Ids<IdType extends IdBase> {
|
||||
private next = 0;
|
||||
public nextThenStep(): IdType {
|
||||
const id = this.next;
|
||||
this.next += 1;
|
||||
return { id } as IdType;
|
||||
}
|
||||
}
|
95
compiler/diagnostics.ts
Normal file
95
compiler/diagnostics.ts
Normal file
@ -0,0 +1,95 @@
|
||||
import { Ctx, File } from "./ctx.ts";
|
||||
import { exhausted } from "./util.ts";
|
||||
|
||||
export type Span = {
|
||||
begin: Pos;
|
||||
end: Pos;
|
||||
};
|
||||
|
||||
export type Pos = {
|
||||
idx: number;
|
||||
line: number;
|
||||
col: number;
|
||||
};
|
||||
|
||||
export type Report = {
|
||||
severity: "fatal" | "error" | "warning" | "info";
|
||||
origin?: string;
|
||||
msg: string;
|
||||
file?: File;
|
||||
span?: Span;
|
||||
pos?: Pos;
|
||||
};
|
||||
|
||||
function severityColor(severity: "fatal" | "error" | "warning" | "info") {
|
||||
switch (severity) {
|
||||
case "fatal":
|
||||
return "\x1b[1m\x1b[31m";
|
||||
case "error":
|
||||
return "\x1b[1m\x1b[31m";
|
||||
case "warning":
|
||||
return "\x1b[1m\x1b[33m";
|
||||
case "info":
|
||||
return "\x1b[1m\x1b[34m";
|
||||
}
|
||||
exhausted(severity)
|
||||
}
|
||||
|
||||
export function prettyPrintReport(ctx: Ctx, rep: Report) {
|
||||
const { severity, msg } = rep;
|
||||
const origin = rep.origin ? `\x1b[1m${rep.origin}:\x1b[0m ` : "";
|
||||
console.error(`${origin}${severityColor(severity)}${severity}:\x1b[0m \x1b[37m${msg}\x1b[0m`);
|
||||
if (rep.file && (rep.span || rep.pos)) {
|
||||
const errorLineOffset = 2
|
||||
const { absPath: path } = ctx.fileInfo(rep.file);
|
||||
const { line, col } = rep.span?.begin ?? rep.pos!;
|
||||
console.error(` --> ./${path}:${line}:${col}`);
|
||||
if (rep.span) {
|
||||
const spanLines = ctx.fileSpanText(rep.file, rep.span).split("\n");
|
||||
spanLines.pop()
|
||||
if (spanLines.length == 1) {
|
||||
console.error(`${rep.span.begin.line.toString().padStart(4, ' ')}| ${spanLines[0]}`);
|
||||
console.error(` | ${severityColor(severity)}${" ".repeat(rep.span.begin.col)}${"~".repeat(rep.span.end.col-rep.span.begin.col)}\x1b[0m`)
|
||||
return
|
||||
}
|
||||
for (let i = 0; i < spanLines.length; i++) {
|
||||
console.error(`${(rep.span.begin.line+i).toString().padStart(4, ' ')}| ${spanLines[i]}`);
|
||||
if (i == 0) {
|
||||
console.error(` | ${" ".repeat(rep.span.begin.col-1)}${severityColor(severity)}${"~".repeat(spanLines[i].length-(rep.span.begin.col-1))}\x1b[0m`)
|
||||
}
|
||||
else if (i == spanLines.length-1) {
|
||||
console.error(` | ${severityColor(severity)}${"~".repeat(rep.span.end.col)}\x1b[0m`)
|
||||
}
|
||||
else {
|
||||
console.error(` | ${severityColor(severity)}${"~".repeat(spanLines[i].length)}\x1b[0m`)
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (rep.pos) {
|
||||
console.error(`${rep.pos.line.toString().padStart(4, ' ')}| ${ctx.filePosLineText(rep.file, rep.pos)}`);
|
||||
console.error(` | ${severityColor(severity)}${" ".repeat(rep.pos.col)}^\x1b[0m`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function printStackTrace() {
|
||||
class StackTracer extends Error {
|
||||
constructor() {
|
||||
super("StackTracer");
|
||||
}
|
||||
}
|
||||
try {
|
||||
throw new StackTracer();
|
||||
} catch (error) {
|
||||
if (!(error instanceof StackTracer)) {
|
||||
throw error;
|
||||
}
|
||||
console.log(
|
||||
error.stack?.replace(
|
||||
"Error: StackTracer",
|
||||
"Stack trace:",
|
||||
) ??
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
102
compiler/main.ts
102
compiler/main.ts
@ -1,5 +1,101 @@
|
||||
import { Compiler } from "./compiler.ts";
|
||||
import * as path from "jsr:@std/path";
|
||||
import { Parser } from "./parser/parser.ts";
|
||||
import * as ast from "./ast/mod.ts";
|
||||
import { Ctx } from "./ctx.ts";
|
||||
import { File } from "./ctx.ts";
|
||||
|
||||
const { program } = await new Compiler(Deno.args[0]).compile();
|
||||
export type Pack = {
|
||||
rootMod: Mod;
|
||||
};
|
||||
|
||||
await Deno.writeTextFile("out.slgbc", JSON.stringify(program));
|
||||
export type Mod = null;
|
||||
|
||||
export interface PackEmitter {
|
||||
emit(pack: Pack): void;
|
||||
}
|
||||
|
||||
export class PackCompiler {
|
||||
private ctx = new Ctx();
|
||||
|
||||
public constructor(
|
||||
private entryFilePath: string,
|
||||
private emitter: PackEmitter,
|
||||
) {}
|
||||
|
||||
public compile() {
|
||||
FileTreeAstCollector
|
||||
.fromEntryFile(this.ctx, this.entryFilePath)
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
|
||||
type _P = { file: File };
|
||||
export class FileTreeAstCollector implements ast.Visitor<[_P]> {
|
||||
private subFilePromise = Promise.resolve();
|
||||
|
||||
private constructor(
|
||||
private ctx: Ctx,
|
||||
private superFile: File | undefined,
|
||||
private ident: string,
|
||||
private absPath: string,
|
||||
private relPath: string,
|
||||
) {}
|
||||
|
||||
public static fromEntryFile(
|
||||
ctx: Ctx,
|
||||
entryFilePath: string,
|
||||
): FileTreeAstCollector {
|
||||
return new FileTreeAstCollector(
|
||||
ctx,
|
||||
undefined,
|
||||
"root",
|
||||
entryFilePath,
|
||||
entryFilePath,
|
||||
);
|
||||
}
|
||||
|
||||
public async collect(): Promise<void> {
|
||||
const text = await Deno.readTextFile(this.absPath);
|
||||
const file = this.ctx.addFile(
|
||||
this.ident,
|
||||
this.absPath,
|
||||
this.relPath,
|
||||
this.superFile,
|
||||
text,
|
||||
);
|
||||
const fileAst = new Parser(file, text).parse();
|
||||
this.ctx.addFileAst(file, fileAst);
|
||||
ast.visitFile(this, fileAst, { file });
|
||||
await this.subFilePromise;
|
||||
}
|
||||
|
||||
visitModFileItem(
|
||||
item: ast.Item,
|
||||
kind: ast.ModFileItem,
|
||||
{ file }: _P,
|
||||
): ast.VisitRes {
|
||||
const { ident: { text: ident }, filePath: relPath } = kind;
|
||||
const absPath = path.join(path.dirname(this.absPath), relPath);
|
||||
this.subFilePromise = this.subFilePromise
|
||||
.then(() => {
|
||||
if (this.ctx.fileHasChildWithIdent(file, ident)) {
|
||||
this.ctx.report({
|
||||
severity: "fatal",
|
||||
msg: `module '${ident}' already declared`,
|
||||
file,
|
||||
span: item.span,
|
||||
});
|
||||
Deno.exit(1);
|
||||
}
|
||||
return new FileTreeAstCollector(
|
||||
this.ctx,
|
||||
file,
|
||||
ident,
|
||||
absPath,
|
||||
relPath,
|
||||
)
|
||||
.collect();
|
||||
});
|
||||
return "stop";
|
||||
}
|
||||
}
|
||||
|
5
compiler/old/main.ts
Normal file
5
compiler/old/main.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { Compiler } from "./compiler.ts";
|
||||
|
||||
const { program } = await new Compiler(Deno.args[0]).compile();
|
||||
|
||||
await Deno.writeTextFile("out.slgbc", JSON.stringify(program));
|
@ -170,9 +170,6 @@ class LocalChecker {
|
||||
}
|
||||
|
||||
private markSrc(src: RValue) {
|
||||
if (src.type === "local") {
|
||||
throw new Error("should be 'copy' or 'move'");
|
||||
}
|
||||
if (
|
||||
(src.type !== "copy" && src.type !== "move") ||
|
||||
src.id !== this.local.id
|
@ -73,7 +73,7 @@ class EliminateUnusedLocalsFnPass {
|
||||
}
|
||||
|
||||
private markUsed(local: RValue) {
|
||||
if (local.type !== "local") {
|
||||
if (local.type !== "copy" && local.type !== "move") {
|
||||
return;
|
||||
}
|
||||
this.locals = this.locals.filter((lid) => lid !== local.id);
|
@ -23,7 +23,7 @@ class LocalExpliciter {
|
||||
}
|
||||
|
||||
private explicitSrc(src: RValue): RValue {
|
||||
if (src.type !== "local" || src.id !== this.local.id) {
|
||||
if (src.type !== "move" || src.id !== this.local.id) {
|
||||
return src;
|
||||
}
|
||||
return this.copyable
|
@ -509,5 +509,5 @@ class FnAstLowerer {
|
||||
}
|
||||
|
||||
function local(id: LocalId): RValue {
|
||||
return { type: "local", id };
|
||||
return { type: "move", id };
|
||||
}
|
@ -45,7 +45,6 @@ export type OpKind =
|
||||
| { type: "ref_mut"; dst: L; src: L }
|
||||
| { type: "ptr"; dst: L; src: L }
|
||||
| { type: "ptr_mut"; dst: L; src: L }
|
||||
| { type: "drop"; src: L }
|
||||
| { type: "deref"; dst: L; src: R }
|
||||
| { type: "assign_deref"; subject: R; src: R }
|
||||
| { type: "field"; dst: L; subject: R; ident: string }
|
||||
@ -67,7 +66,6 @@ export type TerKind =
|
||||
|
||||
export type RValue =
|
||||
| { type: "error" }
|
||||
| { type: "local"; id: BlockId }
|
||||
| { type: "copy"; id: BlockId }
|
||||
| { type: "move"; id: BlockId }
|
||||
| { type: "null" }
|
||||
@ -123,7 +121,6 @@ export function replaceBlockSrcs(
|
||||
case "ref_mut":
|
||||
case "ptr":
|
||||
case "ptr_mut":
|
||||
case "drop":
|
||||
break;
|
||||
case "deref":
|
||||
ok.src = replace(ok.src);
|
||||
@ -190,7 +187,6 @@ export function visitBlockSrcs(
|
||||
case "ref_mut":
|
||||
case "ptr":
|
||||
case "ptr_mut":
|
||||
case "drop":
|
||||
break;
|
||||
case "deref":
|
||||
visitor(ok.src, op, i);
|
||||
@ -307,9 +303,6 @@ export function printMir(mir: Mir) {
|
||||
case "ptr_mut":
|
||||
l(`_${k.dst} = *mut _${k.src};`);
|
||||
break;
|
||||
case "drop":
|
||||
l(`drop _${k.src};`);
|
||||
break;
|
||||
case "deref":
|
||||
l(`_${k.dst} = *${r(k.src)};`);
|
||||
break;
|
||||
@ -372,8 +365,6 @@ export function rvalueToString(rvalue: RValue): string {
|
||||
switch (rvalue.type) {
|
||||
case "error":
|
||||
return `<error>`;
|
||||
case "local":
|
||||
return `_${rvalue.id}`;
|
||||
case "copy":
|
||||
return `copy _${rvalue.id}`;
|
||||
case "move":
|
14
compiler/parser/parser.ts
Normal file
14
compiler/parser/parser.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { File } from "../ast/ast.ts";
|
||||
import { File as CtxFile } from "../ctx.ts";
|
||||
import { todo } from "../util.ts";
|
||||
|
||||
export class Parser {
|
||||
public constructor(
|
||||
private file: CtxFile,
|
||||
private text: string,
|
||||
) {}
|
||||
|
||||
public parse(): File {
|
||||
return todo();
|
||||
}
|
||||
}
|
64
compiler/test_diagnostics.ts
Normal file
64
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
|
||||
},
|
||||
});
|
9
compiler/util.ts
Normal file
9
compiler/util.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export function todo<T>(msg?: string): T {
|
||||
class NotImplemented extends Error {}
|
||||
throw new NotImplemented(msg);
|
||||
}
|
||||
|
||||
export function exhausted(_: never) {
|
||||
class Unexhausted extends Error {}
|
||||
throw new Unexhausted();
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
FROM archlinux
|
||||
RUN pacman -Syu git base-devel deno --noconfirm
|
||||
WORKDIR /workspace
|
||||
ENTRYPOINT ["/bin/bash"]
|
@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
docker build -t slige-dev-env dev-env
|
||||
docker run --name dev-env --rm -it --mount type=bind,source="$(pwd)"/,target=/workspace slige-dev-env
|
164
examples/calculator.slg
Normal file
164
examples/calculator.slg
Normal file
@ -0,0 +1,164 @@
|
||||
mod std;
|
||||
|
||||
type_alias Tok: struct {
|
||||
type: string,
|
||||
value: string,
|
||||
};
|
||||
|
||||
fn lex(text: string) -> [Tok] {
|
||||
let i = 0;
|
||||
let len = std::string_length(text);
|
||||
|
||||
let toks = std::array_new::<Tok>();
|
||||
|
||||
while i < len {
|
||||
if std::string_contains(" \t\n", text[i]) {
|
||||
i += 1;
|
||||
if i >= len {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if text[i] >= '1' and text[i] <= '9' {
|
||||
let value = std::ctos(text[i]);
|
||||
i += 1;
|
||||
while i < len and text[i] >= '0' and text[i] <= '9' {
|
||||
value = std::string_push_char(value, text[i]);
|
||||
i += 1;
|
||||
}
|
||||
let tok = struct { type: "int", value: value };
|
||||
std::array_push(toks, tok);
|
||||
} else if text[i] == '0' {
|
||||
i += 1;
|
||||
let tok = struct { type: "int", value: "0" };
|
||||
std::array_push(toks, tok);
|
||||
} else if text[i] == '+' {
|
||||
i += 1;
|
||||
let tok = struct { type: "+", value: "+" };
|
||||
std::array_push(toks, tok);
|
||||
} else if text[i] == '-' {
|
||||
i += 1;
|
||||
let tok = struct { type: "-", value: "-" };
|
||||
std::array_push(toks, tok);
|
||||
} else if text[i] == '*' {
|
||||
i += 1;
|
||||
let tok = struct { type: "*", value: "*" };
|
||||
std::array_push(toks, tok);
|
||||
} else if text[i] == '/' {
|
||||
i += 1;
|
||||
let tok = struct { type: "/", value: "/" };
|
||||
std::array_push(toks, tok);
|
||||
} else if text[i] == '(' {
|
||||
i += 1;
|
||||
let tok = struct { type: "(", value: "(" };
|
||||
std::array_push(toks, tok);
|
||||
} else if text[i] == ')' {
|
||||
i += 1;
|
||||
let tok = struct { type: ")", value: ")" };
|
||||
std::array_push(toks, tok);
|
||||
} else {
|
||||
std::println("error: illegal character '" + std::ctos(text[i]) + "'");
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
toks
|
||||
}
|
||||
|
||||
type_alias Calc: struct {
|
||||
toks: [Tok],
|
||||
toks_len: int,
|
||||
i: int,
|
||||
};
|
||||
|
||||
fn calc_new(text: string) -> Calc {
|
||||
let toks = lex(text);
|
||||
let toks_len = std::array_length(toks);
|
||||
struct { toks: toks, toks_len: toks_len, i: 0 }
|
||||
}
|
||||
|
||||
fn calc_expr(self: Calc) -> int {
|
||||
calc_add_sub(self)
|
||||
}
|
||||
|
||||
fn calc_add_sub(self: Calc) -> int {
|
||||
let left = calc_mul_div(self);
|
||||
loop {
|
||||
if self.toks[self.i].type == "+" {
|
||||
self.i += 1;
|
||||
let right = calc_mul_div(self);
|
||||
left = left + right;
|
||||
} else if self.toks[self.i].type == "-" {
|
||||
self.i += 1;
|
||||
let right = calc_mul_div(self);
|
||||
left = left - right;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
left
|
||||
}
|
||||
|
||||
fn calc_mul_div(self: Calc) -> int {
|
||||
let left = calc_unary(self);
|
||||
loop {
|
||||
if self.toks[self.i].type == "*" {
|
||||
self.i += 1;
|
||||
let right = calc_unary(self);
|
||||
left = left * right;
|
||||
} else if self.toks[self.i].type == "/" {
|
||||
self.i += 1;
|
||||
let right = calc_unary(self);
|
||||
left = left / right;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
left
|
||||
|
||||
}
|
||||
|
||||
fn calc_unary(self: Calc) -> int {
|
||||
if self.toks[self.i].type == "-" {
|
||||
self.i += 1;
|
||||
let subject = calc_unary(self);
|
||||
-subject
|
||||
} else {
|
||||
calc_operand(self)
|
||||
}
|
||||
}
|
||||
|
||||
fn calc_operand(self: Calc) -> int {
|
||||
if self.i >= self.toks_len {
|
||||
std::println("error: expected expr");
|
||||
0
|
||||
} else if self.toks[self.i].type == "int" {
|
||||
let val = std::stoi(self.toks[self.i].value);
|
||||
self.i += 1;
|
||||
val
|
||||
} else if self.toks[self.i].type == "(" {
|
||||
self.i += 1;
|
||||
let val = calc_expr(self);
|
||||
if self.i >= self.toks_len or self.toks[self.i].type != ")" {
|
||||
std::println("error: missing ')'");
|
||||
return 0;
|
||||
}
|
||||
self.i += 1;
|
||||
val
|
||||
} else {
|
||||
std::println("error: expected expr");
|
||||
self.i += 1;
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
loop {
|
||||
let line = std::input("> ");
|
||||
if line == "exit" {
|
||||
break;
|
||||
}
|
||||
let calc = calc_new(line);
|
||||
let val = calc_expr(calc);
|
||||
std::println(line);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user