use new tok
All checks were successful
Check / Explore-Gitea-Actions (push) Successful in 9s

This commit is contained in:
sfja 2026-03-16 21:41:57 +01:00
parent c7741b8d31
commit d908ff30e0
5 changed files with 55 additions and 53 deletions

View File

@ -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,
) {} ) {}

View File

@ -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",

View File

@ -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,

View File

@ -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;

View File

@ -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,