Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 40 additions & 24 deletions src/interpreter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,9 @@

const value = await this._eval(node.expr, nsScope, []);
assertValue(value);

await this.evalAndSetAttr(node.attr, value, scope, []);

if (
node.expr.type === 'fn'
&& isFunction(value)
Expand Down Expand Up @@ -302,6 +305,9 @@

const value = this._evalSync(node.expr, nsScope, []);
assertValue(value);

this.evalAndSetAttrSync(node.attr, value, scope, []);

if (
node.expr.type === 'fn'
&& isFunction(value)
Expand Down Expand Up @@ -542,7 +548,7 @@

case 'loop': {
// eslint-disable-next-line no-constant-condition
while (true) {

Check warning on line 551 in src/interpreter/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unnecessary conditional, value is always truthy
const v = await this._run(node.statements, scope.createChildScope(), callStack);
if (v.type === 'break') {
if (v.label != null && v.label !== node.label) {
Expand Down Expand Up @@ -648,18 +654,7 @@
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'
Expand Down Expand Up @@ -1086,7 +1081,7 @@

case 'loop': {
// eslint-disable-next-line no-constant-condition
while (true) {

Check warning on line 1084 in src/interpreter/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unnecessary conditional, value is always truthy
const v = this._runSync(node.statements, scope.createChildScope(), callStack);
if (v.type === 'break') {
if (v.label != null && v.label !== node.label) {
Expand Down Expand Up @@ -1192,18 +1187,7 @@
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'
Expand Down Expand Up @@ -1647,7 +1631,7 @@
public pause(): void {
if (this.pausing) return;
let resolve: () => void;
const promise = new Promise<void>(r => { resolve = () => r(); });

Check warning on line 1634 in src/interpreter/index.ts

View workflow job for this annotation

GitHub Actions / lint

Missing return type on function
this.pausing = { promise, resolve: resolve! };
for (const handler of this.pauseHandlers) {
handler();
Expand All @@ -1666,6 +1650,38 @@
this.unpauseHandlers = [];
}

@autobind
private async evalAndSetAttr(attr: Ast.Attribute[], value: Value, scope: Scope, callStack: readonly CallInfo[]): Promise<void> {
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) {
Expand Down
59 changes: 59 additions & 0 deletions test/interpreter.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -324,3 +325,61 @@ describe('pause', () => {
});
});
});

describe('Attribute', () => {
const getAttr = async (name: string, script: string): Promise<Value['attr']> => {
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) }]);
});
});
1 change: 1 addition & 0 deletions unreleased/fix-attr-in-ns-not-evaluated.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Fix: 名前空間内において属性の値が評価されない問題を修正
Loading