From ba014558abcebbc53cf0d5359f54158dd1cdc4a1 Mon Sep 17 00:00:00 2001 From: takejohn Date: Thu, 26 Dec 2024 16:44:48 +0900 Subject: [PATCH 01/17] =?UTF-8?q?=E3=83=97=E3=83=AD=E3=83=91=E3=83=86?= =?UTF-8?q?=E3=82=A3=E3=81=AE=E3=83=81=E3=82=A7=E3=83=83=E3=82=AF=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E3=82=92=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/error.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/error.ts b/src/error.ts index 2e1acf8e..5ab0b015 100644 --- a/src/error.ts +++ b/src/error.ts @@ -1,4 +1,3 @@ -import { TokenKind } from './parser/token.js'; import type { Pos } from './node.js'; export abstract class AiScriptError extends Error { @@ -25,9 +24,12 @@ export abstract class AiScriptError extends Error { export class NonAiScriptError extends AiScriptError { public name = 'Internal'; constructor(error: unknown) { - const message = String( - (error as { message?: unknown } | null | undefined)?.message ?? error, - ); + let message: string; + if (error != null && typeof error === 'object' && 'message' in error) { + message = String(error.message); + } else { + message = String(error); + } super(message, error); } } From 0887e798851d6186412fb46fbdce505589f1cb65 Mon Sep 17 00:00:00 2001 From: takejohn Date: Thu, 26 Dec 2024 16:58:54 +0900 Subject: [PATCH 02/17] =?UTF-8?q?=E5=BC=95=E6=95=B0=E3=83=AA=E3=82=B9?= =?UTF-8?q?=E3=83=88=E3=81=AE=E8=A6=8F=E5=AE=9A=E5=80=A4=E3=81=AE=E5=BC=8F?= =?UTF-8?q?=E3=82=92=E9=A0=86=E3=81=AB=E8=A9=95=E4=BE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interpreter/index.ts | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/src/interpreter/index.ts b/src/interpreter/index.ts index acc12d21..08e4c562 100644 --- a/src/interpreter/index.ts +++ b/src/interpreter/index.ts @@ -14,7 +14,7 @@ import { getPrimProp } from './primitive-props.js'; import { Variable } from './variable.js'; import { Reference } from './reference.js'; import type { JsValue } from './util.js'; -import type { Value, VFn, VUserFn } from './value.js'; +import type { Value, VFn, VFnParam } from './value.js'; export type LogObject = { scope?: string; @@ -670,28 +670,21 @@ export class Interpreter { } case 'fn': { - const params = await Promise.all(node.params.map(async (param) => { - return { + const params: VFnParam[] = []; + for (const param of node.params) { + const defaultValue = param.default ? await this._eval(param.default, scope, callStack) : + param.optional ? NULL : + undefined; + if (defaultValue != null && isControl(defaultValue)) { + return defaultValue; + } + params.push({ dest: param.dest, - default: - param.default ? await this._eval(param.default, scope, callStack) : - param.optional ? NULL : - undefined, + default: defaultValue, // type: (TODO) - }; - })); - const control = params - .map((param) => param.default) - .filter((value) => value != null) - .find(isControl); - if (control != null) { - return control; - } - return FN( - params as VUserFn['params'], - node.children, - scope, - ); + }); + } + return FN(params, node.children, scope); } case 'block': { From 1903e6ce242f831fb056702d94bce4ce711f90c5 Mon Sep 17 00:00:00 2001 From: takejohn Date: Thu, 26 Dec 2024 17:01:54 +0900 Subject: [PATCH 03/17] =?UTF-8?q?=E7=B5=84=E3=81=BF=E8=BE=BC=E3=81=BF?= =?UTF-8?q?=E3=83=97=E3=83=AD=E3=83=91=E3=83=86=E3=82=A3=E3=82=92=E6=8C=81?= =?UTF-8?q?=E3=81=9F=E3=81=AA=E3=81=84=E5=9E=8B=E3=82=92=E6=98=8E=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interpreter/primitive-props.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/interpreter/primitive-props.ts b/src/interpreter/primitive-props.ts index df3c18cc..df50950a 100644 --- a/src/interpreter/primitive-props.ts +++ b/src/interpreter/primitive-props.ts @@ -10,6 +10,8 @@ type VWithPP = VNum|VStr|VArr|VError; const PRIMITIVE_PROPS: { [key in VWithPP['type']]: { [key: string]: (target: Value) => Value } +} & { + [key in (Exclude)['type']]?: never } = { num: { to_str: (target: VNum): VFn => FN_NATIVE(async (_, _opts) => { @@ -433,8 +435,8 @@ const PRIMITIVE_PROPS: { } as const; export function getPrimProp(target: Value, name: string): Value { - if (Object.hasOwn(PRIMITIVE_PROPS, target.type)) { - const props = PRIMITIVE_PROPS[target.type as VWithPP['type']]; + const props = PRIMITIVE_PROPS[target.type]; + if (props != null) { if (Object.hasOwn(props, name)) { return props[name]!(target); } else { From 3a1800811d973194e0ed254a9d56910d2ef0ec27 Mon Sep 17 00:00:00 2001 From: takejohn Date: Thu, 26 Dec 2024 17:07:56 +0900 Subject: [PATCH 04/17] =?UTF-8?q?CharStream=E3=81=AEeof,=20char=E3=82=92ge?= =?UTF-8?q?tter=E3=81=8B=E3=82=89=E3=83=A1=E3=82=BD=E3=83=83=E3=83=89?= =?UTF-8?q?=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/scanner.ts | 112 +++++++++++++++--------------- src/parser/streams/char-stream.ts | 14 ++-- test/parser.ts | 30 ++++---- 3 files changed, 78 insertions(+), 78 deletions(-) diff --git a/src/parser/scanner.ts b/src/parser/scanner.ts index 5cab470f..b7efa604 100644 --- a/src/parser/scanner.ts +++ b/src/parser/scanner.ts @@ -105,11 +105,11 @@ export class Scanner implements ITokenStream { let hasLeftSpacing = false; while (true) { - if (this.stream.eof) { + if (this.stream.eof()) { return TOKEN(TokenKind.EOF, this.stream.getPos(), { hasLeftSpacing }); } // skip spasing - if (spaceChars.includes(this.stream.char)) { + if (spaceChars.includes(this.stream.char())) { this.stream.next(); hasLeftSpacing = true; continue; @@ -118,17 +118,17 @@ export class Scanner implements ITokenStream { // トークン位置を記憶 const pos = this.stream.getPos(); - if (lineBreakChars.includes(this.stream.char)) { + if (lineBreakChars.includes(this.stream.char())) { this.skipEmptyLines(); return TOKEN(TokenKind.NewLine, pos, { hasLeftSpacing }); } // noFallthroughCasesInSwitchと関数の返り値の型を利用し、全ての場合分けがreturnかcontinueで適切に処理されることを強制している // その都合上、break文の使用ないしこのswitch文の後に処理を書くことは極力避けてほしい - switch (this.stream.char) { + switch (this.stream.char()) { case '!': { this.stream.next(); - if (!this.stream.eof && (this.stream.char as string) === '=') { + if (!this.stream.eof() && (this.stream.char()) === '=') { this.stream.next(); return TOKEN(TokenKind.NotEq, pos, { hasLeftSpacing }); } else { @@ -141,15 +141,15 @@ export class Scanner implements ITokenStream { } case '#': { this.stream.next(); - if (!this.stream.eof && (this.stream.char as string) === '#') { + if (!this.stream.eof() && (this.stream.char()) === '#') { this.stream.next(); - if (!this.stream.eof && (this.stream.char as string) === '#') { + if (!this.stream.eof() && (this.stream.char()) === '#') { this.stream.next(); return TOKEN(TokenKind.Sharp3, pos, { hasLeftSpacing }); } else { throw new AiScriptSyntaxError('invalid sequence of characters: "##"', pos); } - } else if (!this.stream.eof && (this.stream.char as string) === '[') { + } else if (!this.stream.eof() && (this.stream.char()) === '[') { this.stream.next(); return TOKEN(TokenKind.OpenSharpBracket, pos, { hasLeftSpacing }); } else { @@ -162,7 +162,7 @@ export class Scanner implements ITokenStream { } case '&': { this.stream.next(); - if (!this.stream.eof && (this.stream.char as string) === '&') { + if (!this.stream.eof() && (this.stream.char()) === '&') { this.stream.next(); return TOKEN(TokenKind.And2, pos, { hasLeftSpacing }); } else { @@ -183,7 +183,7 @@ export class Scanner implements ITokenStream { } case '+': { this.stream.next(); - if (!this.stream.eof && (this.stream.char as string) === '=') { + if (!this.stream.eof() && (this.stream.char()) === '=') { this.stream.next(); return TOKEN(TokenKind.PlusEq, pos, { hasLeftSpacing }); } else { @@ -196,7 +196,7 @@ export class Scanner implements ITokenStream { } case '-': { this.stream.next(); - if (!this.stream.eof && (this.stream.char as string) === '=') { + if (!this.stream.eof() && (this.stream.char()) === '=') { this.stream.next(); return TOKEN(TokenKind.MinusEq, pos, { hasLeftSpacing }); } else { @@ -209,11 +209,11 @@ export class Scanner implements ITokenStream { } case '/': { this.stream.next(); - if (!this.stream.eof && (this.stream.char as string) === '*') { + if (!this.stream.eof() && (this.stream.char()) === '*') { this.stream.next(); this.skipCommentRange(); continue; - } else if (!this.stream.eof && (this.stream.char as string) === '/') { + } else if (!this.stream.eof() && (this.stream.char()) === '/') { this.stream.next(); this.skipCommentLine(); continue; @@ -223,7 +223,7 @@ export class Scanner implements ITokenStream { } case ':': { this.stream.next(); - if (!this.stream.eof && (this.stream.char as string) === ':') { + if (!this.stream.eof() && (this.stream.char()) === ':') { this.stream.next(); return TOKEN(TokenKind.Colon2, pos, { hasLeftSpacing }); } else { @@ -236,10 +236,10 @@ export class Scanner implements ITokenStream { } case '<': { this.stream.next(); - if (!this.stream.eof && (this.stream.char as string) === '=') { + if (!this.stream.eof() && (this.stream.char()) === '=') { this.stream.next(); return TOKEN(TokenKind.LtEq, pos, { hasLeftSpacing }); - } else if (!this.stream.eof && (this.stream.char as string) === ':') { + } else if (!this.stream.eof() && (this.stream.char()) === ':') { this.stream.next(); return TOKEN(TokenKind.Out, pos, { hasLeftSpacing }); } else { @@ -248,10 +248,10 @@ export class Scanner implements ITokenStream { } case '=': { this.stream.next(); - if (!this.stream.eof && (this.stream.char as string) === '=') { + if (!this.stream.eof() && (this.stream.char()) === '=') { this.stream.next(); return TOKEN(TokenKind.Eq2, pos, { hasLeftSpacing }); - } else if (!this.stream.eof && (this.stream.char as string) === '>') { + } else if (!this.stream.eof() && (this.stream.char()) === '>') { this.stream.next(); return TOKEN(TokenKind.Arrow, pos, { hasLeftSpacing }); } else { @@ -260,7 +260,7 @@ export class Scanner implements ITokenStream { } case '>': { this.stream.next(); - if (!this.stream.eof && (this.stream.char as string) === '=') { + if (!this.stream.eof() && (this.stream.char()) === '=') { this.stream.next(); return TOKEN(TokenKind.GtEq, pos, { hasLeftSpacing }); } else { @@ -300,7 +300,7 @@ export class Scanner implements ITokenStream { } case '|': { this.stream.next(); - if (!this.stream.eof && (this.stream.char as string) === '|') { + if (!this.stream.eof() && (this.stream.char()) === '|') { this.stream.next(); return TOKEN(TokenKind.Or2, pos, { hasLeftSpacing }); } else { @@ -318,7 +318,7 @@ export class Scanner implements ITokenStream { const wordToken = this.tryReadWord(hasLeftSpacing); if (wordToken) return wordToken; - throw new AiScriptSyntaxError(`invalid character: "${this.stream.char}"`, pos); + throw new AiScriptSyntaxError(`invalid character: "${this.stream.char()}"`, pos); } } // Use `return` or `continue` before reaching this line. @@ -335,8 +335,8 @@ export class Scanner implements ITokenStream { const pos = this.stream.getPos(); - while (!this.stream.eof && wordChar.test(this.stream.char)) { - value += this.stream.char; + while (!this.stream.eof() && wordChar.test(this.stream.char())) { + value += this.stream.char(); this.stream.next(); } if (value.length === 0) { @@ -419,17 +419,17 @@ export class Scanner implements ITokenStream { const pos = this.stream.getPos(); - while (!this.stream.eof && digit.test(this.stream.char)) { - wholeNumber += this.stream.char; + while (!this.stream.eof() && digit.test(this.stream.char())) { + wholeNumber += this.stream.char(); this.stream.next(); } if (wholeNumber.length === 0) { return; } - if (!this.stream.eof && this.stream.char === '.') { + if (!this.stream.eof() && this.stream.char() === '.') { this.stream.next(); - while (!this.stream.eof as boolean && digit.test(this.stream.char as string)) { - fractional += this.stream.char; + while (!this.stream.eof() && digit.test(this.stream.char())) { + fractional += this.stream.char(); this.stream.next(); } if (fractional.length === 0) { @@ -447,7 +447,7 @@ export class Scanner implements ITokenStream { private readStringLiteral(hasLeftSpacing: boolean): Token { let value = ''; - const literalMark = this.stream.char; + const literalMark = this.stream.char(); let state: 'string' | 'escape' | 'finish' = 'string'; const pos = this.stream.getPos(); @@ -456,28 +456,28 @@ export class Scanner implements ITokenStream { while (state !== 'finish') { switch (state) { case 'string': { - if (this.stream.eof) { + if (this.stream.eof()) { throw new AiScriptUnexpectedEOFError(pos); } - if (this.stream.char === '\\') { + if (this.stream.char() === '\\') { this.stream.next(); state = 'escape'; break; } - if (this.stream.char === literalMark) { + if (this.stream.char() === literalMark) { this.stream.next(); state = 'finish'; break; } - value += this.stream.char; + value += this.stream.char(); this.stream.next(); break; } case 'escape': { - if (this.stream.eof) { + if (this.stream.eof()) { throw new AiScriptUnexpectedEOFError(pos); } - value += this.stream.char; + value += this.stream.char(); this.stream.next(); state = 'string'; break; @@ -502,17 +502,17 @@ export class Scanner implements ITokenStream { switch (state) { case 'string': { // テンプレートの終了が無いままEOFに達した - if (this.stream.eof) { + if (this.stream.eof()) { throw new AiScriptUnexpectedEOFError(pos); } // エスケープ - if (this.stream.char === '\\') { + if (this.stream.char() === '\\') { this.stream.next(); state = 'escape'; break; } // テンプレートの終了 - if (this.stream.char === '`') { + if (this.stream.char() === '`') { this.stream.next(); if (buf.length > 0) { elements.push(TOKEN(TokenKind.TemplateStringElement, elementPos, { hasLeftSpacing, value: buf })); @@ -521,7 +521,7 @@ export class Scanner implements ITokenStream { break; } // 埋め込み式の開始 - if (this.stream.char === '{') { + if (this.stream.char() === '{') { this.stream.next(); if (buf.length > 0) { elements.push(TOKEN(TokenKind.TemplateStringElement, elementPos, { hasLeftSpacing, value: buf })); @@ -532,17 +532,17 @@ export class Scanner implements ITokenStream { state = 'expr'; break; } - buf += this.stream.char; + buf += this.stream.char(); this.stream.next(); break; } case 'escape': { // エスケープ対象の文字が無いままEOFに達した - if (this.stream.eof) { + if (this.stream.eof()) { throw new AiScriptUnexpectedEOFError(pos); } // 普通の文字として取り込み - buf += this.stream.char; + buf += this.stream.char(); this.stream.next(); // 通常の文字列に戻る state = 'string'; @@ -550,18 +550,18 @@ export class Scanner implements ITokenStream { } case 'expr': { // 埋め込み式の終端記号が無いままEOFに達した - if (this.stream.eof) { + if (this.stream.eof()) { throw new AiScriptUnexpectedEOFError(pos); } // skip spasing - if (spaceChars.includes(this.stream.char)) { + if (spaceChars.includes(this.stream.char())) { this.stream.next(); continue; } - if (this.stream.char === '{') { + if (this.stream.char() === '{') { exprBracketDepth++; } - if ((this.stream.char as string) === '}') { + if ((this.stream.char()) === '}') { // 埋め込み式の終了 if (exprBracketDepth === 0) { elements.push(TOKEN(TokenKind.TemplateExprElement, elementPos, { hasLeftSpacing, children: tokenBuf })); @@ -587,20 +587,20 @@ export class Scanner implements ITokenStream { } private skipEmptyLines(): void { - while (!this.stream.eof) { + while (!this.stream.eof()) { // skip spacing - if (spaceChars.includes(this.stream.char) || lineBreakChars.includes(this.stream.char)) { + if (spaceChars.includes(this.stream.char()) || lineBreakChars.includes(this.stream.char())) { this.stream.next(); continue; } - if (this.stream.char === '/') { + if (this.stream.char() === '/') { this.stream.next(); - if (!this.stream.eof && (this.stream.char as string) === '*') { + if (!this.stream.eof() && (this.stream.char()) === '*') { this.stream.next(); this.skipCommentRange(); continue; - } else if (!this.stream.eof && (this.stream.char as string) === '/') { + } else if (!this.stream.eof() && (this.stream.char()) === '/') { this.stream.next(); this.skipCommentLine(); continue; @@ -615,10 +615,10 @@ export class Scanner implements ITokenStream { private skipCommentLine(): void { while (true) { - if (this.stream.eof) { + if (this.stream.eof()) { break; } - if (this.stream.char === '\n') { + if (this.stream.char() === '\n') { break; } this.stream.next(); @@ -627,12 +627,12 @@ export class Scanner implements ITokenStream { private skipCommentRange(): void { while (true) { - if (this.stream.eof) { + if (this.stream.eof()) { break; } - if (this.stream.char === '*') { + if (this.stream.char() === '*') { this.stream.next(); - if ((this.stream.char as string) === '/') { + if ((this.stream.char()) === '/') { this.stream.next(); break; } diff --git a/src/parser/streams/char-stream.ts b/src/parser/streams/char-stream.ts index 58b36793..0bc7f8b3 100644 --- a/src/parser/streams/char-stream.ts +++ b/src/parser/streams/char-stream.ts @@ -28,15 +28,15 @@ export class CharStream { /** * ストリームの終わりに達しているかどうかを取得します。 */ - public get eof(): boolean { + public eof(): boolean { return this.endOfPage && this.isLastPage; } /** * カーソル位置にある文字を取得します。 */ - public get char(): string { - if (this.eof) { + public char(): string { + if (this.eof()) { throw new Error('end of stream'); } return this._char!; @@ -56,7 +56,7 @@ export class CharStream { * カーソル位置を次の文字へ進めます。 */ public next(): void { - if (!this.eof && this._char === '\n') { + if (!this.eof() && this._char === '\n') { this.line++; this.column = 0; } else { @@ -90,7 +90,7 @@ export class CharStream { private moveNext(): void { this.loadChar(); while (true) { - if (!this.eof && this._char === '\r') { + if (!this.eof() && this._char === '\r') { this.incAddr(); this.loadChar(); continue; @@ -111,7 +111,7 @@ export class CharStream { private movePrev(): void { this.loadChar(); while (true) { - if (!this.eof && this._char === '\r') { + if (!this.eof() && this._char === '\r') { this.decAddr(); this.loadChar(); continue; @@ -130,7 +130,7 @@ export class CharStream { } private loadChar(): void { - if (this.eof) { + if (this.eof()) { this._char = undefined; } else { this._char = this.pages.get(this.pageIndex)![this.address]!; diff --git a/test/parser.ts b/test/parser.ts index c2588b01..9a1b0281 100644 --- a/test/parser.ts +++ b/test/parser.ts @@ -8,14 +8,14 @@ describe('CharStream', () => { test.concurrent('char', async () => { const source = 'abc'; const stream = new CharStream(source); - assert.strictEqual('a', stream.char); + assert.strictEqual('a', stream.char()); }); test.concurrent('next', async () => { const source = 'abc'; const stream = new CharStream(source); stream.next(); - assert.strictEqual('b', stream.char); + assert.strictEqual('b', stream.char()); }); describe('prev', () => { @@ -23,37 +23,37 @@ describe('CharStream', () => { const source = 'abc'; const stream = new CharStream(source); stream.next(); - assert.strictEqual('b', stream.char); + assert.strictEqual('b', stream.char()); stream.prev(); - assert.strictEqual('a', stream.char); + assert.strictEqual('a', stream.char()); }); test.concurrent('境界外には移動しない', async () => { const source = 'abc'; const stream = new CharStream(source); stream.prev(); - assert.strictEqual('a', stream.char); + assert.strictEqual('a', stream.char()); }); }); test.concurrent('eof', async () => { const source = 'abc'; const stream = new CharStream(source); - assert.strictEqual(false, stream.eof); + assert.strictEqual(false, stream.eof()); stream.next(); - assert.strictEqual(false, stream.eof); + assert.strictEqual(false, stream.eof()); stream.next(); - assert.strictEqual(false, stream.eof); + assert.strictEqual(false, stream.eof()); stream.next(); - assert.strictEqual(true, stream.eof); + assert.strictEqual(true, stream.eof()); }); test.concurrent('EOFでcharを参照するとエラー', async () => { const source = ''; const stream = new CharStream(source); - assert.strictEqual(true, stream.eof); + assert.strictEqual(true, stream.eof()); try { - stream.char; + stream.char(); } catch (e) { return; } @@ -63,13 +63,13 @@ describe('CharStream', () => { test.concurrent('CRは読み飛ばされる', async () => { const source = 'a\r\nb'; const stream = new CharStream(source); - assert.strictEqual('a', stream.char); + assert.strictEqual('a', stream.char()); stream.next(); - assert.strictEqual('\n', stream.char); + assert.strictEqual('\n', stream.char()); stream.next(); - assert.strictEqual('b', stream.char); + assert.strictEqual('b', stream.char()); stream.next(); - assert.strictEqual(true, stream.eof); + assert.strictEqual(true, stream.eof()); }); }); From e07546d8487cebb24c329c1aeb89887f347d6661 Mon Sep 17 00:00:00 2001 From: takejohn Date: Thu, 26 Dec 2024 17:09:12 +0900 Subject: [PATCH 05/17] =?UTF-8?q?=E5=A4=89=E6=95=B0=E3=81=AB=E5=9E=8B?= =?UTF-8?q?=E6=B3=A8=E9=87=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interpreter/primitive-props.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interpreter/primitive-props.ts b/src/interpreter/primitive-props.ts index df50950a..75cf2e1f 100644 --- a/src/interpreter/primitive-props.ts +++ b/src/interpreter/primitive-props.ts @@ -221,7 +221,7 @@ const PRIMITIVE_PROPS: { filter: (target: VArr): VFn => FN_NATIVE(async ([fn], opts) => { assertFunction(fn); - const vals = [] as Value[]; + const vals: Value[] = []; for (let i = 0; i < target.value.length; i++) { const item = target.value[i]!; const res = await opts.call(fn, [item, NUM(i)]); From ce06cb6c233afad4a38515e0f92d79af1f5b70ee Mon Sep 17 00:00:00 2001 From: takejohn Date: Thu, 26 Dec 2024 17:11:56 +0900 Subject: [PATCH 06/17] =?UTF-8?q?as=E3=82=92satisfies=E3=81=AB=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interpreter/scope.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interpreter/scope.ts b/src/interpreter/scope.ts index 34142551..33b96bfc 100644 --- a/src/interpreter/scope.ts +++ b/src/interpreter/scope.ts @@ -103,7 +103,7 @@ export class Scope { public getAll(): Map { const vars = this.layerdStates.reduce((arr, layer) => { return [...arr, ...layer]; - }, [] as [string, Variable][]); + }, [] satisfies [string, Variable][]); return new Map(vars); } From c67aec278b91115b1884bb9d797653e0e5282224 Mon Sep 17 00:00:00 2001 From: takejohn Date: Thu, 26 Dec 2024 23:43:10 +0900 Subject: [PATCH 07/17] =?UTF-8?q?=E3=82=B9=E3=83=97=E3=83=AC=E3=83=83?= =?UTF-8?q?=E3=83=89=E6=A7=8B=E6=96=87=E3=82=92=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/utils.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/parser/utils.ts b/src/parser/utils.ts index c3e97d77..80e70efc 100644 --- a/src/parser/utils.ts +++ b/src/parser/utils.ts @@ -9,15 +9,7 @@ export function NODE( start: Ast.Pos, end: Ast.Pos, ): Extract { - const node: Record = { type }; - for (const key of Object.keys(params)) { - type Key = keyof typeof params; - if (params[key as Key] !== undefined) { - node[key] = params[key as Key]; - } - } - node.loc = { start, end }; - return node as Extract; + return { type, ...params, loc: { start, end } } as Extract; } export function CALL_NODE( From a378d7d61b62463f47fb1ea3e017f46e88c66517 Mon Sep 17 00:00:00 2001 From: takejohn Date: Fri, 27 Dec 2024 00:04:18 +0900 Subject: [PATCH 08/17] =?UTF-8?q?visitNode=E9=96=A2=E6=95=B0=E3=81=AB?= =?UTF-8?q?=E3=82=B8=E3=82=A7=E3=83=8D=E3=83=AA=E3=82=AF=E3=82=B9=E3=82=92?= =?UTF-8?q?=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugins/validate-jump-statements.ts | 2 +- src/parser/plugins/validate-keyword.ts | 2 +- src/parser/plugins/validate-type.ts | 2 +- src/parser/visit.ts | 122 +++++++----------- 4 files changed, 49 insertions(+), 79 deletions(-) diff --git a/src/parser/plugins/validate-jump-statements.ts b/src/parser/plugins/validate-jump-statements.ts index d1a346e2..219dd8b0 100644 --- a/src/parser/plugins/validate-jump-statements.ts +++ b/src/parser/plugins/validate-jump-statements.ts @@ -17,7 +17,7 @@ function isInLoopScope(ancestors: Ast.Node[]): boolean { return false; } -function validateNode(node: Ast.Node, ancestors: Ast.Node[]): Ast.Node { +function validateNode(node: T, ancestors: Ast.Node[]): T { switch (node.type) { case 'return': { if (!ancestors.some(({ type }) => type === 'fn')) { diff --git a/src/parser/plugins/validate-keyword.ts b/src/parser/plugins/validate-keyword.ts index 9ccfad78..5467d862 100644 --- a/src/parser/plugins/validate-keyword.ts +++ b/src/parser/plugins/validate-keyword.ts @@ -87,7 +87,7 @@ function validateTypeParams(node: Ast.Fn | Ast.FnTypeSource): void { } } -function validateNode(node: Ast.Node): Ast.Node { +function validateNode(node: T): T { switch (node.type) { case 'def': { validateDest(node.dest); diff --git a/src/parser/plugins/validate-type.ts b/src/parser/plugins/validate-type.ts index 1d043919..ba613c31 100644 --- a/src/parser/plugins/validate-type.ts +++ b/src/parser/plugins/validate-type.ts @@ -23,7 +23,7 @@ function collectTypeParams(node: Ast.Node, ancestors: Ast.Node[]): Ast.TypeParam return items; } -function validateNode(node: Ast.Node, ancestors: Ast.Node[]): Ast.Node { +function validateNode(node: T, ancestors: Ast.Node[]): T { switch (node.type) { case 'def': { if (node.varType != null) { diff --git a/src/parser/visit.ts b/src/parser/visit.ts index a5686997..265f3b2f 100644 --- a/src/parser/visit.ts +++ b/src/parser/visit.ts @@ -1,10 +1,10 @@ import type * as Ast from '../node.js'; -export function visitNode(node: Ast.Node, fn: (node: Ast.Node, ancestors: Ast.Node[]) => Ast.Node): Ast.Node { +export function visitNode(node: T, fn: (node: T, ancestors: Ast.Node[]) => T): T { return visitNodeInner(node, fn, []); } -function visitNodeInner(node: Ast.Node, fn: (node: Ast.Node, ancestors: Ast.Node[]) => Ast.Node, ancestors: Ast.Node[]): Ast.Node { +function visitNodeInner(node: T, fn: (node: T, ancestors: Ast.Node[]) => T, ancestors: Ast.Node[]): T { const result = fn(node, ancestors); ancestors.push(node); @@ -12,149 +12,149 @@ function visitNodeInner(node: Ast.Node, fn: (node: Ast.Node, ancestors: Ast.Node switch (result.type) { case 'def': { if (result.varType != null) { - result.varType = visitNodeInner(result.varType, fn, ancestors) as Ast.Definition['varType']; + result.varType = visitNodeInner(result.varType, fn, ancestors); } - result.attr = result.attr.map((attr) => visitNodeInner(attr, fn, ancestors) as Ast.Attribute); - result.expr = visitNodeInner(result.expr, fn, ancestors) as Ast.Definition['expr']; + result.attr = result.attr.map((attr) => visitNodeInner(attr, fn, ancestors)); + result.expr = visitNodeInner(result.expr, fn, ancestors); break; } case 'return': { - result.expr = visitNodeInner(result.expr, fn, ancestors) as Ast.Return['expr']; + result.expr = visitNodeInner(result.expr, fn, ancestors); break; } case 'each': { - result.items = visitNodeInner(result.items, fn, ancestors) as Ast.Each['items']; - result.for = visitNodeInner(result.for, fn, ancestors) as Ast.Each['for']; + result.items = visitNodeInner(result.items, fn, ancestors); + result.for = visitNodeInner(result.for, fn, ancestors); break; } case 'for': { if (result.from != null) { - result.from = visitNodeInner(result.from, fn, ancestors) as Ast.For['from']; + result.from = visitNodeInner(result.from, fn, ancestors); } if (result.to != null) { - result.to = visitNodeInner(result.to, fn, ancestors) as Ast.For['to']; + result.to = visitNodeInner(result.to, fn, ancestors); } if (result.times != null) { - result.times = visitNodeInner(result.times, fn, ancestors) as Ast.For['times']; + result.times = visitNodeInner(result.times, fn, ancestors); } - result.for = visitNodeInner(result.for, fn, ancestors) as Ast.For['for']; + result.for = visitNodeInner(result.for, fn, ancestors); break; } case 'loop': { for (let i = 0; i < result.statements.length; i++) { - result.statements[i] = visitNodeInner(result.statements[i]!, fn, ancestors) as Ast.Loop['statements'][number]; + result.statements[i] = visitNodeInner(result.statements[i]!, fn, ancestors); } break; } case 'addAssign': case 'subAssign': case 'assign': { - result.expr = visitNodeInner(result.expr, fn, ancestors) as Ast.Assign['expr']; - result.dest = visitNodeInner(result.dest, fn, ancestors) as Ast.Assign['dest']; + result.expr = visitNodeInner(result.expr, fn, ancestors); + result.dest = visitNodeInner(result.dest, fn, ancestors); break; } case 'plus': { - result.expr = visitNodeInner(result.expr, fn, ancestors) as Ast.Plus['expr']; + result.expr = visitNodeInner(result.expr, fn, ancestors); break; } case 'minus': { - result.expr = visitNodeInner(result.expr, fn, ancestors) as Ast.Minus['expr']; + result.expr = visitNodeInner(result.expr, fn, ancestors); break; } case 'not': { - result.expr = visitNodeInner(result.expr, fn, ancestors) as Ast.Not['expr']; + result.expr = visitNodeInner(result.expr, fn, ancestors); break; } case 'if': { - result.cond = visitNodeInner(result.cond, fn, ancestors) as Ast.If['cond']; - result.then = visitNodeInner(result.then, fn, ancestors) as Ast.If['then']; + result.cond = visitNodeInner(result.cond, fn, ancestors); + result.then = visitNodeInner(result.then, fn, ancestors); for (const prop of result.elseif) { - prop.cond = visitNodeInner(prop.cond, fn, ancestors) as Ast.If['elseif'][number]['cond']; - prop.then = visitNodeInner(prop.then, fn, ancestors) as Ast.If['elseif'][number]['then']; + prop.cond = visitNodeInner(prop.cond, fn, ancestors); + prop.then = visitNodeInner(prop.then, fn, ancestors); } if (result.else != null) { - result.else = visitNodeInner(result.else, fn, ancestors) as Ast.If['else']; + result.else = visitNodeInner(result.else, fn, ancestors); } break; } case 'fn': { for (const param of result.params) { if (param.default) { - param.default = visitNodeInner(param.default!, fn, ancestors) as Ast.Fn['params'][number]['default']; + param.default = visitNodeInner(param.default!, fn, ancestors); } if (param.argType != null) { - param.argType = visitNodeInner(param.argType, fn, ancestors) as Ast.Fn['params'][number]['argType']; + param.argType = visitNodeInner(param.argType, fn, ancestors); } } if (result.retType != null) { - result.retType = visitNodeInner(result.retType, fn, ancestors) as Ast.Fn['retType']; + result.retType = visitNodeInner(result.retType, fn, ancestors); } for (let i = 0; i < result.children.length; i++) { - result.children[i] = visitNodeInner(result.children[i]!, fn, ancestors) as Ast.Fn['children'][number]; + result.children[i] = visitNodeInner(result.children[i]!, fn, ancestors); } break; } case 'match': { - result.about = visitNodeInner(result.about, fn, ancestors) as Ast.Match['about']; + result.about = visitNodeInner(result.about, fn, ancestors); for (const prop of result.qs) { - prop.q = visitNodeInner(prop.q, fn, ancestors) as Ast.Match['qs'][number]['q']; - prop.a = visitNodeInner(prop.a, fn, ancestors) as Ast.Match['qs'][number]['a']; + prop.q = visitNodeInner(prop.q, fn, ancestors); + prop.a = visitNodeInner(prop.a, fn, ancestors); } if (result.default != null) { - result.default = visitNodeInner(result.default, fn, ancestors) as Ast.Match['default']; + result.default = visitNodeInner(result.default, fn, ancestors); } break; } case 'block': { for (let i = 0; i < result.statements.length; i++) { - result.statements[i] = visitNodeInner(result.statements[i]!, fn, ancestors) as Ast.Block['statements'][number]; + result.statements[i] = visitNodeInner(result.statements[i]!, fn, ancestors); } break; } case 'exists': { - result.identifier = visitNodeInner(result.identifier, fn, ancestors) as Ast.Exists['identifier']; + result.identifier = visitNodeInner(result.identifier, fn, ancestors); break; } case 'tmpl': { for (let i = 0; i < result.tmpl.length; i++) { const item = result.tmpl[i]!; if (typeof item !== 'string') { - result.tmpl[i] = visitNodeInner(item, fn, ancestors) as Ast.Tmpl['tmpl'][number]; + result.tmpl[i] = visitNodeInner(item, fn, ancestors); } } break; } case 'obj': { for (const item of result.value) { - result.value.set(item[0], visitNodeInner(item[1], fn, ancestors) as Ast.Expression); + result.value.set(item[0], visitNodeInner(item[1], fn, ancestors)); } break; } case 'arr': { for (let i = 0; i < result.value.length; i++) { - result.value[i] = visitNodeInner(result.value[i]!, fn, ancestors) as Ast.Arr['value'][number]; + result.value[i] = visitNodeInner(result.value[i]!, fn, ancestors); } break; } case 'call': { - result.target = visitNodeInner(result.target, fn, ancestors) as Ast.Call['target']; + result.target = visitNodeInner(result.target, fn, ancestors); for (let i = 0; i < result.args.length; i++) { - result.args[i] = visitNodeInner(result.args[i]!, fn, ancestors) as Ast.Call['args'][number]; + result.args[i] = visitNodeInner(result.args[i]!, fn, ancestors); } break; } case 'index': { - result.target = visitNodeInner(result.target, fn, ancestors) as Ast.Index['target']; - result.index = visitNodeInner(result.index, fn, ancestors) as Ast.Index['index']; + result.target = visitNodeInner(result.target, fn, ancestors); + result.index = visitNodeInner(result.index, fn, ancestors); break; } case 'prop': { - result.target = visitNodeInner(result.target, fn, ancestors) as Ast.Prop['target']; + result.target = visitNodeInner(result.target, fn, ancestors); break; } case 'ns': { for (let i = 0; i < result.members.length; i++) { - result.members[i] = visitNodeInner(result.members[i]!, fn, ancestors) as (typeof result.members)[number]; + result.members[i] = visitNodeInner(result.members[i]!, fn, ancestors); } break; } @@ -173,50 +173,20 @@ function visitNodeInner(node: Ast.Node, fn: (node: Ast.Node, ancestors: Ast.Node case 'neq': case 'and': case 'or': { - result.left = visitNodeInner(result.left, fn, ancestors) as ( - Ast.Pow | - Ast.Mul | - Ast.Div | - Ast.Rem | - Ast.Add | - Ast.Sub | - Ast.Lt | - Ast.Lteq | - Ast.Gt | - Ast.Gteq | - Ast.Eq | - Ast.Neq | - Ast.And | - Ast.Or - )['left']; - result.right = visitNodeInner(result.right, fn, ancestors) as ( - Ast.Pow | - Ast.Mul | - Ast.Div | - Ast.Rem | - Ast.Add | - Ast.Sub | - Ast.Lt | - Ast.Lteq | - Ast.Gt | - Ast.Gteq | - Ast.Eq | - Ast.Neq | - Ast.And | - Ast.Or - )['right']; + result.left = visitNodeInner(result.left, fn, ancestors); + result.right = visitNodeInner(result.right, fn, ancestors); break; } case 'fnTypeSource': { for (let i = 0; i < result.params.length; i++) { - result.params[i] = visitNodeInner(result.params[i]!, fn, ancestors) as Ast.FnTypeSource['params'][number]; + result.params[i] = visitNodeInner(result.params[i]!, fn, ancestors); } break; } case 'unionTypeSource': { for (let i = 0; i < result.inners.length; i++) { - result.inners[i] = visitNodeInner(result.inners[i]!, fn, ancestors) as Ast.UnionTypeSource['inners'][number]; + result.inners[i] = visitNodeInner(result.inners[i]!, fn, ancestors); } break; } From f41ed13a79ca688a4fb75b153e8d79527281485c Mon Sep 17 00:00:00 2001 From: takejohn Date: Fri, 27 Dec 2024 00:30:07 +0900 Subject: [PATCH 09/17] =?UTF-8?q?Ast.For=E3=81=AE=E5=9E=8B=E3=82=92?= =?UTF-8?q?=E5=8E=B3=E6=A0=BC=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- etc/aiscript.api.md | 12 +++++++----- src/interpreter/index.ts | 8 ++++---- src/node.ts | 12 +++++++----- src/parser/plugins/validate-keyword.ts | 2 +- src/parser/syntaxes/statements.ts | 14 +++++++++----- src/parser/utils.ts | 4 ++++ src/parser/visit.ts | 7 ++----- 7 files changed, 34 insertions(+), 25 deletions(-) diff --git a/etc/aiscript.api.md b/etc/aiscript.api.md index ed176ac7..9a9f2b9f 100644 --- a/etc/aiscript.api.md +++ b/etc/aiscript.api.md @@ -347,12 +347,14 @@ type FnTypeSource = NodeBase & { // @public (undocumented) type For = NodeBase & { type: 'for'; - var?: string; - from?: Expression; - to?: Expression; - times?: Expression; for: Statement | Expression; -}; +} & ({ + var: string; + from: Expression; + to: Expression; +} | { + times: Expression; +}); // @public (undocumented) function getLangVersion(input: string): string | null; diff --git a/src/interpreter/index.ts b/src/interpreter/index.ts index 08e4c562..2c077947 100644 --- a/src/interpreter/index.ts +++ b/src/interpreter/index.ts @@ -423,7 +423,7 @@ export class Interpreter { } case 'for': { - if (node.times) { + if ('times' in node) { const times = await this._eval(node.times, scope, callStack); if (isControl(times)) { return times; @@ -438,11 +438,11 @@ export class Interpreter { } } } else { - const from = await this._eval(node.from!, scope, callStack); + const from = await this._eval(node.from, scope, callStack); if (isControl(from)) { return from; } - const to = await this._eval(node.to!, scope, callStack); + const to = await this._eval(node.to, scope, callStack); if (isControl(to)) { return to; } @@ -450,7 +450,7 @@ export class Interpreter { assertNumber(to); for (let i = from.value; i < from.value + to.value; i++) { const v = await this._eval(node.for, scope.createChildScope(new Map([ - [node.var!, { + [node.var, { isMutable: false, value: NUM(i), }], diff --git a/src/node.ts b/src/node.ts index 2c73d695..019c7c15 100644 --- a/src/node.ts +++ b/src/node.ts @@ -80,12 +80,14 @@ export type Each = NodeBase & { export type For = NodeBase & { type: 'for'; // for文 - var?: string; // イテレータ変数名 - from?: Expression; // 開始値 - to?: Expression; // 終値 - times?: Expression; // 回数 for: Statement | Expression; // 本体処理 -}; +} & ({ + var: string; // イテレータ変数名 + from: Expression; // 開始値 + to: Expression; // 終値 +} | { + times: Expression; // 回数 +}); export type Loop = NodeBase & { type: 'loop'; // loop文 diff --git a/src/parser/plugins/validate-keyword.ts b/src/parser/plugins/validate-keyword.ts index 5467d862..c456eecd 100644 --- a/src/parser/plugins/validate-keyword.ts +++ b/src/parser/plugins/validate-keyword.ts @@ -113,7 +113,7 @@ function validateNode(node: T): T { break; } case 'for': { - if (node.var != null && reservedWord.includes(node.var)) { + if ('var' in node && reservedWord.includes(node.var)) { throwReservedWordError(node.var, node.loc); } break; diff --git a/src/parser/syntaxes/statements.ts b/src/parser/syntaxes/statements.ts index 050c15d5..744e3da6 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, unexpectedTokenError } from '../utils.js'; +import { CALL_NODE, LOC, NODE, unexpectedTokenError } from '../utils.js'; import { TokenKind } from '../token.js'; import { parseBlock, parseDest, parseParams } from './common.js'; import { parseExpr } from './expressions.js'; @@ -306,12 +306,14 @@ function parseFor(s: ITokenStream): Ast.For { const body = parseBlockOrStatement(s); - return NODE('for', { + return { + type: 'for', var: name, from: _from, to, for: body, - }, startPos, s.getPos()); + loc: LOC(startPos, s.getPos()), + }; } else { // times syntax @@ -324,10 +326,12 @@ function parseFor(s: ITokenStream): Ast.For { const body = parseBlockOrStatement(s); - return NODE('for', { + return { + type: 'for', times, for: body, - }, startPos, s.getPos()); + loc: LOC(startPos, s.getPos()), + }; } } diff --git a/src/parser/utils.ts b/src/parser/utils.ts index 80e70efc..753e4fbf 100644 --- a/src/parser/utils.ts +++ b/src/parser/utils.ts @@ -12,6 +12,10 @@ export function NODE( return { type, ...params, loc: { start, end } } as Extract; } +export function LOC(start: Ast.Pos, end: Ast.Pos): Ast.Loc { + return { start, end }; +} + export function CALL_NODE( name: string, args: Ast.Expression[], diff --git a/src/parser/visit.ts b/src/parser/visit.ts index 265f3b2f..8b0a1b86 100644 --- a/src/parser/visit.ts +++ b/src/parser/visit.ts @@ -28,13 +28,10 @@ function visitNodeInner(node: T, fn: (no break; } case 'for': { - if (result.from != null) { + if ('from' in result) { result.from = visitNodeInner(result.from, fn, ancestors); - } - if (result.to != null) { result.to = visitNodeInner(result.to, fn, ancestors); - } - if (result.times != null) { + } else { result.times = visitNodeInner(result.times, fn, ancestors); } result.for = visitNodeInner(result.for, fn, ancestors); From c19c273ea78ac517f112c34ebe5b4b1beccc58ba Mon Sep 17 00:00:00 2001 From: takejohn Date: Fri, 27 Dec 2024 01:58:15 +0900 Subject: [PATCH 10/17] =?UTF-8?q?=E3=81=A7=E3=81=8D=E3=82=8B=E3=81=A0?= =?UTF-8?q?=E3=81=91non-null=E3=82=A2=E3=82=B5=E3=83=BC=E3=82=B7=E3=83=A7?= =?UTF-8?q?=E3=83=B3=E3=82=92=E4=BD=BF=E3=82=8F=E3=81=AA=E3=81=84=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB(interpreter=E9=96=A2=E9=80=A3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interpreter/index.ts | 30 +++++++++++++++++------------- src/interpreter/primitive-props.ts | 12 ++++-------- src/interpreter/scope.ts | 21 ++++++++++++--------- 3 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/interpreter/index.ts b/src/interpreter/index.ts index 2c077947..0f8a42a9 100644 --- a/src/interpreter/index.ts +++ b/src/interpreter/index.ts @@ -61,7 +61,7 @@ export class Interpreter { const q = args[0]; assertString(q); if (this.opts.in == null) return NULL; - const a = await this.opts.in!(q.value); + const a = await this.opts.in(q.value); return STR(a); }), }; @@ -280,15 +280,19 @@ export class Interpreter { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition return result ?? NULL; } else { - const fnScope = fn.scope!.createChildScope(); + const fnScope = fn.scope.createChildScope(); for (const [i, param] of fn.params.entries()) { - const arg = args[i]; - if (!param.default) expectAny(arg); - this.define(fnScope, param.dest, arg ?? param.default!, true); + let arg = args[i]; + if (!param.default) { + expectAny(arg); + } else if (!arg) { + arg = param.default; + } + this.define(fnScope, param.dest, arg, true); } const info: CallInfo = { name: fn.name ?? '', pos }; - return unWrapRet(await this._run(fn.statements!, fnScope, [...callStack, info])); + return unWrapRet(await this._run(fn.statements, fnScope, [...callStack, info])); } } @@ -604,8 +608,9 @@ export class Interpreter { return target; } if (isObject(target)) { - if (target.value.has(node.name)) { - return target.value.get(node.name)!; + const value = target.value.get(node.name); + if (value != null) { + return value; } else { return NULL; } @@ -632,8 +637,9 @@ export class Interpreter { return item; } else if (isObject(target)) { assertString(i); - if (target.value.has(i.value)) { - return target.value.get(i.value)!; + const value = target.value.get(i.value); + if (value != null) { + return value; } else { return NULL; } @@ -844,9 +850,7 @@ export class Interpreter { let v: Value | Control = NULL; - for (let i = 0; i < program.length; i++) { - const node = program[i]!; - + for (const node of program) { v = await this._eval(node, scope, callStack); if (v.type === 'return') { this.log('block:return', { scope: scope.name, val: v.value }); diff --git a/src/interpreter/primitive-props.ts b/src/interpreter/primitive-props.ts index 75cf2e1f..6d33d1fa 100644 --- a/src/interpreter/primitive-props.ts +++ b/src/interpreter/primitive-props.ts @@ -222,8 +222,7 @@ const PRIMITIVE_PROPS: { filter: (target: VArr): VFn => FN_NATIVE(async ([fn], opts) => { assertFunction(fn); const vals: Value[] = []; - for (let i = 0; i < target.value.length; i++) { - const item = target.value[i]!; + for (const [i, item] of target.value.entries()) { const res = await opts.call(fn, [item, NUM(i)]); assertBoolean(res); if (res.value) vals.push(item); @@ -245,8 +244,7 @@ const PRIMITIVE_PROPS: { find: (target: VArr): VFn => FN_NATIVE(async ([fn], opts) => { assertFunction(fn); - for (let i = 0; i < target.value.length; i++) { - const item = target.value[i]!; + for (const [i, item] of target.value.entries()) { const res = await opts.call(fn, [item, NUM(i)]); assertBoolean(res); if (res.value) return item; @@ -384,8 +382,7 @@ const PRIMITIVE_PROPS: { every: (target: VArr): VFn => FN_NATIVE(async ([fn], opts) => { assertFunction(fn); - for (let i = 0; i < target.value.length; i++) { - const item = target.value[i]!; + for (const [i, item] of target.value.entries()) { const res = await opts.call(fn, [item, NUM(i)]); assertBoolean(res); if (!res.value) return FALSE; @@ -395,8 +392,7 @@ const PRIMITIVE_PROPS: { some: (target: VArr): VFn => FN_NATIVE(async ([fn], opts) => { assertFunction(fn); - for (let i = 0; i < target.value.length; i++) { - const item = target.value[i]!; + for (const [i, item] of target.value.entries()) { const res = await opts.call(fn, [item, NUM(i)]); assertBoolean(res); if (res.value) return TRUE; diff --git a/src/interpreter/scope.ts b/src/interpreter/scope.ts index 33b96bfc..a9c37dc8 100644 --- a/src/interpreter/scope.ts +++ b/src/interpreter/scope.ts @@ -4,9 +4,11 @@ import type { Value } from './value.js'; import type { Variable } from './variable.js'; import type { LogObject } from './index.js'; +export type LayerdStates = [Map, ...Map[]] + export class Scope { private parent?: Scope; - private layerdStates: Map[]; + private layerdStates: LayerdStates; public name: string; public opts: { log?(type: string, params: LogObject): void; @@ -14,7 +16,7 @@ export class Scope { } = {}; public nsName?: string; - constructor(layerdStates: Scope['layerdStates'] = [], parent?: Scope, name?: Scope['name'], nsName?: string) { + constructor(layerdStates: Scope['layerdStates'] = [new Map()], parent?: Scope, name?: Scope['name'], nsName?: string) { this.layerdStates = layerdStates; this.parent = parent; this.name = name || (layerdStates.length === 1 ? '' : ''); @@ -41,13 +43,13 @@ export class Scope { @autobind public createChildScope(states: Map = new Map(), name?: Scope['name']): Scope { - const layer = [states, ...this.layerdStates]; + const layer: LayerdStates = [states, ...this.layerdStates]; return new Scope(layer, this, name); } @autobind public createChildNamespaceScope(nsName: string, states: Map = new Map(), name?: Scope['name']): Scope { - const layer = [states, ...this.layerdStates]; + const layer: LayerdStates = [states, ...this.layerdStates]; return new Scope(layer, this, name, nsName); } @@ -58,8 +60,9 @@ export class Scope { @autobind public get(name: string): Value { for (const layer of this.layerdStates) { - if (layer.has(name)) { - const state = layer.get(name)!.value; + const value = layer.get(name); + if (value) { + const state = value.value; this.log('read', { var: name, val: state }); return state; } @@ -115,7 +118,7 @@ export class Scope { @autobind public add(name: string, variable: Variable): void { this.log('add', { var: name, val: variable }); - const states = this.layerdStates[0]!; + const states = this.layerdStates[0]; if (states.has(name)) { throw new AiScriptRuntimeError( `Variable '${name}' already exists in scope '${this.name}'`, @@ -135,8 +138,8 @@ export class Scope { public assign(name: string, val: Value): void { let i = 1; for (const layer of this.layerdStates) { - if (layer.has(name)) { - const variable = layer.get(name)!; + const variable = layer.get(name); + if (variable != null) { if (!variable.isMutable) { throw new AiScriptRuntimeError(`Cannot assign to an immutable variable ${name}.`); } From 9ba41ae44e991589b611806cef4bb4dd9c578d3f Mon Sep 17 00:00:00 2001 From: takejohn Date: Mon, 30 Dec 2024 01:34:22 +0900 Subject: [PATCH 11/17] =?UTF-8?q?tokens=E3=81=AE=E5=9E=8B=E3=82=92?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/scanner.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/parser/scanner.ts b/src/parser/scanner.ts index b7efa604..e69e99d7 100644 --- a/src/parser/scanner.ts +++ b/src/parser/scanner.ts @@ -16,7 +16,7 @@ const wordChar = /^[A-Za-z0-9_]$/; */ export class Scanner implements ITokenStream { private stream: CharStream; - private _tokens: Token[] = []; + private _tokens: [Token, ...Token[]]; constructor(source: string) constructor(stream: CharStream) @@ -26,14 +26,14 @@ export class Scanner implements ITokenStream { } else { this.stream = x; } - this._tokens.push(this.readToken()); + this._tokens = [this.readToken()]; } /** * カーソル位置にあるトークンを取得します。 */ public getToken(): Token { - return this._tokens[0]!; + return this._tokens[0]; } /** @@ -69,7 +69,7 @@ export class Scanner implements ITokenStream { */ public next(): void { // 現在のトークンがEOFだったら次のトークンに進まない - if (this._tokens[0]!.kind === TokenKind.EOF) { + if (this._tokens[0].kind === TokenKind.EOF) { return; } From 04949d70772d5aa2f6b7ddda26c692023f67e8de Mon Sep 17 00:00:00 2001 From: takejohn Date: Tue, 31 Dec 2024 15:52:45 +0900 Subject: [PATCH 12/17] =?UTF-8?q?Token=E3=81=AE=E5=9E=8B=E3=82=92=E5=8E=B3?= =?UTF-8?q?=E5=AF=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/scanner.ts | 35 ++++----- src/parser/streams/token-stream.ts | 24 ++----- src/parser/syntaxes/common.ts | 7 +- src/parser/syntaxes/expressions.ts | 30 ++++---- src/parser/syntaxes/statements.ts | 17 +++-- src/parser/syntaxes/toplevel.ts | 12 ++-- src/parser/syntaxes/types.ts | 12 ++-- src/parser/token.ts | 111 ++++++++++++++++++++++++----- test/parser.ts | 7 +- 9 files changed, 165 insertions(+), 90 deletions(-) diff --git a/src/parser/scanner.ts b/src/parser/scanner.ts index e69e99d7..c0d29a8e 100644 --- a/src/parser/scanner.ts +++ b/src/parser/scanner.ts @@ -4,7 +4,7 @@ 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'; +import type { IdentifierOrLiteralToken, NormalToken, SimpleToken, TemplateExprToken, TemplateToken, TokenPosition } from './token.js'; const spaceChars = [' ', '\t']; const lineBreakChars = ['\r', '\n']; @@ -14,9 +14,9 @@ const wordChar = /^[A-Za-z0-9_]$/; /** * 入力文字列からトークンを読み取るクラス */ -export class Scanner implements ITokenStream { +export class Scanner implements ITokenStream { private stream: CharStream; - private _tokens: [Token, ...Token[]]; + private _tokens: [NormalToken, ...NormalToken[]]; constructor(source: string) constructor(stream: CharStream) @@ -32,7 +32,7 @@ export class Scanner implements ITokenStream { /** * カーソル位置にあるトークンを取得します。 */ - public getToken(): Token { + public getToken(): NormalToken { return this._tokens[0]; } @@ -46,17 +46,10 @@ export class Scanner implements ITokenStream { /** * カーソル位置にあるトークンの種類を取得します。 */ - public getTokenKind(): TokenKind { + public getTokenKind(): NormalToken['kind'] { return this.getToken().kind; } - /** - * カーソル位置にあるトークンに含まれる値を取得します。 - */ - public getTokenValue(): string { - return this.getToken().value!; - } - /** * カーソル位置にあるトークンの位置情報を取得します。 */ @@ -83,7 +76,7 @@ export class Scanner implements ITokenStream { /** * トークンの先読みを行います。カーソル位置は移動されません。 */ - public lookahead(offset: number): Token { + public lookahead(offset: number): NormalToken { while (this._tokens.length <= offset) { this._tokens.push(this.readToken()); } @@ -95,13 +88,13 @@ export class Scanner implements ITokenStream { * カーソル位置にあるトークンの種類が指定したトークンの種類と一致することを確認します。 * 一致しなかった場合には文法エラーを発生させます。 */ - public expect(kind: TokenKind): void { + public expect(kind: NormalToken['kind']): void { if (!this.is(kind)) { throw unexpectedTokenError(this.getTokenKind(), this.getPos()); } } - private readToken(): Token { + private readToken(): NormalToken { let hasLeftSpacing = false; while (true) { @@ -329,7 +322,7 @@ export class Scanner implements ITokenStream { // Do not add any more code here. This line should be unreachable. } - private tryReadWord(hasLeftSpacing: boolean): Token | undefined { + private tryReadWord(hasLeftSpacing: boolean): SimpleToken | IdentifierOrLiteralToken | undefined { // read a word let value = ''; @@ -413,7 +406,7 @@ export class Scanner implements ITokenStream { } } - private tryReadDigits(hasLeftSpacing: boolean): Token | undefined { + private tryReadDigits(hasLeftSpacing: boolean): IdentifierOrLiteralToken | undefined { let wholeNumber = ''; let fractional = ''; @@ -445,7 +438,7 @@ export class Scanner implements ITokenStream { return TOKEN(TokenKind.NumberLiteral, pos, { hasLeftSpacing, value }); } - private readStringLiteral(hasLeftSpacing: boolean): Token { + private readStringLiteral(hasLeftSpacing: boolean): IdentifierOrLiteralToken { let value = ''; const literalMark = this.stream.char(); let state: 'string' | 'escape' | 'finish' = 'string'; @@ -487,10 +480,10 @@ export class Scanner implements ITokenStream { return TOKEN(TokenKind.StringLiteral, pos, { hasLeftSpacing, value }); } - private readTemplate(hasLeftSpacing: boolean): Token { - const elements: Token[] = []; + private readTemplate(hasLeftSpacing: boolean): TemplateToken { + const elements: TemplateToken['children'] = []; let buf = ''; - let tokenBuf: Token[] = []; + let tokenBuf: TemplateExprToken['children'] = []; let state: 'string' | 'escape' | 'expr' | 'finish' = 'string'; let exprBracketDepth = 0; diff --git a/src/parser/streams/token-stream.ts b/src/parser/streams/token-stream.ts index e21b66a8..b31190eb 100644 --- a/src/parser/streams/token-stream.ts +++ b/src/parser/streams/token-stream.ts @@ -5,26 +5,21 @@ import type { Token, TokenPosition } from '../token.js'; /** * トークンの読み取りに関するインターフェース */ -export interface ITokenStream { +export interface ITokenStream { /** * カーソル位置にあるトークンを取得します。 */ - getToken(): Token; + getToken(): T; /** * カーソル位置にあるトークンの種類が指定したトークンの種類と一致するかどうかを示す値を取得します。 */ - is(kind: TokenKind): boolean; + is(kind: T['kind']): boolean; /** * カーソル位置にあるトークンの種類を取得します。 */ - getTokenKind(): TokenKind; - - /** - * カーソル位置にあるトークンに含まれる値を取得します。 - */ - getTokenValue(): string; + getTokenKind(): T['kind']; /** * カーソル位置にあるトークンの位置情報を取得します。 @@ -39,13 +34,13 @@ export interface ITokenStream { /** * トークンの先読みを行います。カーソル位置は移動されません。 */ - lookahead(offset: number): Token; + lookahead(offset: number): T; /** * カーソル位置にあるトークンの種類が指定したトークンの種類と一致することを確認します。 * 一致しなかった場合には文法エラーを発生させます。 */ - expect(kind: TokenKind): void; + expect(kind: T['kind']): void; } /** @@ -83,13 +78,6 @@ export class TokenStream implements ITokenStream { return this.getTokenKind() === kind; } - /** - * カーソル位置にあるトークンに含まれる値を取得します。 - */ - public getTokenValue(): string { - return this.getToken().value!; - } - /** * カーソル位置にあるトークンの種類を取得します。 */ diff --git a/src/parser/syntaxes/common.ts b/src/parser/syntaxes/common.ts index 946c112c..82891087 100644 --- a/src/parser/syntaxes/common.ts +++ b/src/parser/syntaxes/common.ts @@ -15,9 +15,10 @@ import type * as Ast from '../../node.js'; */ export function parseDest(s: ITokenStream): Ast.Expression { // 全部parseExprに任せるとparseReferenceが型注釈を巻き込んでパースしてしまうためIdentifierのみ個別に処理。 - if (s.is(TokenKind.Identifier)) { - const nameStartPos = s.getPos(); - const name = s.getTokenValue(); + const token = s.getToken(); + if (token.kind === TokenKind.Identifier) { + const nameStartPos = token.pos; + const name = token.value; s.next(); return NODE('identifier', { name }, nameStartPos, s.getPos()); } else { diff --git a/src/parser/syntaxes/expressions.ts b/src/parser/syntaxes/expressions.ts index 5d6e2641..d9ee3025 100644 --- a/src/parser/syntaxes/expressions.ts +++ b/src/parser/syntaxes/expressions.ts @@ -1,7 +1,7 @@ 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 { expectTokenKind, TokenKind } from '../token.js'; import { parseBlock, parseOptionalSeparator, parseParams } from './common.js'; import { parseBlockOrStatement } from './statements.js'; import { parseType, parseTypeParams } from './types.js'; @@ -103,8 +103,9 @@ function parseInfix(s: ITokenStream, left: Ast.Expression, minBp: number): Ast.E } if (op === TokenKind.Dot) { - s.expect(TokenKind.Identifier); - const name = s.getTokenValue(); + const token = s.getToken(); + expectTokenKind(token, TokenKind.Identifier); + const name = token.value; s.next(); return NODE('prop', { @@ -192,8 +193,9 @@ function parsePostfix(s: ITokenStream, expr: Ast.Expression): Ast.Expression { function parseAtom(s: ITokenStream, isStatic: boolean): Ast.Expression { const startPos = s.getPos(); + const token = s.getToken(); - switch (s.getTokenKind()) { + switch (token.kind) { case TokenKind.IfKeyword: { if (isStatic) break; return parseIf(s); @@ -219,12 +221,12 @@ function parseAtom(s: ITokenStream, isStatic: boolean): Ast.Expression { if (isStatic) break; - for (const [i, element] of s.getToken().children!.entries()) { + for (const [i, element] of token.children.entries()) { switch (element.kind) { case TokenKind.TemplateStringElement: { // トークンの終了位置を取得するために先読み - const nextToken = s.getToken().children![i + 1] ?? s.lookahead(1); - values.push(NODE('str', { value: element.value! }, element.pos, nextToken.pos)); + const nextToken = token.children[i + 1] ?? s.lookahead(1); + values.push(NODE('str', { value: element.value }, element.pos, nextToken.pos)); break; } case TokenKind.TemplateExprElement: { @@ -251,13 +253,13 @@ function parseAtom(s: ITokenStream, isStatic: boolean): Ast.Expression { return NODE('tmpl', { tmpl: values }, startPos, s.getPos()); } case TokenKind.StringLiteral: { - const value = s.getTokenValue(); + const value = token.value; s.next(); return NODE('str', { value }, startPos, s.getPos()); } case TokenKind.NumberLiteral: { // TODO: validate number value - const value = Number(s.getTokenValue()); + const value = Number(token.value); s.next(); return NODE('num', { value }, startPos, s.getPos()); } @@ -541,8 +543,9 @@ function parseReference(s: ITokenStream): Ast.Identifier { break; } } - s.expect(TokenKind.Identifier); - segs.push(s.getTokenValue()); + const token = s.getToken(); + expectTokenKind(token, TokenKind.Identifier); + segs.push(token.value); s.next(); } return NODE('identifier', { name: segs.join(':') }, startPos, s.getPos()); @@ -565,8 +568,9 @@ function parseObject(s: ITokenStream, isStatic: boolean): Ast.Obj { const map = new Map(); while (!s.is(TokenKind.CloseBrace)) { - s.expect(TokenKind.Identifier); - const k = s.getTokenValue(); + const token = s.getToken(); + expectTokenKind(token, TokenKind.Identifier); + const k = token.value; s.next(); s.expect(TokenKind.Colon); diff --git a/src/parser/syntaxes/statements.ts b/src/parser/syntaxes/statements.ts index 744e3da6..a7ef2f7f 100644 --- a/src/parser/syntaxes/statements.ts +++ b/src/parser/syntaxes/statements.ts @@ -1,6 +1,6 @@ import { AiScriptSyntaxError } from '../../error.js'; import { CALL_NODE, LOC, NODE, unexpectedTokenError } from '../utils.js'; -import { TokenKind } from '../token.js'; +import { expectTokenKind, TokenKind } from '../token.js'; import { parseBlock, parseDest, parseParams } from './common.js'; import { parseExpr } from './expressions.js'; import { parseType, parseTypeParams } from './types.js'; @@ -154,9 +154,10 @@ function parseFnDef(s: ITokenStream): Ast.Definition { s.expect(TokenKind.At); s.next(); - s.expect(TokenKind.Identifier); + const token = s.getToken(); + expectTokenKind(token, TokenKind.Identifier); const nameStartPos = s.getPos(); - const name = s.getTokenValue(); + const name = token.value; s.next(); const dest = NODE('identifier', { name }, nameStartPos, s.getPos()); @@ -279,8 +280,9 @@ function parseFor(s: ITokenStream): Ast.For { const identPos = s.getPos(); - s.expect(TokenKind.Identifier); - const name = s.getTokenValue(); + const token = s.getToken(); + expectTokenKind(token, TokenKind.Identifier); + const name = token.value; s.next(); let _from: Ast.Expression; @@ -388,8 +390,9 @@ function parseAttr(s: ITokenStream): Ast.Attribute { s.expect(TokenKind.OpenSharpBracket); s.next(); - s.expect(TokenKind.Identifier); - const name = s.getTokenValue(); + const token = s.getToken(); + expectTokenKind(token, TokenKind.Identifier); + const name = token.value; s.next(); let value: Ast.Expression; diff --git a/src/parser/syntaxes/toplevel.ts b/src/parser/syntaxes/toplevel.ts index 91a07787..e8060289 100644 --- a/src/parser/syntaxes/toplevel.ts +++ b/src/parser/syntaxes/toplevel.ts @@ -1,5 +1,5 @@ import { NODE } from '../utils.js'; -import { TokenKind } from '../token.js'; +import { expectTokenKind, TokenKind } from '../token.js'; import { AiScriptSyntaxError, AiScriptUnexpectedEOFError } from '../../error.js'; import { parseDefStatement, parseStatement } from './statements.js'; import { parseExpr } from './expressions.js'; @@ -67,8 +67,9 @@ export function parseNamespace(s: ITokenStream): Ast.Namespace { s.expect(TokenKind.Colon2); s.next(); - s.expect(TokenKind.Identifier); - const name = s.getTokenValue(); + const token = s.getToken(); + expectTokenKind(token, TokenKind.Identifier); + const name = token.value; s.next(); const members: (Ast.Namespace | Ast.Definition)[] = []; @@ -131,8 +132,9 @@ export function parseMeta(s: ITokenStream): Ast.Meta { s.next(); let name = null; - if (s.is(TokenKind.Identifier)) { - name = s.getTokenValue(); + const token = s.getToken(); + if (token.kind === TokenKind.Identifier) { + name = token.value; s.next(); } diff --git a/src/parser/syntaxes/types.ts b/src/parser/syntaxes/types.ts index a29ced1a..dc0258cb 100644 --- a/src/parser/syntaxes/types.ts +++ b/src/parser/syntaxes/types.ts @@ -1,5 +1,5 @@ import { AiScriptSyntaxError, AiScriptUnexpectedEOFError } from '../../error.js'; -import { TokenKind } from '../token.js'; +import { expectTokenKind, TokenKind } from '../token.js'; import { NODE } from '../utils.js'; import { parseOptionalSeparator } from './common.js'; @@ -51,8 +51,9 @@ export function parseTypeParams(s: ITokenStream): TypeParam[] { * ``` */ function parseTypeParam(s: ITokenStream): TypeParam { - s.expect(TokenKind.Identifier); - const name = s.getTokenValue(); + const token = s.getToken(); + expectTokenKind(token, TokenKind.Identifier); + const name = token.value; s.next(); return { name }; @@ -154,8 +155,9 @@ function parseNamedType(s: ITokenStream): Ast.TypeSource { const startPos = s.getPos(); let name: string; - if (s.is(TokenKind.Identifier)) { - name = s.getTokenValue(); + const token = s.getToken(); + if (token.kind === TokenKind.Identifier) { + name = token.value; s.next(); } else { s.expect(TokenKind.NullKeyword); diff --git a/src/parser/token.ts b/src/parser/token.ts index 94425cde..33f4ff2a 100644 --- a/src/parser/token.ts +++ b/src/parser/token.ts @@ -1,3 +1,5 @@ +import { unexpectedTokenError } from './utils.js'; + export enum TokenKind { EOF, NewLine, @@ -113,22 +115,99 @@ export enum TokenKind { export type TokenPosition = { column: number, line: number }; -export class Token { - constructor( - public kind: TokenKind, - public pos: TokenPosition, - public hasLeftSpacing = false, - /** for number literal, string literal */ - public value?: string, - /** for template syntax */ - public children?: Token[], - ) { } +type TokenBase = { + kind: TokenKind; + pos: TokenPosition; + hasLeftSpacing: boolean; +}; + +export type EOFToken = TokenBase & { + kind: TokenKind.EOF; +}; + +export type SimpleToken = TokenBase & { + kind: Exclude< + TokenKind, + TokenKind.EOF + | TokenKind.Identifier + | TokenKind.NumberLiteral + | TokenKind.StringLiteral + | TokenKind.Template + | TokenKind.TemplateStringElement + | TokenKind.TemplateExprElement + >; +} + +/** for number literal, string literal */ +export type IdentifierOrLiteralToken = TokenBase & { + kind: TokenKind.Identifier | TokenKind.NumberLiteral | TokenKind.StringLiteral; + value: string; +}; + +/** for template syntax */ +export type TemplateToken = TokenBase & { + kind: TokenKind.Template; + children: (EOFToken | TemplateExprToken | TemplateStringToken)[]; +}; + +export type TemplateStringToken = TokenBase & { + kind: TokenKind.TemplateStringElement; + value: string; +}; + +export type TemplateExprToken = TokenBase & { + kind: TokenKind.TemplateExprElement; + children: NormalToken[]; +}; + +export type NormalToken = EOFToken | SimpleToken | IdentifierOrLiteralToken | TemplateToken; + +export type Token = NormalToken | TemplateStringToken | TemplateExprToken; + +export function TOKEN( + kind: EOFToken['kind'], + pos: TokenPosition, + opts?: Omit, +): EOFToken; +export function TOKEN( + kind: SimpleToken['kind'], + pos: TokenPosition, + opts: Omit, +): SimpleToken; +export function TOKEN( + kind: IdentifierOrLiteralToken['kind'], + pos: TokenPosition, + opts: Omit, +): IdentifierOrLiteralToken; +export function TOKEN( + kind: TemplateToken['kind'], + pos: TokenPosition, + opts: Omit, +): TemplateToken; +export function TOKEN( + kind: TemplateStringToken['kind'], + pos: TokenPosition, + opts: Omit, +): TemplateStringToken; +export function TOKEN( + kind: TemplateExprToken['kind'], + pos: TokenPosition, + opts: Omit, +): TemplateExprToken; +export function TOKEN( + kind: TokenBase['kind'], + pos: TokenPosition, + opts?: Omit, +): TokenBase { + if (opts == null) { + return { kind, pos, hasLeftSpacing: false }; + } else { + return { kind, pos, ...opts }; + } } -/** - * - opts.value: for number literal, string literal - * - opts.children: for template syntax -*/ -export function TOKEN(kind: TokenKind, pos: TokenPosition, opts?: { hasLeftSpacing?: boolean, value?: Token['value'], children?: Token['children'] }): Token { - return new Token(kind, pos, opts?.hasLeftSpacing, opts?.value, opts?.children); +export function expectTokenKind(token: Token, kind: T): asserts token is Token & { kind: T } { + if (token.kind !== kind) { + throw unexpectedTokenError(token.kind, token.pos); + } } diff --git a/test/parser.ts b/test/parser.ts index 9a1b0281..fa386fa0 100644 --- a/test/parser.ts +++ b/test/parser.ts @@ -79,7 +79,10 @@ describe('Scanner', () => { return stream; } function next(stream: Scanner, kind: TokenKind, pos: TokenPosition, opts: { hasLeftSpacing?: boolean, value?: string }) { - assert.deepStrictEqual(stream.getToken(), TOKEN(kind, pos, opts)); + if (opts.hasLeftSpacing == null) { + opts.hasLeftSpacing = false; + } + assert.deepStrictEqual(stream.getToken(), TOKEN(kind as any, pos, opts as any)); stream.next(); } @@ -139,7 +142,7 @@ describe('Scanner', () => { test.concurrent('lookahead', async () => { const source = '@abc() { }'; const stream = init(source); - assert.deepStrictEqual(stream.lookahead(1), TOKEN(TokenKind.Identifier, { line: 1, column: 2 }, { value: 'abc' })); + assert.deepStrictEqual(stream.lookahead(1), TOKEN(TokenKind.Identifier, { line: 1, column: 2 }, { hasLeftSpacing: false, value: 'abc' })); next(stream, TokenKind.At, { line: 1, column: 1 }, { }); next(stream, TokenKind.Identifier, { line: 1, column: 2 }, { value: 'abc' }); next(stream, TokenKind.OpenParen, { line: 1, column: 5 }, { }); From 75f7fd2ea4a89982c3bd6682127c5f4a0a35b915 Mon Sep 17 00:00:00 2001 From: takejohn Date: Tue, 31 Dec 2024 20:45:07 +0900 Subject: [PATCH 13/17] CHANGELOG --- unreleased/ast-node-for-type.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 unreleased/ast-node-for-type.md diff --git a/unreleased/ast-node-for-type.md b/unreleased/ast-node-for-type.md new file mode 100644 index 00000000..f89a69b0 --- /dev/null +++ b/unreleased/ast-node-for-type.md @@ -0,0 +1 @@ +- **Breaking Change** For Hosts: Ast.Forの型がより厳格になりました。 From 2c0d7c5e22a205645c2553cbd022d10fd5c30431 Mon Sep 17 00:00:00 2001 From: takejohn Date: Tue, 31 Dec 2024 21:09:05 +0900 Subject: [PATCH 14/17] =?UTF-8?q?=E7=B5=84=E3=81=BF=E8=BE=BC=E3=81=BF?= =?UTF-8?q?=E3=83=97=E3=83=AD=E3=83=91=E3=83=86=E3=82=A3=E3=81=AE=E8=A7=A3?= =?UTF-8?q?=E6=B1=BA=E3=81=ABMap=E3=82=92=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interpreter/primitive-props.ts | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/interpreter/primitive-props.ts b/src/interpreter/primitive-props.ts index 6d33d1fa..6bb61b9e 100644 --- a/src/interpreter/primitive-props.ts +++ b/src/interpreter/primitive-props.ts @@ -9,11 +9,11 @@ import type { Value, VArr, VFn, VNum, VStr, VError } from './value.js'; type VWithPP = VNum|VStr|VArr|VError; const PRIMITIVE_PROPS: { - [key in VWithPP['type']]: { [key: string]: (target: Value) => Value } + [key in VWithPP['type']]: Map Value>; } & { - [key in (Exclude)['type']]?: never + [key in (Exclude)['type']]?: never; } = { - num: { + num: new Map(Object.entries({ to_str: (target: VNum): VFn => FN_NATIVE(async (_, _opts) => { return STR(target.value.toString()); }), @@ -21,9 +21,9 @@ const PRIMITIVE_PROPS: { to_hex: (target: VNum): VFn => FN_NATIVE(async (_, _opts) => { return STR(target.value.toString(16)); }), - }, + })), - str: { + str: new Map(Object.entries({ to_num: (target: VStr): VFn => FN_NATIVE(async (_, _opts) => { const parsed = parseInt(target.value, 10); if (isNaN(parsed)) return NULL; @@ -170,9 +170,9 @@ const PRIMITIVE_PROPS: { return STR(target.value.padEnd(width.value, s)); }), - }, + })), - arr: { + arr: new Map(Object.entries({ len: (target: VArr): VNum => NUM(target.value.length), push: (target: VArr): VFn => FN_NATIVE(async ([val], _opts) => { @@ -421,20 +421,21 @@ const PRIMITIVE_PROPS: { assertNumber(index); return target.value.at(index.value) ?? otherwise ?? NULL; }), - }, + })), - error: { + error: new Map(Object.entries({ name: (target: VError): VStr => STR(target.value), info: (target: VError): Value => target.info ?? NULL, - }, + })), } as const; export function getPrimProp(target: Value, name: string): Value { const props = PRIMITIVE_PROPS[target.type]; if (props != null) { - if (Object.hasOwn(props, name)) { - return props[name]!(target); + const prop = props.get(name); + if (prop != null) { + return prop(target); } else { throw new AiScriptRuntimeError(`No such prop (${name}) in ${target.type}.`); } From ded74a78ff7a625e4b2177f8296a94ace85c37db Mon Sep 17 00:00:00 2001 From: takejohn Date: Sun, 12 Jan 2025 14:56:56 +0900 Subject: [PATCH 15/17] =?UTF-8?q?=E3=82=A8=E3=83=A9=E3=83=BC=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/plugins/validate-jump-statements.ts | 12 ++++++++---- src/parser/syntaxes/common.ts | 6 ++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/parser/plugins/validate-jump-statements.ts b/src/parser/plugins/validate-jump-statements.ts index 1c9b51f1..7b4264ff 100644 --- a/src/parser/plugins/validate-jump-statements.ts +++ b/src/parser/plugins/validate-jump-statements.ts @@ -55,8 +55,10 @@ function validateNode(node: T, ancestors: Ast.Node[]): T { break; } case 'for': { - if (block.times != null && ancestors.includes(block.times)) { - throw new AiScriptSyntaxError('break corresponding to for is not allowed in the count', node.loc.start); + if ('times' in block) { + if (ancestors.includes(block.times)) { + throw new AiScriptSyntaxError('break corresponding to for is not allowed in the count', node.loc.start); + } } else if (ancestors.some((ancestor) => ancestor === block.from || ancestor === block.to)) { throw new AiScriptSyntaxError('break corresponding to for is not allowed in the range', node.loc.start); } @@ -107,8 +109,10 @@ function validateNode(node: T, ancestors: Ast.Node[]): T { break; } case 'for': { - if (block.times != null && ancestors.includes(block.times)) { - throw new AiScriptSyntaxError('continue corresponding to for is not allowed in the count', node.loc.start); + if ('times' in block) { + if (ancestors.includes(block.times)) { + throw new AiScriptSyntaxError('continue corresponding to for is not allowed in the count', node.loc.start); + } } else if (ancestors.some((ancestor) => ancestor === block.from || ancestor === block.to)) { throw new AiScriptSyntaxError('continue corresponding to for is not allowed in the range', node.loc.start); } diff --git a/src/parser/syntaxes/common.ts b/src/parser/syntaxes/common.ts index f66ed78a..c69a0c4f 100644 --- a/src/parser/syntaxes/common.ts +++ b/src/parser/syntaxes/common.ts @@ -1,4 +1,4 @@ -import { TokenKind } from '../token.js'; +import { expectTokenKind, TokenKind } from '../token.js'; import { AiScriptSyntaxError, AiScriptUnexpectedEOFError } from '../../error.js'; import { NODE } from '../utils.js'; import { parseStatement } from './statements.js'; @@ -149,7 +149,9 @@ export function parseLabel(s: ITokenStream): string { throw new AiScriptSyntaxError('cannot use spaces in a label', s.getPos()); } s.expect(TokenKind.Identifier); - const label = s.getTokenValue(); + const token = s.getToken(); + expectTokenKind(token, TokenKind.Identifier); + const label = token.value; s.next(); return label; From 42285ebd8aaafcdc22b0c3259c04625b89ea520a Mon Sep 17 00:00:00 2001 From: takejohn Date: Wed, 15 Jan 2025 14:47:03 +0900 Subject: [PATCH 16/17] =?UTF-8?q?layerdStates=E3=82=92LayeredStates?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- etc/aiscript.api.md | 2 +- src/interpreter/scope.ts | 32 ++++++++++++++++---------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/etc/aiscript.api.md b/etc/aiscript.api.md index a24367a7..6ca83a88 100644 --- a/etc/aiscript.api.md +++ b/etc/aiscript.api.md @@ -671,7 +671,7 @@ type Return = NodeBase & { // @public (undocumented) export class Scope { - constructor(layerdStates?: Scope['layerdStates'], parent?: Scope, name?: Scope['name'], nsName?: string); + constructor(layeredStates?: Scope['layeredStates'], parent?: Scope, name?: Scope['name'], nsName?: string); add(name: string, variable: Variable): void; assign(name: string, val: Value): void; // (undocumented) diff --git a/src/interpreter/scope.ts b/src/interpreter/scope.ts index a9c37dc8..c6640c49 100644 --- a/src/interpreter/scope.ts +++ b/src/interpreter/scope.ts @@ -4,11 +4,11 @@ import type { Value } from './value.js'; import type { Variable } from './variable.js'; import type { LogObject } from './index.js'; -export type LayerdStates = [Map, ...Map[]] +export type LayeredStates = [Map, ...Map[]] export class Scope { private parent?: Scope; - private layerdStates: LayerdStates; + private layeredStates: LayeredStates; public name: string; public opts: { log?(type: string, params: LogObject): void; @@ -16,10 +16,10 @@ export class Scope { } = {}; public nsName?: string; - constructor(layerdStates: Scope['layerdStates'] = [new Map()], parent?: Scope, name?: Scope['name'], nsName?: string) { - this.layerdStates = layerdStates; + constructor(layeredStates: Scope['layeredStates'] = [new Map()], parent?: Scope, name?: Scope['name'], nsName?: string) { + this.layeredStates = layeredStates; this.parent = parent; - this.name = name || (layerdStates.length === 1 ? '' : ''); + this.name = name || (layeredStates.length === 1 ? '' : ''); this.nsName = nsName; } @@ -43,13 +43,13 @@ export class Scope { @autobind public createChildScope(states: Map = new Map(), name?: Scope['name']): Scope { - const layer: LayerdStates = [states, ...this.layerdStates]; + const layer: LayeredStates = [states, ...this.layeredStates]; return new Scope(layer, this, name); } @autobind public createChildNamespaceScope(nsName: string, states: Map = new Map(), name?: Scope['name']): Scope { - const layer: LayerdStates = [states, ...this.layerdStates]; + const layer: LayeredStates = [states, ...this.layeredStates]; return new Scope(layer, this, name, nsName); } @@ -59,7 +59,7 @@ export class Scope { */ @autobind public get(name: string): Value { - for (const layer of this.layerdStates) { + for (const layer of this.layeredStates) { const value = layer.get(name); if (value) { const state = value.value; @@ -70,7 +70,7 @@ export class Scope { throw new AiScriptRuntimeError( `No such variable '${name}' in scope '${this.name}'`, - { scope: this.layerdStates }); + { scope: this.layeredStates }); } /** @@ -88,7 +88,7 @@ export class Scope { */ @autobind public exists(name: string): boolean { - for (const layer of this.layerdStates) { + for (const layer of this.layeredStates) { if (layer.has(name)) { this.log('exists', { var: name }); return true; @@ -104,7 +104,7 @@ export class Scope { */ @autobind public getAll(): Map { - const vars = this.layerdStates.reduce((arr, layer) => { + const vars = this.layeredStates.reduce((arr, layer) => { return [...arr, ...layer]; }, [] satisfies [string, Variable][]); return new Map(vars); @@ -118,11 +118,11 @@ export class Scope { @autobind public add(name: string, variable: Variable): void { this.log('add', { var: name, val: variable }); - const states = this.layerdStates[0]; + const states = this.layeredStates[0]; if (states.has(name)) { throw new AiScriptRuntimeError( `Variable '${name}' already exists in scope '${this.name}'`, - { scope: this.layerdStates }); + { scope: this.layeredStates }); } states.set(name, variable); if (this.parent == null) this.onUpdated(name, variable.value); @@ -137,7 +137,7 @@ export class Scope { @autobind public assign(name: string, val: Value): void { let i = 1; - for (const layer of this.layerdStates) { + for (const layer of this.layeredStates) { const variable = layer.get(name); if (variable != null) { if (!variable.isMutable) { @@ -147,7 +147,7 @@ export class Scope { variable.value = val; this.log('assign', { var: name, val: val }); - if (i === this.layerdStates.length) this.onUpdated(name, val); + if (i === this.layeredStates.length) this.onUpdated(name, val); return; } i++; @@ -155,6 +155,6 @@ export class Scope { throw new AiScriptRuntimeError( `No such variable '${name}' in scope '${this.name}'`, - { scope: this.layerdStates }); + { scope: this.layeredStates }); } } From ab035fbbc3dfb70dd6426e8024a76dbcae19c6d1 Mon Sep 17 00:00:00 2001 From: Take-John Date: Mon, 20 Jan 2025 21:48:53 +0900 Subject: [PATCH 17/17] Update src/parser/visit.ts Co-authored-by: salano_ym <53254905+salano-ym@users.noreply.github.com> --- src/parser/visit.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser/visit.ts b/src/parser/visit.ts index 8b0a1b86..e4cade6d 100644 --- a/src/parser/visit.ts +++ b/src/parser/visit.ts @@ -1,6 +1,6 @@ import type * as Ast from '../node.js'; -export function visitNode(node: T, fn: (node: T, ancestors: Ast.Node[]) => T): T { +export function visitNode(node: T, fn: (node: U, ancestors: Ast.Node[]) => U): T { return visitNodeInner(node, fn, []); }