From 5f07afde0ac3d8cb700c62e3f482d825e3847ef4 Mon Sep 17 00:00:00 2001 From: takejohn Date: Mon, 11 Nov 2024 00:37:07 +0900 Subject: [PATCH 1/6] =?UTF-8?q?REPL=E3=82=92scripts=E3=81=AB=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + console.js => scripts/repl.mjs | 0 2 files changed, 1 insertion(+) rename console.js => scripts/repl.mjs (100%) diff --git a/package.json b/package.json index f67ed1ed..98e058f3 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "scripts": { "start": "node ./scripts/start.mjs", "parse": "node ./scripts/parse.mjs", + "repl": "node ./scripts/repl.mjs", "ts": "npm run ts-esm && npm run ts-dts", "ts-esm": "tsc --outDir built/esm", "ts-dts": "tsc --outDir built/dts --declaration true --emitDeclarationOnly true --declarationMap true", diff --git a/console.js b/scripts/repl.mjs similarity index 100% rename from console.js rename to scripts/repl.mjs From ef18095c647ab267137a0f10e55f24af689d6a67 Mon Sep 17 00:00:00 2001 From: takejohn Date: Mon, 11 Nov 2024 00:37:14 +0900 Subject: [PATCH 2/6] =?UTF-8?q?=E3=83=89=E3=82=AD=E3=83=A5=E3=83=A1?= =?UTF-8?q?=E3=83=B3=E3=83=88=E3=81=AB=E8=BF=BD=E8=A8=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/execution.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/execution.md b/docs/execution.md index 16981277..0240ba99 100644 --- a/docs/execution.md +++ b/docs/execution.md @@ -36,3 +36,10 @@ $ npm run start ``` $ npm run parse ``` + +## 4. REPL上で実行 +コマンドライン上で対話的にコードを実行します。 +以下のコマンドを実行し、コードを入力します。 +``` +$npm run repl +``` From 76f852005f7dd6d747ec1dbcdc08cb8214d95f80 Mon Sep 17 00:00:00 2001 From: takejohn Date: Mon, 11 Nov 2024 00:41:33 +0900 Subject: [PATCH 3/6] =?UTF-8?q?Interpreter=E3=82=92=E4=BF=9D=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/repl.mjs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/repl.mjs b/scripts/repl.mjs index 13a18711..28a56224 100644 --- a/scripts/repl.mjs +++ b/scripts/repl.mjs @@ -36,14 +36,12 @@ const getInterpreter = () => new Interpreter({}, { } }); -let interpreter; +const interpreter = getInterpreter(); async function main(){ let a = await i.question('> '); - interpreter?.abort(); if (a === 'exit') return false; try { let ast = Parser.parse(a); - interpreter = getInterpreter(); await interpreter.exec(ast); } catch(e) { console.log(chalk.red(`${e}`)); From 2bda9c2e7df2bd206f8cf1ef3963b6f41462937a Mon Sep 17 00:00:00 2001 From: takejohn Date: Mon, 11 Nov 2024 14:14:00 +0900 Subject: [PATCH 4/6] =?UTF-8?q?REPL=E3=82=92=E8=A4=87=E6=95=B0=E8=A1=8C?= =?UTF-8?q?=E5=85=A5=E5=8A=9B=E3=81=AB=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- etc/aiscript.api.md | 6 ++++++ scripts/repl.mjs | 34 ++++++++++++++++++++++++------ src/error.ts | 11 ++++++++++ src/parser/scanner.ts | 15 +++++++------ src/parser/streams/token-stream.ts | 4 ++-- src/parser/syntaxes/common.ts | 11 +++++++++- src/parser/syntaxes/expressions.ts | 23 ++++++++++++++------ src/parser/syntaxes/statements.ts | 6 +++--- src/parser/syntaxes/toplevel.ts | 5 ++++- src/parser/utils.ts | 11 ++++++++++ 10 files changed, 99 insertions(+), 27 deletions(-) diff --git a/etc/aiscript.api.md b/etc/aiscript.api.md index 70e7f955..ae027d00 100644 --- a/etc/aiscript.api.md +++ b/etc/aiscript.api.md @@ -80,6 +80,11 @@ class AiScriptTypeError extends AiScriptError { pos: Pos; } +// @public +class AiScriptUnexpectedEOFError extends AiScriptSyntaxError { + constructor(pos: Pos, info?: unknown); +} + // @public class AiScriptUserError extends AiScriptRuntimeError { constructor(message: string, info?: unknown); @@ -284,6 +289,7 @@ declare namespace errors { AiScriptError, NonAiScriptError, AiScriptSyntaxError, + AiScriptUnexpectedEOFError, AiScriptTypeError, AiScriptNamespaceError, AiScriptRuntimeError, diff --git a/scripts/repl.mjs b/scripts/repl.mjs index 28a56224..744bc93b 100644 --- a/scripts/repl.mjs +++ b/scripts/repl.mjs @@ -1,6 +1,6 @@ import * as readline from 'readline/promises'; import chalk from 'chalk'; -import { Parser, Interpreter, utils } from '@syuilo/aiscript'; +import { errors, Parser, Interpreter, utils } from '@syuilo/aiscript'; const { valToString } = utils; const i = readline.createInterface({ @@ -14,7 +14,7 @@ https://github.com/syuilo/aiscript Type 'exit' to end this session.`); -const getInterpreter = () => new Interpreter({}, { +const interpreter = new Interpreter({}, { in(q) { return i.question(q + ': '); }, @@ -36,12 +36,34 @@ const getInterpreter = () => new Interpreter({}, { } }); -const interpreter = getInterpreter(); -async function main(){ +async function getAst() { + let script = ''; let a = await i.question('> '); - if (a === 'exit') return false; + while (true) { + try { + if (a === 'exit') return null; + script += a; + let ast = Parser.parse(script); + script = ''; + return ast; + } catch(e) { + if (e instanceof errors.AiScriptUnexpectedEOFError) { + script += '\n'; + a = await i.question('... '); + } else { + script = ''; + throw e; + } + } + } +} + +async function main(){ try { - let ast = Parser.parse(a); + let ast = await getAst(); + if (ast == null) { + return false; + } await interpreter.exec(ast); } catch(e) { console.log(chalk.red(`${e}`)); diff --git a/src/error.ts b/src/error.ts index e62c9b68..2e1acf8e 100644 --- a/src/error.ts +++ b/src/error.ts @@ -1,3 +1,4 @@ +import { TokenKind } from './parser/token.js'; import type { Pos } from './node.js'; export abstract class AiScriptError extends Error { @@ -40,6 +41,16 @@ export class AiScriptSyntaxError extends AiScriptError { super(`${message} (Line ${pos.line}, Column ${pos.column})`, info); } } + +/** + * Unexpected EOF errors. + */ +export class AiScriptUnexpectedEOFError extends AiScriptSyntaxError { + constructor(pos: Pos, info?: unknown) { + super('unexpected EOF', pos, info); + } +} + /** * Type validation(parser/plugins/validate-type) errors. */ diff --git a/src/parser/scanner.ts b/src/parser/scanner.ts index 691c3f68..401f2bd3 100644 --- a/src/parser/scanner.ts +++ b/src/parser/scanner.ts @@ -1,6 +1,7 @@ -import { AiScriptSyntaxError } from '../error.js'; +import { AiScriptSyntaxError, AiScriptUnexpectedEOFError } from '../error.js'; import { CharStream } from './streams/char-stream.js'; import { TOKEN, TokenKind } from './token.js'; +import { unexpectedTokenError } from './utils.js'; import type { ITokenStream } from './streams/token-stream.js'; import type { Token, TokenPosition } from './token.js'; @@ -96,7 +97,7 @@ export class Scanner implements ITokenStream { */ public expect(kind: TokenKind): void { if (!this.is(kind)) { - throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[this.getTokenKind()]}`, this.getPos()); + throw unexpectedTokenError(this.getTokenKind(), this.getPos()); } } @@ -456,7 +457,7 @@ export class Scanner implements ITokenStream { switch (state) { case 'string': { if (this.stream.eof) { - throw new AiScriptSyntaxError('unexpected EOF', pos); + throw new AiScriptUnexpectedEOFError(pos); } if (this.stream.char === '\\') { this.stream.next(); @@ -474,7 +475,7 @@ export class Scanner implements ITokenStream { } case 'escape': { if (this.stream.eof) { - throw new AiScriptSyntaxError('unexpected EOF', pos); + throw new AiScriptUnexpectedEOFError(pos); } value += this.stream.char; this.stream.next(); @@ -502,7 +503,7 @@ export class Scanner implements ITokenStream { case 'string': { // テンプレートの終了が無いままEOFに達した if (this.stream.eof) { - throw new AiScriptSyntaxError('unexpected EOF', pos); + throw new AiScriptUnexpectedEOFError(pos); } // エスケープ if (this.stream.char === '\\') { @@ -538,7 +539,7 @@ export class Scanner implements ITokenStream { case 'escape': { // エスケープ対象の文字が無いままEOFに達した if (this.stream.eof) { - throw new AiScriptSyntaxError('unexpected EOF', pos); + throw new AiScriptUnexpectedEOFError(pos); } // 普通の文字として取り込み buf += this.stream.char; @@ -550,7 +551,7 @@ export class Scanner implements ITokenStream { case 'expr': { // 埋め込み式の終端記号が無いままEOFに達した if (this.stream.eof) { - throw new AiScriptSyntaxError('unexpected EOF', pos); + throw new AiScriptUnexpectedEOFError(pos); } // 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 bf727a30..e21b66a8 100644 --- a/src/parser/streams/token-stream.ts +++ b/src/parser/streams/token-stream.ts @@ -1,5 +1,5 @@ -import { AiScriptSyntaxError } from '../../error.js'; import { TOKEN, TokenKind } from '../token.js'; +import { unexpectedTokenError } from '../utils.js'; import type { Token, TokenPosition } from '../token.js'; /** @@ -131,7 +131,7 @@ export class TokenStream implements ITokenStream { */ public expect(kind: TokenKind): void { if (!this.is(kind)) { - throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[this.getTokenKind()]}`, this.getPos()); + throw unexpectedTokenError(this.getTokenKind(), this.getPos()); } } diff --git a/src/parser/syntaxes/common.ts b/src/parser/syntaxes/common.ts index 48cb68b3..a9216823 100644 --- a/src/parser/syntaxes/common.ts +++ b/src/parser/syntaxes/common.ts @@ -1,5 +1,5 @@ import { TokenKind } from '../token.js'; -import { AiScriptSyntaxError } from '../../error.js'; +import { AiScriptSyntaxError, AiScriptUnexpectedEOFError } from '../../error.js'; import { NODE } from '../utils.js'; import { parseStatement } from './statements.js'; import { parseExpr } from './expressions.js'; @@ -75,6 +75,9 @@ export function parseParams(s: ITokenStream): Ast.Fn['params'] { case TokenKind.CloseParen: { break; } + case TokenKind.EOF: { + throw new AiScriptUnexpectedEOFError(s.getPos()); + } default: { throw new AiScriptSyntaxError('separator expected', s.getPos()); } @@ -116,6 +119,9 @@ export function parseBlock(s: ITokenStream): (Ast.Statement | Ast.Expression)[] case TokenKind.CloseBrace: { break; } + case TokenKind.EOF: { + throw new AiScriptUnexpectedEOFError(s.getPos()); + } default: { throw new AiScriptSyntaxError('Multiple statements cannot be placed on a single line.', s.getPos()); } @@ -184,6 +190,9 @@ function parseFnType(s: ITokenStream): Ast.TypeSource { s.next(); break; } + case TokenKind.EOF: { + throw new AiScriptUnexpectedEOFError(s.getPos()); + } default: { throw new AiScriptSyntaxError('separator expected', s.getPos()); } diff --git a/src/parser/syntaxes/expressions.ts b/src/parser/syntaxes/expressions.ts index 80887680..39ca2591 100644 --- a/src/parser/syntaxes/expressions.ts +++ b/src/parser/syntaxes/expressions.ts @@ -1,5 +1,5 @@ -import { AiScriptSyntaxError } from '../../error.js'; -import { NODE } from '../utils.js'; +import { AiScriptSyntaxError, AiScriptUnexpectedEOFError } from '../../error.js'; +import { NODE, unexpectedTokenError } from '../utils.js'; import { TokenStream } from '../streams/token-stream.js'; import { TokenKind } from '../token.js'; import { parseBlock, parseOptionalSeparator, parseParams, parseType } from './common.js'; @@ -92,7 +92,7 @@ function parsePrefix(s: ITokenStream, minBp: number): Ast.Expression { return NODE('not', { expr }, startPos, endPos); } default: { - throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[op]}`, startPos); + throw unexpectedTokenError(op, startPos); } } } @@ -166,7 +166,7 @@ function parseInfix(s: ITokenStream, left: Ast.Expression, minBp: number): Ast.E return NODE('or', { left, right }, startPos, endPos); } default: { - throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[op]}`, startPos); + throw unexpectedTokenError(op, startPos); } } } @@ -192,7 +192,7 @@ function parsePostfix(s: ITokenStream, expr: Ast.Expression): Ast.Expression { }, startPos, s.getPos()); } default: { - throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[op]}`, startPos); + throw unexpectedTokenError(op, startPos); } } } @@ -243,7 +243,7 @@ function parseAtom(s: ITokenStream, isStatic: boolean): Ast.Expression { break; } default: { - throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[element.kind]}`, element.pos); + throw unexpectedTokenError(element.kind, element.pos); } } } @@ -290,7 +290,7 @@ function parseAtom(s: ITokenStream, isStatic: boolean): Ast.Expression { return expr; } } - throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[s.getTokenKind()]}`, startPos); + throw unexpectedTokenError(s.getTokenKind(), startPos); } /** @@ -326,6 +326,9 @@ function parseCall(s: ITokenStream, target: Ast.Expression): Ast.Call { case TokenKind.CloseParen: { break; } + case TokenKind.EOF: { + throw new AiScriptUnexpectedEOFError(s.getPos()); + } default: { throw new AiScriptSyntaxError('separator expected', s.getPos()); } @@ -580,6 +583,9 @@ function parseObject(s: ITokenStream, isStatic: boolean): Ast.Obj { case TokenKind.CloseBrace: { break; } + case TokenKind.EOF: { + throw new AiScriptUnexpectedEOFError(s.getPos()); + } default: { throw new AiScriptSyntaxError('separator expected', s.getPos()); } @@ -624,6 +630,9 @@ function parseArray(s: ITokenStream, isStatic: boolean): Ast.Arr { case TokenKind.CloseBracket: { break; } + case TokenKind.EOF: { + throw new AiScriptUnexpectedEOFError(s.getPos()); + } default: { throw new AiScriptSyntaxError('separator expected', s.getPos()); } diff --git a/src/parser/syntaxes/statements.ts b/src/parser/syntaxes/statements.ts index 52719dbf..5097313f 100644 --- a/src/parser/syntaxes/statements.ts +++ b/src/parser/syntaxes/statements.ts @@ -1,5 +1,5 @@ import { AiScriptSyntaxError } from '../../error.js'; -import { CALL_NODE, NODE } from '../utils.js'; +import { CALL_NODE, NODE, unexpectedTokenError } from '../utils.js'; import { TokenKind } from '../token.js'; import { parseBlock, parseDest, parseParams, parseType } from './common.js'; import { parseExpr } from './expressions.js'; @@ -78,7 +78,7 @@ export function parseDefStatement(s: ITokenStream): Ast.Definition { return parseFnDef(s); } default: { - throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[s.getTokenKind()]}`, s.getPos()); + throw unexpectedTokenError(s.getTokenKind(), s.getPos()); } } } @@ -117,7 +117,7 @@ function parseVarDef(s: ITokenStream): Ast.Definition { break; } default: { - throw new AiScriptSyntaxError(`unexpected token: ${TokenKind[s.getTokenKind()]}`, s.getPos()); + throw unexpectedTokenError(s.getTokenKind(), s.getPos()); } } s.next(); diff --git a/src/parser/syntaxes/toplevel.ts b/src/parser/syntaxes/toplevel.ts index 32a48fe9..91a07787 100644 --- a/src/parser/syntaxes/toplevel.ts +++ b/src/parser/syntaxes/toplevel.ts @@ -1,6 +1,6 @@ import { NODE } from '../utils.js'; import { TokenKind } from '../token.js'; -import { AiScriptSyntaxError } from '../../error.js'; +import { AiScriptSyntaxError, AiScriptUnexpectedEOFError } from '../../error.js'; import { parseDefStatement, parseStatement } from './statements.js'; import { parseExpr } from './expressions.js'; @@ -105,6 +105,9 @@ export function parseNamespace(s: ITokenStream): Ast.Namespace { case TokenKind.CloseBrace: { break; } + case TokenKind.EOF: { + throw new AiScriptUnexpectedEOFError(s.getPos()); + } default: { throw new AiScriptSyntaxError('Multiple statements cannot be placed on a single line.', s.getPos()); } diff --git a/src/parser/utils.ts b/src/parser/utils.ts index a029a2b4..c3e97d77 100644 --- a/src/parser/utils.ts +++ b/src/parser/utils.ts @@ -1,3 +1,6 @@ +import { AiScriptSyntaxError, AiScriptUnexpectedEOFError } from '../error.js'; +import { TokenKind } from './token.js'; +import type { AiScriptError } from '../error.js'; import type * as Ast from '../node.js'; export function NODE( @@ -29,3 +32,11 @@ export function CALL_NODE( args, }, start, end); } + +export function unexpectedTokenError(token: TokenKind, pos: Ast.Pos, info?: unknown): AiScriptError { + if (token === TokenKind.EOF) { + return new AiScriptUnexpectedEOFError(pos, info); + } else { + return new AiScriptSyntaxError(`unexpected token: ${TokenKind[token]}`, pos, info); + } +} From 298ec65fbbf6a7305cd986ee9912d25ab31a790e Mon Sep 17 00:00:00 2001 From: takejohn Date: Mon, 11 Nov 2024 14:18:26 +0900 Subject: [PATCH 5/6] =?UTF-8?q?REPL=E3=81=AE=E7=B5=82=E4=BA=86=E3=82=B3?= =?UTF-8?q?=E3=83=9E=E3=83=B3=E3=83=89=E3=82=92=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/repl.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/repl.mjs b/scripts/repl.mjs index 744bc93b..76a2ce9a 100644 --- a/scripts/repl.mjs +++ b/scripts/repl.mjs @@ -12,7 +12,7 @@ console.log( `Welcome to AiScript! https://github.com/syuilo/aiscript -Type 'exit' to end this session.`); +Type '.exit' to end this session.`); const interpreter = new Interpreter({}, { in(q) { @@ -41,7 +41,7 @@ async function getAst() { let a = await i.question('> '); while (true) { try { - if (a === 'exit') return null; + if (a === '.exit') return null; script += a; let ast = Parser.parse(script); script = ''; From 2f1c578398ed79076b2df17eb33d63ebc4cb3e40 Mon Sep 17 00:00:00 2001 From: Take-John Date: Fri, 15 Nov 2024 14:16:59 +0900 Subject: [PATCH 6/6] =?UTF-8?q?=E6=94=B9=E8=A1=8C=E6=99=82=E3=81=AB?= =?UTF-8?q?=E6=B7=B1=E3=81=95=E3=81=8C=E5=A4=89=E3=82=8F=E3=82=89=E3=81=AA?= =?UTF-8?q?=E3=81=84=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: salano_ym <53254905+salano-ym@users.noreply.github.com> --- scripts/repl.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/repl.mjs b/scripts/repl.mjs index 76a2ce9a..d385f88b 100644 --- a/scripts/repl.mjs +++ b/scripts/repl.mjs @@ -38,7 +38,7 @@ const interpreter = new Interpreter({}, { async function getAst() { let script = ''; - let a = await i.question('> '); + let a = await i.question('>>> '); while (true) { try { if (a === '.exit') return null;