From e02c847a2ad7abd107af5c126ef03ab41a22591d Mon Sep 17 00:00:00 2001 From: Take-John <105504345+takejohn@users.noreply.github.com> Date: Mon, 28 Oct 2024 10:39:56 +0900 Subject: [PATCH] =?UTF-8?q?if=E5=BC=8F=E3=82=84match=E5=BC=8F=E3=80=81for?= =?UTF-8?q?=E6=96=87=E3=81=8C=E6=96=871=E3=81=A4=E3=81=A7=E3=82=82?= =?UTF-8?q?=E3=82=B9=E3=82=B3=E3=83=BC=E3=83=97=E3=82=92=E7=94=9F=E6=88=90?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=20(#827)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interpreter/index.ts | 19 +++++---- test/syntax.ts | 45 +++++++++++++++++++++ unreleased/single-statement-clause-scope.md | 1 + 3 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 unreleased/single-statement-clause-scope.md diff --git a/src/interpreter/index.ts b/src/interpreter/index.ts index a9517de1..33520fe8 100644 --- a/src/interpreter/index.ts +++ b/src/interpreter/index.ts @@ -4,6 +4,7 @@ import { autobind } from '../utils/mini-autobind.js'; import { AiScriptError, NonAiScriptError, AiScriptNamespaceError, AiScriptIndexOutOfRangeError, AiScriptRuntimeError, AiScriptHostsideError } from '../error.js'; +import * as Ast from '../node.js'; import { Scope } from './scope.js'; import { std } from './lib/std.js'; import { assertNumber, assertString, assertFunction, assertBoolean, assertObject, assertArray, eq, isObject, isArray, expectAny, reprValue } from './util.js'; @@ -12,7 +13,6 @@ import { getPrimProp } from './primitive-props.js'; import { Variable } from './variable.js'; import type { JsValue } from './util.js'; import type { Value, VFn } from './value.js'; -import type * as Ast from '../node.js'; export type LogObject = { scope?: string; @@ -266,6 +266,11 @@ export class Interpreter { } } + @autobind + private _evalClause(node: Ast.Statement | Ast.Expression, scope: Scope): Promise { + return this._eval(node, Ast.isStatement(node) ? scope.createChildScope() : scope); + } + @autobind private _eval(node: Ast.Node, scope: Scope): Promise { return this.__eval(node, scope).catch(e => { @@ -303,21 +308,21 @@ export class Interpreter { const cond = await this._eval(node.cond, scope); assertBoolean(cond); if (cond.value) { - return this._eval(node.then, scope); + return this._evalClause(node.then, scope); } else { if (node.elseif && node.elseif.length > 0) { for (const elseif of node.elseif) { const cond = await this._eval(elseif.cond, scope); assertBoolean(cond); if (cond.value) { - return this._eval(elseif.then, scope); + return this._evalClause(elseif.then, scope); } } if (node.else) { return this._eval(node.else, scope); } } else if (node.else) { - return this._eval(node.else, scope); + return this._evalClause(node.else, scope); } } return NULL; @@ -328,11 +333,11 @@ export class Interpreter { for (const qa of node.qs) { const q = await this._eval(qa.q, scope); if (eq(about, q)) { - return await this._eval(qa.a, scope); + return await this._evalClause(qa.a, scope); } } if (node.default) { - return await this._eval(node.default, scope); + return await this._evalClause(node.default, scope); } return NULL; } @@ -355,7 +360,7 @@ export class Interpreter { const times = await this._eval(node.times, scope); assertNumber(times); for (let i = 0; i < times.value; i++) { - const v = await this._eval(node.for, scope); + const v = await this._evalClause(node.for, scope); if (v.type === 'break') { break; } else if (v.type === 'return') { diff --git a/test/syntax.ts b/test/syntax.ts index 6cee2171..913dc32b 100644 --- a/test/syntax.ts +++ b/test/syntax.ts @@ -743,6 +743,15 @@ describe('for', () => { } assert.fail(); }); + + test.concurrent('scope', async () => { + await assert.rejects(async () => { + await exe(` + for 1 let a = 1 + <: a + `); + }); + }); }); describe('each', () => { @@ -1478,6 +1487,27 @@ describe('if', () => { `); eq(res2, STR('kawaii')); }); + + test.concurrent('scope', async () => { + await assert.rejects(async () => { + await exe(` + if true let a = 1 + <: a + `); + }); + await assert.rejects(async () => { + await exe(` + if false null elif true let a = 1 + <: a + `); + }); + await assert.rejects(async () => { + await exe(` + if false null else let a = 1 + <: a + `); + }); + }); }); describe('eval', () => { @@ -1559,6 +1589,21 @@ describe('match', () => { `); eq(res, STR('ai')); }); + + test.concurrent('scope', async () => { + await assert.rejects(async () => { + await exe(` + match 1 { case 1 => let a = 1 } + <: a + `); + }); + await assert.rejects(async () => { + await exe(` + match 1 { default => let a = 1 } + <: a + `); + }); + }); }); describe('exists', () => { diff --git a/unreleased/single-statement-clause-scope.md b/unreleased/single-statement-clause-scope.md new file mode 100644 index 00000000..a86206a7 --- /dev/null +++ b/unreleased/single-statement-clause-scope.md @@ -0,0 +1 @@ +- **Breaking Change** if式やmatch式、for文の内容が1つの文である場合にもスコープが生成されるようになりました。これらの構文内で定義された変数は外部から参照できなくなります。