Repository: syuilo/aiscript
Pull request #230 (2023-01-28 16:25:00)

fix: break & return statement in each statement #229
<!-- ℹ お読みください
PRありがとうございます！ PRを作成する前に、以下をご確認ください:
- 可能であればタイトルに、以下で示すようなPRの種類が分かるキーワードをプリフィクスしてください。
  - fix / refactor / feat / enhance / perf / chore
  - また、PRの粒度が適切であることを確認してください。ひとつのPRに複数の種類の変更や関心を含めることは避けてください。
- このPRによって解決されるIssueがある場合は、そのIssueへの参照を本文内に含めてください。
- CHANGELOG.mdに変更点を追記してください。リファクタリングなど、利用者に影響を与えない変更についてはこの限りではありません。
- この変更により新たに作成、もしくは更新すべきドキュメントがないか確認してください。
- 機能追加やバグ修正をした場合は、可能であればテストケースを追加してください。
- テスト、Lintが通っていることを予め確認してください。
  - `npm run test`、`npm run lint`でぞれぞれ実施可能です
- `npm run api`を実行してAPIレポートを更新し、差分がある場合はコミットしてください。
ご協力ありがとうございます🤗
-->
<!-- ℹ README
Thank you for your PR! Before creating a PR, please check the following:
- If possible, prefix the title with a keyword that identifies the type of this PR, as shown below.
  - fix / refactor / feat / enhance / perf / chore
  - Also, make sure that the granularity of this PR is appropriate. Please do not include more than one type of change or interest in a single PR.
- If there is an Issue which will be resolved by this PR, please include a reference to the Issue in the text.
- Please add the summary of the changes to CHANGELOG.md. However, this is not necessary for changes that do not affect the users, such as refactoring.
- Check if there are any documents that need to be created or updated due to this change.
- If you have added a feature or fixed a bug, please add a test case if possible.
- Please make sure that tests and Lint are passed in advance.
  - You can run it with `npm run test` and `npm run lint`.
- Run `npm run api` to update the API report and commit it if there are any diffs.
Thanks for your cooperation 🤗
-->

# What
<!-- このPRで何をしたのか？ どう変わるのか？ -->
<!-- What did you do with this PR? How will it change things? -->
- #229 の修正
- 対応するテストの追加・修正
  - 追加: each文の中のreturn文
  - 修正: break文の後にループが終わっているか
- CHANGELOG.mdに修正内容を追加

# Why
<!-- なぜそうするのか？ どういう意図なのか？ 何が困っているのか？ -->
<!-- Why do you do it? What are your intentions? What is the problem? -->

# Additional info (optional)
<!-- テスト観点など -->
<!-- Test perspective, etc -->



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9f878f0..66a8fa8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
 # 0.12.2
 - 否定構文`!`
 - インタプリタ処理速度の調整
+- each文の中でbreakとreturnが動作しない問題を修正
 
 # 0.12.1
 - 文字列をシングルクォートでも定義可能に
diff --git a/src/interpreter/index.ts b/src/interpreter/index.ts
index f0305e5..470163c 100644
--- a/src/interpreter/index.ts
+++ b/src/interpreter/index.ts
@@ -281,9 +281,14 @@ export class Interpreter {
 				const items = await this._eval(node.items, scope);
 				assertArray(items);
 				for (const item of items.value) {
-					await this._eval(node.for, scope.createChildScope(new Map([
+					const v = await this._eval(node.for, scope.createChildScope(new Map([
 						[node.var, item],
 					])));
+					if (v.type === 'break') {
+						break;
+					} else if (v.type === 'return') {
+						return v;
+					}
 				}
 				return NULL;
 			}


Test patch:
diff --git a/test/index.ts b/test/index.ts
index e95b9f5..772d952 100644
--- a/test/index.ts
+++ b/test/index.ts
@@ -1080,6 +1080,37 @@ describe('Return', () => {
 		`);
 		eq(res, NUM(1));
 	});
+
+	test.concurrent('return inside each', async () =>
+	{
+		const res = await exe(`
+		@f() {
+			var count = 0
+			each (let item, ["ai", "chan", "kawaii"]) {
+				count += 1
+				if (item == "chan") {
+					return count
+				}
+			}
+		}
+		<: f()
+		`);
+		eq(res, NUM(2));
+	});
+
+	test.concurrent('return inside each 2', async () =>
+	{
+		const res = await exe(`
+		@f() {
+			each (let item, ["ai", "chan", "kawaii"]) {
+				return 1
+			}
+			2
+		}
+		<: f()
+		`);
+		eq(res, NUM(1));
+	});
 });
 
 describe('Eval', () => {
@@ -1288,12 +1319,12 @@ describe('loop', () => {
 
 	test.concurrent('with continue', async () => {
 		const res = await exe(`
-		var a = ["ai" "chan" "kawaii" "!"]
+		var a = ["ai" "chan" "kawaii" "yo" "!"]
 		var b = []
 		loop {
 			var x = a.shift()
 			if (x == "chan") continue
-			if (x == "!") break
+			if (x == "yo") break
 			b.push(x)
 		}
 		<: b
@@ -1396,7 +1427,7 @@ describe('for of', () => {
 	test.concurrent('Break', async () => {
 		const res = await exe(`
 		let msgs = []
-		each let item, ["ai", "chan", "kawaii"] {
+		each let item, ["ai", "chan", "kawaii" "yo"] {
 			if (item == "kawaii") break
 			msgs.push([item, "!"].join())
 		}