From 9f2d5b983c507dfd1093b5e8a688b5b0711180f7 Mon Sep 17 00:00:00 2001 From: takejohn Date: Fri, 5 Dec 2025 17:18:05 +0900 Subject: [PATCH] =?UTF-8?q?=E5=90=8D=E5=89=8D=E7=A9=BA=E9=96=93=E5=86=85?= =?UTF-8?q?=E3=81=AB=E3=81=8A=E3=81=84=E3=81=A6=E5=B1=9E=E6=80=A7=E3=81=AE?= =?UTF-8?q?=E5=80=A4=E3=81=8C=E8=A9=95=E4=BE=A1=E3=81=95=E3=82=8C=E3=81=AA?= =?UTF-8?q?=E3=81=84=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/interpreter/index.ts | 64 ++++++++++++++-------- test/interpreter.ts | 59 ++++++++++++++++++++ unreleased/fix-attr-in-ns-not-evaluated.md | 1 + 3 files changed, 100 insertions(+), 24 deletions(-) create mode 100644 unreleased/fix-attr-in-ns-not-evaluated.md diff --git a/src/interpreter/index.ts b/src/interpreter/index.ts index 8423791d..1ea3d16c 100644 --- a/src/interpreter/index.ts +++ b/src/interpreter/index.ts @@ -258,6 +258,9 @@ export class Interpreter { const value = await this._eval(node.expr, nsScope, []); assertValue(value); + + await this.evalAndSetAttr(node.attr, value, scope, []); + if ( node.expr.type === 'fn' && isFunction(value) @@ -302,6 +305,9 @@ export class Interpreter { const value = this._evalSync(node.expr, nsScope, []); assertValue(value); + + this.evalAndSetAttrSync(node.attr, value, scope, []); + if ( node.expr.type === 'fn' && isFunction(value) @@ -648,18 +654,7 @@ export class Interpreter { if (isControl(value)) { return value; } - if (node.attr.length > 0) { - const attrs: Value['attr'] = []; - for (const nAttr of node.attr) { - const value = await this._eval(nAttr.value, scope, callStack); - assertValue(value); - attrs.push({ - name: nAttr.name, - value, - }); - } - value.attr = attrs; - } + await this.evalAndSetAttr(node.attr, value, scope, callStack); if ( node.expr.type === 'fn' && node.dest.type === 'identifier' @@ -1192,18 +1187,7 @@ export class Interpreter { if (isControl(value)) { return value; } - if (node.attr.length > 0) { - const attrs: Value['attr'] = []; - for (const nAttr of node.attr) { - const value = this._evalSync(nAttr.value, scope, callStack); - assertValue(value); - attrs.push({ - name: nAttr.name, - value, - }); - } - value.attr = attrs; - } + this.evalAndSetAttrSync(node.attr, value, scope, callStack); if ( node.expr.type === 'fn' && node.dest.type === 'identifier' @@ -1666,6 +1650,38 @@ export class Interpreter { this.unpauseHandlers = []; } + @autobind + private async evalAndSetAttr(attr: Ast.Attribute[], value: Value, scope: Scope, callStack: readonly CallInfo[]): Promise { + if (attr.length > 0) { + const attrs: Value['attr'] = []; + for (const nAttr of attr) { + const value = await this._eval(nAttr.value, scope, callStack); + assertValue(value); + attrs.push({ + name: nAttr.name, + value, + }); + } + value.attr = attrs; + } + } + + @autobind + private evalAndSetAttrSync(attr: Ast.Attribute[], value: Value, scope: Scope, callStack: readonly CallInfo[]): void { + if (attr.length > 0) { + const attrs: Value['attr'] = []; + for (const nAttr of attr) { + const value = this._evalSync(nAttr.value, scope, callStack); + assertValue(value); + attrs.push({ + name: nAttr.name, + value, + }); + } + value.attr = attrs; + } + } + @autobind private define(scope: Scope, dest: Ast.Expression, value: Value, isMutable: boolean): void { switch (dest.type) { diff --git a/test/interpreter.ts b/test/interpreter.ts index bcf9e64d..0b8bc0ea 100644 --- a/test/interpreter.ts +++ b/test/interpreter.ts @@ -1,6 +1,7 @@ import * as assert from 'assert'; import { describe, expect, test, vi, beforeEach, afterEach } from 'vitest'; import { Parser, Interpreter, values, errors, utils, Ast } from '../src'; +import { FALSE, NUM, OBJ, STR, TRUE, Value } from '../src/interpreter/value'; let { FN_NATIVE } = values; let { AiScriptRuntimeError, AiScriptIndexOutOfRangeError, AiScriptHostsideError } = errors; @@ -324,3 +325,61 @@ describe('pause', () => { }); }); }); + +describe('Attribute', () => { + const getAttr = async (name: string, script: string): Promise => { + const parser = new Parser(); + const interpreter = new Interpreter({}); + const ast = parser.parse(script); + await interpreter.exec(ast); + const value = interpreter.scope.get(name); + return value.attr; + }; + + test.concurrent('no attribute', async () => { + const attr = await getAttr('f', ` + @f() {} + `); + expect(attr).toBeUndefined(); + }); + + test.concurrent('single attribute', async () => { + const attr = await getAttr('f', ` + #[x 42] + @f() {} + `); + expect(attr).toStrictEqual([{ name: 'x', value: NUM(42) }]); + }); + + test.concurrent('multiple attributes', async () => { + const attr = await getAttr('f', ` + #[o { a: 1, b: 2 }] + #[s "ai"] + #[b false] + @f() {} + `); + expect(attr).toStrictEqual([ + { name: 'o', value: OBJ(new Map([['a', NUM(1)], ['b', NUM(2)]])) }, + { name: 's', value: STR('ai') }, + { name: 'b', value: FALSE }, + ]); + }); + + test.concurrent('single attribute without value', async () => { + const attr = await getAttr('f', ` + #[x] + @f() {} + `); + expect(attr).toStrictEqual([{ name: 'x', value: TRUE }]); + }); + + test.concurrent('attribute under namespace', async () => { + const attr = await getAttr('Ns:f', ` + :: Ns { + #[x 42] + @f() {} + } + `); + expect(attr).toStrictEqual([{ name: 'x', value: NUM(42) }]); + }); +}); diff --git a/unreleased/fix-attr-in-ns-not-evaluated.md b/unreleased/fix-attr-in-ns-not-evaluated.md new file mode 100644 index 00000000..25c80fe2 --- /dev/null +++ b/unreleased/fix-attr-in-ns-not-evaluated.md @@ -0,0 +1 @@ +- Fix: 名前空間内において属性の値が評価されない問題を修正