diff --git a/etc/aiscript.api.md b/etc/aiscript.api.md index 82532fc2..92ec4507 100644 --- a/etc/aiscript.api.md +++ b/etc/aiscript.api.md @@ -36,14 +36,18 @@ class AiScriptRuntimeError extends AiScriptError { // @public class AiScriptSyntaxError extends AiScriptError { - constructor(message: string, info?: any); + constructor(message: string, loc: Loc, info?: any); + // (undocumented) + loc: Loc; // (undocumented) name: string; } // @public class AiScriptTypeError extends AiScriptError { - constructor(message: string, info?: any); + constructor(message: string, loc: Loc, info?: any); + // (undocumented) + loc: Loc; // (undocumented) name: string; } diff --git a/src/error.ts b/src/error.ts index 0a7a0f35..0c7a6b44 100644 --- a/src/error.ts +++ b/src/error.ts @@ -1,3 +1,5 @@ +import type { Loc } from './node.js'; + export abstract class AiScriptError extends Error { // name is read by Error.prototype.toString public name = 'AiScript'; @@ -30,8 +32,8 @@ export class NonAiScriptError extends AiScriptError { */ export class AiScriptSyntaxError extends AiScriptError { public name = 'Syntax'; - constructor(message: string, info?: any) { - super(message, info); + constructor(message: string, public loc: Loc, info?: any) { + super(`${message} (Line ${loc.line}, Column ${loc.column})`, info); } } /** @@ -39,8 +41,8 @@ export class AiScriptSyntaxError extends AiScriptError { */ export class AiScriptTypeError extends AiScriptError { public name = 'Type'; - constructor(message: string, info?: any) { - super(message, info); + constructor(message: string, public loc: Loc, info?: any) { + super(`${message} (Line ${loc.line}, Column ${loc.column})`, info); } } diff --git a/src/parser/plugins/validate-keyword.ts b/src/parser/plugins/validate-keyword.ts index 1d7d5b10..6c1fdac1 100644 --- a/src/parser/plugins/validate-keyword.ts +++ b/src/parser/plugins/validate-keyword.ts @@ -42,8 +42,8 @@ const reservedWord = [ // 'out', ]; -function throwReservedWordError(name: string): void { - throw new AiScriptSyntaxError(`Reserved word "${name}" cannot be used as variable name.`); +function throwReservedWordError(name: string, loc: Ast.Loc): void { + throw new AiScriptSyntaxError(`Reserved word "${name}" cannot be used as variable name.`, loc); } function validateNode(node: Ast.Node): Ast.Node { @@ -53,20 +53,20 @@ function validateNode(node: Ast.Node): Ast.Node { case 'ns': case 'identifier': { if (reservedWord.includes(node.name)) { - throwReservedWordError(node.name); + throwReservedWordError(node.name, node.loc); } break; } case 'meta': { if (node.name != null && reservedWord.includes(node.name)) { - throwReservedWordError(node.name); + throwReservedWordError(node.name, node.loc); } break; } case 'fn': { for (const arg of node.args) { if (reservedWord.includes(arg.name)) { - throwReservedWordError(arg.name); + throwReservedWordError(arg.name, node.loc); } } break; diff --git a/src/parser/scanner.ts b/src/parser/scanner.ts index 67a5d93e..3bd61a82 100644 --- a/src/parser/scanner.ts +++ b/src/parser/scanner.ts @@ -75,7 +75,7 @@ export class Scanner implements ITokenStream { */ public expect(kind: TokenKind): void { if (this.kind !== kind) { - throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[this.kind]}`); + throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[this.kind]}`, this.token.loc); } } @@ -140,7 +140,7 @@ export class Scanner implements ITokenStream { this.stream.next(); token = TOKEN(TokenKind.OpenSharpBracket, loc, { hasLeftSpacing }); } else { - throw new AiScriptSyntaxError('invalid character: "#"'); + throw new AiScriptSyntaxError('invalid character: "#"', loc); } break; } @@ -327,7 +327,7 @@ export class Scanner implements ITokenStream { token = wordToken; break; } - throw new AiScriptSyntaxError(`invalid character: "${this.stream.char}"`); + throw new AiScriptSyntaxError(`invalid character: "${this.stream.char}"`, loc); } break; } @@ -432,7 +432,7 @@ export class Scanner implements ITokenStream { this.stream.next(); } if (fractional.length === 0) { - throw new AiScriptSyntaxError('digit expected'); + throw new AiScriptSyntaxError('digit expected', loc); } } let value; @@ -456,7 +456,7 @@ export class Scanner implements ITokenStream { switch (state) { case 'string': { if (this.stream.eof) { - throw new AiScriptSyntaxError('unexpected EOF'); + throw new AiScriptSyntaxError('unexpected EOF', loc); } if (this.stream.char === '\\') { this.stream.next(); @@ -474,7 +474,7 @@ export class Scanner implements ITokenStream { } case 'escape': { if (this.stream.eof) { - throw new AiScriptSyntaxError('unexpected EOF'); + throw new AiScriptSyntaxError('unexpected EOF', loc); } value += this.stream.char; this.stream.next(); @@ -501,7 +501,7 @@ export class Scanner implements ITokenStream { case 'string': { // テンプレートの終了が無いままEOFに達した if (this.stream.eof) { - throw new AiScriptSyntaxError('unexpected EOF'); + throw new AiScriptSyntaxError('unexpected EOF', loc); } // エスケープ if (this.stream.char === '\\') { @@ -537,7 +537,7 @@ export class Scanner implements ITokenStream { case 'escape': { // エスケープ対象の文字が無いままEOFに達した if (this.stream.eof) { - throw new AiScriptSyntaxError('unexpected EOF'); + throw new AiScriptSyntaxError('unexpected EOF', loc); } // 普通の文字として取り込み buf += this.stream.char; @@ -549,7 +549,7 @@ export class Scanner implements ITokenStream { case 'expr': { // 埋め込み式の終端記号が無いままEOFに達した if (this.stream.eof) { - throw new AiScriptSyntaxError('unexpected EOF'); + throw new AiScriptSyntaxError('unexpected EOF', loc); } // skip spasing if (spaceChars.includes(this.stream.char)) { diff --git a/src/parser/streams/token-stream.ts b/src/parser/streams/token-stream.ts index 3dae2a2d..c0fadcaa 100644 --- a/src/parser/streams/token-stream.ts +++ b/src/parser/streams/token-stream.ts @@ -101,7 +101,7 @@ export class TokenStream implements ITokenStream { */ public expect(kind: TokenKind): void { if (this.kind !== kind) { - throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[this.kind]}`); + throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[this.kind]}`, this.token.loc); } } diff --git a/src/parser/syntaxes/common.ts b/src/parser/syntaxes/common.ts index 311b4f85..6c200ad5 100644 --- a/src/parser/syntaxes/common.ts +++ b/src/parser/syntaxes/common.ts @@ -22,7 +22,7 @@ export function parseParams(s: ITokenStream): { name: string, argType?: Ast.Node if (s.kind === TokenKind.Comma) { s.next(); } else if (!s.token.hasLeftSpacing) { - throw new AiScriptSyntaxError('separator expected'); + throw new AiScriptSyntaxError('separator expected', s.token.loc); } } @@ -61,7 +61,7 @@ export function parseBlock(s: ITokenStream): Ast.Node[] { steps.push(parseStatement(s)); if ((s.kind as TokenKind) !== TokenKind.NewLine && (s.kind as TokenKind) !== TokenKind.CloseBrace) { - throw new AiScriptSyntaxError('Multiple statements cannot be placed on a single line.'); + throw new AiScriptSyntaxError('Multiple statements cannot be placed on a single line.', s.token.loc); } while ((s.kind as TokenKind) === TokenKind.NewLine) { s.next(); @@ -101,7 +101,7 @@ function parseFnType(s: ITokenStream): Ast.Node { if (s.kind === TokenKind.Comma) { s.next(); } else if (!s.token.hasLeftSpacing) { - throw new AiScriptSyntaxError('separator expected'); + throw new AiScriptSyntaxError('separator expected', s.token.loc); } } const type = parseType(s); diff --git a/src/parser/syntaxes/expressions.ts b/src/parser/syntaxes/expressions.ts index 7aafacf7..eecbc66f 100644 --- a/src/parser/syntaxes/expressions.ts +++ b/src/parser/syntaxes/expressions.ts @@ -70,7 +70,7 @@ function parsePrefix(s: ITokenStream, minBp: number): Ast.Node { if (expr.type === 'num') { return NODE('num', { value: expr.value }, loc); } else { - throw new AiScriptSyntaxError('currently, sign is only supported for number literal.'); + throw new AiScriptSyntaxError('currently, sign is only supported for number literal.', loc); } // TODO: 将来的にサポートされる式を拡張 // return NODE('plus', { expr }, loc); @@ -80,7 +80,7 @@ function parsePrefix(s: ITokenStream, minBp: number): Ast.Node { if (expr.type === 'num') { return NODE('num', { value: -1 * expr.value }, loc); } else { - throw new AiScriptSyntaxError('currently, sign is only supported for number literal.'); + throw new AiScriptSyntaxError('currently, sign is only supported for number literal.', loc); } // TODO: 将来的にサポートされる式を拡張 // return NODE('minus', { expr }, loc); @@ -89,7 +89,7 @@ function parsePrefix(s: ITokenStream, minBp: number): Ast.Node { return NODE('not', { expr }, loc); } default: { - throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[op]}`); + throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[op]}`, loc); } } } @@ -161,7 +161,7 @@ function parseInfix(s: ITokenStream, left: Ast.Node, minBp: number): Ast.Node { return NODE('or', { left, right }, loc); } default: { - throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[op]}`); + throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[op]}`, loc); } } } @@ -186,7 +186,7 @@ function parsePostfix(s: ITokenStream, expr: Ast.Node): Ast.Node { }, loc); } default: { - throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[op]}`); + throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[op]}`, loc); } } } @@ -231,13 +231,13 @@ function parseAtom(s: ITokenStream, isStatic: boolean): Ast.Node { const exprStream = new TokenStream(element.children!); const expr = parseExpr(exprStream, false); if (exprStream.kind !== TokenKind.EOF) { - throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[exprStream.token.kind]}`); + throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[exprStream.token.kind]}`, exprStream.token.loc); } values.push(expr); break; } default: { - throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[element.kind]}`); + throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[element.kind]}`, element.loc); } } } @@ -283,7 +283,7 @@ function parseAtom(s: ITokenStream, isStatic: boolean): Ast.Node { return expr; } } - throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[s.kind]}`); + throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[s.kind]}`, loc); } /** @@ -301,7 +301,7 @@ function parseCall(s: ITokenStream, target: Ast.Node): Ast.Node { if (s.kind === TokenKind.Comma) { s.next(); } else if (!s.token.hasLeftSpacing) { - throw new AiScriptSyntaxError('separator expected'); + throw new AiScriptSyntaxError('separator expected', s.token.loc); } } @@ -451,11 +451,11 @@ function parseReference(s: ITokenStream): Ast.Node { if (segs.length > 0) { if (s.kind === TokenKind.Colon) { if (s.token.hasLeftSpacing) { - throw new AiScriptSyntaxError('Cannot use spaces in a reference.'); + throw new AiScriptSyntaxError('Cannot use spaces in a reference.', s.token.loc); } s.next(); if (s.token.hasLeftSpacing) { - throw new AiScriptSyntaxError('Cannot use spaces in a reference.'); + throw new AiScriptSyntaxError('Cannot use spaces in a reference.', s.token.loc); } } else { break; @@ -505,7 +505,7 @@ function parseObject(s: ITokenStream, isStatic: boolean): Ast.Node { // noop } else { if (!s.token.hasLeftSpacing) { - throw new AiScriptSyntaxError('separator expected'); + throw new AiScriptSyntaxError('separator expected', s.token.loc); } } @@ -546,7 +546,7 @@ function parseArray(s: ITokenStream, isStatic: boolean): Ast.Node { // noop } else { if (!s.token.hasLeftSpacing) { - throw new AiScriptSyntaxError('separator expected'); + throw new AiScriptSyntaxError('separator expected', s.token.loc); } } diff --git a/src/parser/syntaxes/statements.ts b/src/parser/syntaxes/statements.ts index f36ba94d..c5655175 100644 --- a/src/parser/syntaxes/statements.ts +++ b/src/parser/syntaxes/statements.ts @@ -72,7 +72,7 @@ export function parseDefStatement(s: ITokenStream): Ast.Node { return parseFnDef(s); } default: { - throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[s.kind]}`); + throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[s.kind]}`, s.token.loc); } } } @@ -112,7 +112,7 @@ function parseVarDef(s: ITokenStream): Ast.Node { break; } default: { - throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[s.kind]}`); + throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[s.kind]}`, s.token.loc); } } s.next(); @@ -213,7 +213,7 @@ function parseEach(s: ITokenStream): Ast.Node { if (s.kind === TokenKind.Comma) { s.next(); } else if (!s.token.hasLeftSpacing) { - throw new AiScriptSyntaxError('separator expected'); + throw new AiScriptSyntaxError('separator expected', s.token.loc); } const items = parseExpr(s, false); @@ -263,7 +263,7 @@ function parseFor(s: ITokenStream): Ast.Node { if ((s.kind as TokenKind) === TokenKind.Comma) { s.next(); } else if (!s.token.hasLeftSpacing) { - throw new AiScriptSyntaxError('separator expected'); + throw new AiScriptSyntaxError('separator expected', s.token.loc); } const to = parseExpr(s, false); @@ -326,7 +326,7 @@ function parseStatementWithAttr(s: ITokenStream): Ast.Node { const statement = parseStatement(s); if (statement.type !== 'def') { - throw new AiScriptSyntaxError('invalid attribute.'); + throw new AiScriptSyntaxError('invalid attribute.', statement.loc); } if (statement.attr != null) { statement.attr.push(...attrs); diff --git a/src/parser/syntaxes/toplevel.ts b/src/parser/syntaxes/toplevel.ts index cd6f8ba2..a84e60de 100644 --- a/src/parser/syntaxes/toplevel.ts +++ b/src/parser/syntaxes/toplevel.ts @@ -36,7 +36,7 @@ export function parseTopLevel(s: ITokenStream): Ast.Node[] { } if ((s.kind as TokenKind) !== TokenKind.NewLine && (s.kind as TokenKind) !== TokenKind.EOF) { - throw new AiScriptSyntaxError('Multiple statements cannot be placed on a single line.'); + throw new AiScriptSyntaxError('Multiple statements cannot be placed on a single line.', s.token.loc); } while ((s.kind as TokenKind) === TokenKind.NewLine) { s.next(); @@ -82,7 +82,7 @@ export function parseNamespace(s: ITokenStream): Ast.Node { } if ((s.kind as TokenKind) !== TokenKind.NewLine && (s.kind as TokenKind) !== TokenKind.CloseBrace) { - throw new AiScriptSyntaxError('Multiple statements cannot be placed on a single line.'); + throw new AiScriptSyntaxError('Multiple statements cannot be placed on a single line.', s.token.loc); } while ((s.kind as TokenKind) === TokenKind.NewLine) { s.next(); diff --git a/src/type.ts b/src/type.ts index 5e93acf0..54cd9f0f 100644 --- a/src/type.ts +++ b/src/type.ts @@ -151,7 +151,7 @@ export function getTypeBySource(typeSource: Ast.TypeSource): Type { return T_GENERIC(typeSource.name, [innerType]); } } - throw new AiScriptSyntaxError(`Unknown type: '${getTypeNameBySource(typeSource)}'`); + throw new AiScriptSyntaxError(`Unknown type: '${getTypeNameBySource(typeSource)}'`, typeSource.loc); } else { const argTypes = typeSource.args.map(arg => getTypeBySource(arg)); return T_FN(argTypes, getTypeBySource(typeSource.result));