This commit is contained in:
parent
c7741b8d31
commit
d908ff30e0
12
src/ast.ts
12
src/ast.ts
@ -1,29 +1,31 @@
|
||||
import { Loc } from "./diagnostics.ts";
|
||||
|
||||
export function create<Tag extends NodeKind["tag"]>(
|
||||
line: number,
|
||||
loc: Loc,
|
||||
tag: Tag,
|
||||
kind: Omit<NodeKind & { tag: Tag }, "tag">,
|
||||
): Node {
|
||||
return Node.create(line, tag, kind);
|
||||
return Node.create(loc, tag, kind);
|
||||
}
|
||||
|
||||
export class Node {
|
||||
private static idCounter = 0;
|
||||
|
||||
static create<Tag extends NodeKind["tag"]>(
|
||||
line: number,
|
||||
loc: Loc,
|
||||
tag: Tag,
|
||||
kind: Omit<NodeKind & { tag: Tag }, "tag">,
|
||||
): Node {
|
||||
return new Node(
|
||||
Node.idCounter++,
|
||||
line,
|
||||
loc,
|
||||
{ tag, ...kind } as NodeKind & { tag: Tag },
|
||||
);
|
||||
}
|
||||
|
||||
private constructor(
|
||||
public id: number,
|
||||
public line: number,
|
||||
public loc: Loc,
|
||||
public kind: NodeKind,
|
||||
) {}
|
||||
|
||||
|
||||
@ -15,11 +15,13 @@ export type FileInfo = {
|
||||
|
||||
export function printDiagnostics(
|
||||
filename: string,
|
||||
line: number,
|
||||
loc: Loc,
|
||||
severity: "error" | "info",
|
||||
message: string,
|
||||
text?: string,
|
||||
) {
|
||||
const line = loc.line;
|
||||
|
||||
const severityColor = ({
|
||||
"error": "red",
|
||||
"info": "blue",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import * as ast from "../ast.ts";
|
||||
import { printDiagnostics } from "../diagnostics.ts";
|
||||
import { Loc, printDiagnostics } from "../diagnostics.ts";
|
||||
import { Ty } from "../ty.ts";
|
||||
import { builtins } from "./builtins.ts";
|
||||
import { ResolveMap } from "./resolve.ts";
|
||||
@ -46,14 +46,14 @@ export class Checker {
|
||||
this.assertCompatible(
|
||||
exprTy,
|
||||
explicitTy,
|
||||
sym.stmt.kind.expr.line,
|
||||
sym.stmt.kind.expr.loc,
|
||||
);
|
||||
}
|
||||
return exprTy;
|
||||
}
|
||||
if (sym.tag === "FnParam") {
|
||||
if (!node.kind.ty) {
|
||||
this.error(node.line, `parameter must have a type`);
|
||||
this.error(node.loc, `parameter must have a type`);
|
||||
this.fail();
|
||||
}
|
||||
return this.check(node.kind.ty);
|
||||
@ -91,13 +91,13 @@ export class Checker {
|
||||
for (const value of node.kind.values) {
|
||||
const valueTy = this.check(value);
|
||||
if (ty) {
|
||||
this.assertCompatible(ty, valueTy, value.line);
|
||||
this.assertCompatible(ty, valueTy, value.loc);
|
||||
} else {
|
||||
ty = valueTy;
|
||||
}
|
||||
}
|
||||
if (!ty) {
|
||||
this.error(node.line, `could not infer type of empty array`);
|
||||
this.error(node.loc, `could not infer type of empty array`);
|
||||
this.fail();
|
||||
}
|
||||
const length = node.kind.values.length;
|
||||
@ -120,7 +120,7 @@ export class Checker {
|
||||
return Ty.create("Slice", { ty: exprTy.kind.ty });
|
||||
}
|
||||
this.error(
|
||||
node.line,
|
||||
node.loc,
|
||||
`cannot use index operator on '${exprTy.pretty()}' with '${argTy.pretty()}'`,
|
||||
);
|
||||
this.fail();
|
||||
@ -150,7 +150,7 @@ export class Checker {
|
||||
}
|
||||
}
|
||||
this.error(
|
||||
node.line,
|
||||
node.loc,
|
||||
`operator '${node.kind.tok}' cannot be applied to type '${exprTy.pretty()}'`,
|
||||
);
|
||||
this.fail();
|
||||
@ -167,7 +167,7 @@ export class Checker {
|
||||
);
|
||||
if (!binaryOp) {
|
||||
this.error(
|
||||
node.line,
|
||||
node.loc,
|
||||
`operator '${node.kind.tok}' cannot be applied to types '${left.pretty()}' and '${right.pretty()}'`,
|
||||
);
|
||||
this.fail();
|
||||
@ -180,7 +180,7 @@ export class Checker {
|
||||
const operandTy = operandExpr && this.check(operandExpr);
|
||||
if (operandTy && !operandTy.compatibleWith(Ty.Int)) {
|
||||
this.error(
|
||||
operandExpr.line,
|
||||
operandExpr.loc,
|
||||
`range operand must be '${Ty.Int.pretty()}', not '${operandTy.pretty()}'`,
|
||||
);
|
||||
this.fail();
|
||||
@ -198,7 +198,7 @@ export class Checker {
|
||||
case "bool":
|
||||
return Ty.Bool;
|
||||
default:
|
||||
this.error(node.line, `unknown type '${node.kind.ident}'`);
|
||||
this.error(node.loc, `unknown type '${node.kind.ident}'`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -216,14 +216,14 @@ export class Checker {
|
||||
const lengthTy = this.check(node.kind.length);
|
||||
if (!lengthTy.compatibleWith(Ty.Int)) {
|
||||
this.error(
|
||||
node.kind.length.line,
|
||||
node.kind.length.loc,
|
||||
`for array length, expected 'int', got '${lengthTy.pretty()}'`,
|
||||
);
|
||||
this.fail();
|
||||
}
|
||||
if (!node.kind.length.is("IntExpr")) {
|
||||
this.error(
|
||||
node.kind.length.line,
|
||||
node.kind.length.loc,
|
||||
`array length must be an 'int' expression`,
|
||||
);
|
||||
this.fail();
|
||||
@ -254,11 +254,11 @@ export class Checker {
|
||||
: Ty.Void;
|
||||
if (!ty.compatibleWith(retTy)) {
|
||||
this.error(
|
||||
node.line,
|
||||
node.loc,
|
||||
`type '${ty.pretty()}' not compatible with return type '${retTy.pretty()}'`,
|
||||
);
|
||||
this.info(
|
||||
stmt.kind.retTy?.line ?? stmt.line,
|
||||
stmt.kind.retTy?.loc ?? stmt.loc,
|
||||
`return type '${retTy}' defined here`,
|
||||
);
|
||||
this.fail();
|
||||
@ -282,7 +282,7 @@ export class Checker {
|
||||
|
||||
if (!callableTy) {
|
||||
this.error(
|
||||
node.line,
|
||||
node.loc,
|
||||
`type '${calleeTy.pretty()}' not callable`,
|
||||
);
|
||||
this.fail();
|
||||
@ -293,12 +293,12 @@ export class Checker {
|
||||
const params = callableTy.kind.params;
|
||||
if (args.length !== params.length) {
|
||||
this.error(
|
||||
node.line,
|
||||
node.loc,
|
||||
`incorrect amount of arguments. got ${args.length} expected ${params.length}`,
|
||||
);
|
||||
if (calleeTy.is("FnStmt")) {
|
||||
this.info(
|
||||
calleeTy.kind.stmt.line,
|
||||
calleeTy.kind.stmt.loc,
|
||||
"function defined here",
|
||||
);
|
||||
}
|
||||
@ -307,14 +307,14 @@ export class Checker {
|
||||
for (const i of args.keys()) {
|
||||
if (!args[i].compatibleWith(params[i])) {
|
||||
this.error(
|
||||
node.kind.args[i].line,
|
||||
node.kind.args[i].loc,
|
||||
`type '${args[i].pretty()}' not compatible with type '${
|
||||
params[i].pretty()
|
||||
}', for argument ${i}`,
|
||||
);
|
||||
if (calleeTy.is("FnStmt")) {
|
||||
this.info(
|
||||
calleeTy.kind.stmt.kind.params[i].line,
|
||||
calleeTy.kind.stmt.kind.params[i].loc,
|
||||
`parameter '${
|
||||
calleeTy.kind.stmt.kind.params[i]
|
||||
.as("Param").kind.ident
|
||||
@ -327,30 +327,30 @@ export class Checker {
|
||||
return callableTy.kind.retTy;
|
||||
}
|
||||
|
||||
private assertCompatible(left: Ty, right: Ty, line: number): void {
|
||||
private assertCompatible(left: Ty, right: Ty, loc: Loc): void {
|
||||
if (!left.compatibleWith(right)) {
|
||||
this.error(
|
||||
line,
|
||||
loc,
|
||||
`type '${left.pretty()}' not compatible with type '${right.pretty()}'`,
|
||||
);
|
||||
this.fail();
|
||||
}
|
||||
}
|
||||
|
||||
private error(line: number, message: string) {
|
||||
private error(loc: Loc, message: string) {
|
||||
printDiagnostics(
|
||||
this.filename,
|
||||
line,
|
||||
loc,
|
||||
"error",
|
||||
message,
|
||||
this.text,
|
||||
);
|
||||
}
|
||||
|
||||
private info(line: number, message: string) {
|
||||
private info(loc: Loc, message: string) {
|
||||
printDiagnostics(
|
||||
this.filename,
|
||||
line,
|
||||
loc,
|
||||
"info",
|
||||
message,
|
||||
this.text,
|
||||
|
||||
@ -11,7 +11,7 @@ export function parse(
|
||||
export class Parser {
|
||||
private toks: Tok[];
|
||||
private idx = 0;
|
||||
private currentLine = 1;
|
||||
private currentLoc: Loc = { idx: 0, line: 1, col: 1 };
|
||||
private prevTok: Tok | null = null;
|
||||
|
||||
constructor(
|
||||
@ -148,7 +148,7 @@ export class Parser {
|
||||
}
|
||||
}
|
||||
|
||||
parseRangeTail(loc: number, begin: ast.Node | null, tok: string): ast.Node {
|
||||
parseRangeTail(loc: Loc, begin: ast.Node | null, tok: string): ast.Node {
|
||||
const limit: ast.RangeLimit = tok === ".." ? "Exclusive" : "Inclusive";
|
||||
let end: ast.Node | null = null;
|
||||
if (![";", ",", ")", "]"].some((tok) => this.test(tok))) {
|
||||
@ -319,7 +319,7 @@ export class Parser {
|
||||
}
|
||||
}
|
||||
|
||||
private mustEat(type: string, loc: number = this.loc()): Tok {
|
||||
private mustEat(type: string, loc = this.loc()): Tok {
|
||||
const tok = this.current;
|
||||
if (tok.type !== type) {
|
||||
this.error(
|
||||
@ -333,7 +333,7 @@ export class Parser {
|
||||
return tok;
|
||||
}
|
||||
|
||||
private error(message: string, loc: number): never {
|
||||
private error(message: string, loc: Loc): never {
|
||||
printDiagnostics(this.filename, loc, "error", message, this.text);
|
||||
throw new Error();
|
||||
Deno.exit(1);
|
||||
@ -353,7 +353,7 @@ export class Parser {
|
||||
}
|
||||
this.idx += 1;
|
||||
if (!this.done) {
|
||||
this.currentLine = this.current.line;
|
||||
this.currentLoc = this.current.loc;
|
||||
}
|
||||
}
|
||||
|
||||
@ -361,8 +361,8 @@ export class Parser {
|
||||
return !this.done && this.current.type == type;
|
||||
}
|
||||
|
||||
private loc(): number {
|
||||
return this.currentLine;
|
||||
private loc(): Loc {
|
||||
return this.currentLoc;
|
||||
}
|
||||
|
||||
private get current(): Tok {
|
||||
@ -374,8 +374,7 @@ export class Parser {
|
||||
}
|
||||
}
|
||||
|
||||
export type Tok = { type: string; value: string; line: number };
|
||||
export type Tok2 = { type: string; value: string; loc: Loc };
|
||||
export type Tok = { type: string; value: string; loc: Loc };
|
||||
|
||||
const keywordPattern =
|
||||
/^(?:(?:fn)|(?:return)|(?:let)|(?:if)|(?:else)|(?:while)|(?:break)|(?:or)|(?:and)|(?:not)|(?:mut))/;
|
||||
@ -384,13 +383,13 @@ const operatorPattern2 =
|
||||
/((?:\->)|(?:==)|(?:!=)|(?:<=)|(?:>=)|(?:<<)|(?:>>)|(?:\.\*)|(?:\.\.)|(?:\.\.=)|[\n\(\)\{\}\[\]\,\.\;\:\!\=\<\>\&\^\|\+\-\*\/\%])/g;
|
||||
|
||||
export function tokenize(text: string): Tok[] {
|
||||
return new Lexer<Tok2>()
|
||||
return new Lexer()
|
||||
.add(/[ \t\r\n]+/, (_) => null)
|
||||
.add(/\/\/[^\n]*/, (_) => null)
|
||||
.add(operatorPattern2, (loc, value) => ({ type: value, value, loc }))
|
||||
.add(/[a-zA-Z_][a-zA-Z0-9_]*/, (loc, value) => {
|
||||
const type = keywordPattern.test(value) ? value : "ident";
|
||||
return ({ type, value, loc });
|
||||
return { type, value, loc };
|
||||
})
|
||||
.add(/0|(?:[1-9][0-9]*)/, (loc, value) => {
|
||||
return { type: "int", value, loc };
|
||||
@ -398,21 +397,20 @@ export function tokenize(text: string): Tok[] {
|
||||
.add(/./, (loc, value) => {
|
||||
return null;
|
||||
})
|
||||
.lex(text)
|
||||
.map<Tok>(({ type, value, loc: { line } }) => ({ type, value, line }));
|
||||
.lex(text);
|
||||
}
|
||||
|
||||
type LexRule<TokT> = {
|
||||
type LexRule = {
|
||||
pattern: RegExp;
|
||||
action: LexAction<TokT>;
|
||||
action: LexAction;
|
||||
};
|
||||
|
||||
type LexAction<TokT> = (loc: Loc, match: string) => TokT | null;
|
||||
type LexAction = (loc: Loc, match: string) => Tok | null;
|
||||
|
||||
class Lexer<TokT> {
|
||||
private rules: LexRule<TokT>[] = [];
|
||||
class Lexer {
|
||||
private rules: LexRule[] = [];
|
||||
|
||||
add(pattern: RegExp, action: LexAction<TokT>): this {
|
||||
add(pattern: RegExp, action: LexAction): this {
|
||||
this.rules.push({
|
||||
pattern: new RegExp(`^(?:${pattern.source})`),
|
||||
action,
|
||||
@ -420,8 +418,8 @@ class Lexer<TokT> {
|
||||
return this;
|
||||
}
|
||||
|
||||
lex(text: string): TokT[] {
|
||||
const toks: TokT[] = [];
|
||||
lex(text: string): Tok[] {
|
||||
const toks: Tok[] = [];
|
||||
let idx = 0;
|
||||
let line = 1;
|
||||
let col = 1;
|
||||
|
||||
@ -83,7 +83,7 @@ export function resolve(
|
||||
if (sym === null) {
|
||||
printDiagnostics(
|
||||
filename,
|
||||
node.line,
|
||||
node.loc,
|
||||
"error",
|
||||
`undefined symbol '${k.ident}'`,
|
||||
text,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user