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