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: 名前空間内において属性の値が評価されない問題を修正