Skip to content

Commit

Permalink
if式やmatch式、for文が文1つでもスコープを生成するように (aiscript-dev#827)
Browse files Browse the repository at this point in the history
takejohn authored Oct 28, 2024
1 parent 9f43986 commit e02c847
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 7 deletions.
19 changes: 12 additions & 7 deletions src/interpreter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand Down Expand Up @@ -266,6 +266,11 @@ export class Interpreter {
}
}

@autobind
private _evalClause(node: Ast.Statement | Ast.Expression, scope: Scope): Promise<Value> {
return this._eval(node, Ast.isStatement(node) ? scope.createChildScope() : scope);
}

@autobind
private _eval(node: Ast.Node, scope: Scope): Promise<Value> {
return this.__eval(node, scope).catch(e => {
Expand Down Expand Up @@ -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) {

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

View workflow job for this annotation

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);
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;
Expand All @@ -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;
}
Expand All @@ -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') {
Expand Down
45 changes: 45 additions & 0 deletions test/syntax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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', () => {
Expand Down
1 change: 1 addition & 0 deletions unreleased/single-statement-clause-scope.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **Breaking Change** if式やmatch式、for文の内容が1つの文である場合にもスコープが生成されるようになりました。これらの構文内で定義された変数は外部から参照できなくなります。

0 comments on commit e02c847

Please sign in to comment.