Skip to content

feature: ネストされた名前空間下の変数を参照できるように (#629) #630

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 22, 2024
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@
- `arr.fill`, `arr.repeat`, `Arr:create`を追加
- JavaScriptのように分割代入ができるように(現段階では機能は最小限)
- スコープおよび名前が同一である変数が宣言された際のエラーメッセージを修正
- ネストされた名前空間下の変数を参照できるように

# 0.17.0
- `package.json`を修正
6 changes: 5 additions & 1 deletion etc/aiscript.api.md
Original file line number Diff line number Diff line change
@@ -850,9 +850,11 @@ type Return_2 = NodeBase_2 & {

// @public (undocumented)
export class Scope {
constructor(layerdStates?: Scope['layerdStates'], parent?: Scope, name?: Scope['name']);
constructor(layerdStates?: Scope['layerdStates'], parent?: Scope, name?: Scope['name'], nsName?: string);
add(name: string, variable: Variable): void;
assign(name: string, val: Value): void;
// (undocumented)
createChildNamespaceScope(nsName: string, states?: Map<string, Variable>, name?: Scope['name']): Scope;
// Warning: (ae-forgotten-export) The symbol "Variable" needs to be exported by the entry point index.d.ts
//
// (undocumented)
@@ -863,6 +865,8 @@ export class Scope {
// (undocumented)
name: string;
// (undocumented)
nsName?: string;
// (undocumented)
opts: {
log?(type: string, params: Record<string, any>): void;
onUpdated?(name: string, value: Value): void;
17 changes: 9 additions & 8 deletions src/interpreter/index.ts
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@
in?(q: string): Promise<string>;
out?(value: Value): void;
err?(e: AiScriptError): void;
log?(type: string, params: Record<string, any>): void;

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

GitHub Actions / lint

Unexpected any. Specify a different type
maxStep?: number;
} = {},
) {
@@ -112,10 +112,10 @@
}

@autobind
public static collectMetadata(script?: Ast.Node[]): Map<any, any> | undefined {

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

GitHub Actions / lint

Unexpected any. Specify a different type

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

GitHub Actions / lint

Unexpected any. Specify a different type
if (script == null || script.length === 0) return;

function nodeToJs(node: Ast.Node): any {

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

GitHub Actions / lint

Unexpected any. Specify a different type
switch (node.type) {
case 'arr': return node.value.map(item => nodeToJs(item));
case 'bool': return node.value;
@@ -175,11 +175,11 @@
}

@autobind
private async collectNs(script: Ast.Node[]): Promise<void> {
private async collectNs(script: Ast.Node[], scope = this.scope): Promise<void> {
for (const node of script) {
switch (node.type) {
case 'ns': {
await this.collectNsMember(node);
await this.collectNsMember(node, scope);
break;
}

@@ -191,8 +191,10 @@
}

@autobind
private async collectNsMember(ns: Ast.Namespace): Promise<void> {
const scope = this.scope.createChildScope();
private async collectNsMember(ns: Ast.Namespace, scope = this.scope): Promise<void> {
const nsScope = scope.createChildNamespaceScope(ns.name);

await this.collectNs(ns.members, nsScope);

for (const node of ns.members) {
switch (node.type) {
@@ -203,16 +205,15 @@

const variable: Variable = {
isMutable: node.mut,
value: await this._eval(node.expr, scope),
value: await this._eval(node.expr, nsScope),
};
scope.add(node.name, variable);
nsScope.add(node.name, variable);

this.scope.add(ns.name + ':' + node.name, variable);
break;
}

case 'ns': {
break; // TODO
break; // nop
}

default: {
@@ -269,7 +270,7 @@
if (cond.value) {
return this._eval(node.then, scope);
} else {
if (node.elseif && node.elseif.length > 0) {

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

GitHub Actions / lint

Unnecessary conditional, value is always truthy
for (const elseif of node.elseif) {
const cond = await this._eval(elseif.cond, scope);
assertBoolean(cond);
@@ -303,7 +304,7 @@

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

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

GitHub Actions / lint

Unnecessary conditional, value is always truthy
const v = await this._run(node.statements, scope.createChildScope());
if (v.type === 'break') {
break;
@@ -630,12 +631,12 @@
} else if (dest.type === 'arr') {
assertArray(value);
await Promise.all(dest.value.map(
(item, index) => this.assign(scope, item, value.value[index] ?? NULL)

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

GitHub Actions / lint

Missing trailing comma
));
} else if (dest.type === 'obj') {
assertObject(value);
await Promise.all([...dest.value].map(
([key, item]) => this.assign(scope, item, value.value.get(key) ?? NULL)

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

GitHub Actions / lint

Missing trailing comma
));
} else {
throw new AiScriptRuntimeError('The left-hand side of an assignment expression must be a variable or a property/index access.');
13 changes: 11 additions & 2 deletions src/interpreter/scope.ts
Original file line number Diff line number Diff line change
@@ -11,11 +11,13 @@ export class Scope {
log?(type: string, params: Record<string, any>): void;
onUpdated?(name: string, value: Value): void;
} = {};
public nsName?: string;

constructor(layerdStates: Scope['layerdStates'] = [], parent?: Scope, name?: Scope['name']) {
constructor(layerdStates: Scope['layerdStates'] = [], parent?: Scope, name?: Scope['name'], nsName?: string) {
this.layerdStates = layerdStates;
this.parent = parent;
this.name = name || (layerdStates.length === 1 ? '<root>' : '<anonymous>');
this.nsName = nsName;
}

@autobind
@@ -42,6 +44,12 @@ export class Scope {
return new Scope(layer, this, name);
}

@autobind
public createChildNamespaceScope(nsName: string, states: Map<string, Variable> = new Map(), name?: Scope['name']): Scope {
const layer = [states, ...this.layerdStates];
return new Scope(layer, this, name, nsName);
}

/**
* 指定した名前の変数を取得します
* @param name - 変数名
@@ -90,7 +98,7 @@ export class Scope {
}

/**
* 指定した名前の変数を現在のスコープに追加します
* 指定した名前の変数を現在のスコープに追加します。名前空間である場合は接頭辞を付して親のスコープにも追加します
* @param name - 変数名
* @param val - 初期値
*/
@@ -105,6 +113,7 @@ export class Scope {
}
states.set(name, variable);
if (this.parent == null) this.onUpdated(name, variable.value);
else if (this.nsName != null) this.parent.add(this.nsName + ':' + name, variable);
}

/**
27 changes: 27 additions & 0 deletions test/index.ts
Original file line number Diff line number Diff line change
@@ -1871,6 +1871,33 @@ describe('namespace', () => {
}
assert.fail();
});

test.concurrent('nested', async () => {
const res = await exe(`
<: Foo:Bar:baz()

:: Foo {
:: Bar {
@baz() { "ai" }
}
}
`);
eq(res, STR('ai'));
});

test.concurrent('nested ref', async () => {
const res = await exe(`
<: Foo:baz

:: Foo {
let baz = Bar:ai
:: Bar {
let ai = "kawaii"
}
}
`);
eq(res, STR('kawaii'));
});
});

describe('literal', () => {
Loading