Repository: syuilo/aiscript (ts)

Issue:
Scopeの定義済み変数の一覧を取得できるように

Pull request description:
add scope.getAll
<!-- ℹ お読みください
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? -->

Resolve #161 

# Why
<!-- なぜそうするのか？ どういう意図なのか？ 何が困っているのか？ -->
<!-- Why do you do it? What are your intentions? What is the problem? -->

# Additional info (optional)
<!-- テスト観点など -->
<!-- Test perspective, etc -->


Patch:
diff --git a/etc/aiscript.api.md b/etc/aiscript.api.md
index 434839b..620a107 100644
--- a/etc/aiscript.api.md
+++ b/etc/aiscript.api.md
@@ -53,6 +53,14 @@ function assertObject(val: Value): asserts val is VObj;
 // @public (undocumented)
 function assertString(val: Value): asserts val is VStr;
 
+// @public (undocumented)
+type Attr_2 = {
+    attr?: {
+        name: string;
+        value: Value;
+    }[];
+};
+
 // @public (undocumented)
 const BOOL: (bool: VBool['value']) => {
     type: "bool";
@@ -206,7 +214,7 @@ function valToJs(val: Value): any;
 function valToString(val: Value, simple?: boolean): any;
 
 // @public (undocumented)
-type Value = VNull | VBool | VNum | VStr | VArr | VObj | VFn | VReturn | VBreak | VContinue;
+type Value = (VNull | VBool | VNum | VStr | VArr | VObj | VFn | VReturn | VBreak | VContinue) & Attr_2;
 
 declare namespace values {
     export {
@@ -220,6 +228,7 @@ declare namespace values {
         VReturn,
         VBreak,
         VContinue,
+        Attr_2 as Attr,
         Value,
         NULL,
         TRUE,
diff --git a/src/interpreter/index.ts b/src/interpreter/index.ts
index 526e1ed..8f561e4 100644
--- a/src/interpreter/index.ts
+++ b/src/interpreter/index.ts
@@ -284,7 +284,18 @@ export class AiScript {
 			}
 
 			case 'def': {
-				scope.add(node.name, await this._eval(node.expr, scope));
+				const value = await this._eval(node.expr, scope);
+				if (node.attr.length > 0) {
+					const attrs: Value['attr'] = [];
+					for (const nAttr of node.attr) {
+						attrs.push({
+							name: nAttr.name,
+							value: await this._eval(nAttr.value, scope)
+						});
+					}
+					value.attr = attrs;
+				}
+				scope.add(node.name, value);
 				return NULL;
 			}
 
diff --git a/src/interpreter/scope.ts b/src/interpreter/scope.ts
index 19336a8..64258ce 100644
--- a/src/interpreter/scope.ts
+++ b/src/interpreter/scope.ts
@@ -61,6 +61,17 @@ export class Scope {
 			});
 	}
 
+	/**
+	 * 現在のスコープに存在する全ての変数を取得します
+	 */
+	@autobind
+	public getAll(): Map<string, Value> {
+		const vars = this.layerdStates.reduce((arr, layer) => {
+			return [...arr, ...layer];
+		}, [] as [string, Value][]);
+		return new Map(vars);
+	}
+
 	/**
 	 * 指定した名前の変数を現在のスコープに追加します
 	 * @param name 変数名
diff --git a/src/interpreter/value.ts b/src/interpreter/value.ts
index 83473d3..f41f8cb 100644
--- a/src/interpreter/value.ts
+++ b/src/interpreter/value.ts
@@ -57,7 +57,14 @@ export type VContinue = {
 	value: null;
 };
 
-export type Value = VNull | VBool | VNum | VStr | VArr | VObj | VFn | VReturn | VBreak | VContinue;
+export type Attr = {
+	attr?: {
+		name: string;
+		value: Value;
+	}[];
+};
+
+export type Value = (VNull | VBool | VNum | VStr | VArr | VObj | VFn | VReturn | VBreak | VContinue) & Attr;
 
 export const NULL = {
 	type: 'null' as const,


Test patch:
diff --git a/test/index.ts b/test/index.ts
index 2e879cb..6a3bf41 100644
--- a/test/index.ts
+++ b/test/index.ts
@@ -68,6 +68,31 @@ it('legacy parser api', async () => {
 	eq(res, STR('Hello, world!'));
 });
 
+describe('Interpreter', () => {
+	describe('Scope', () => {
+		it('getAll', async () => {
+			const aiscript = new AiScript({});
+			await aiscript.exec(Parser.parse(`
+			#a = 1
+			@b() {
+				#x = a + 1
+				x
+			}
+			if true {
+				$y <- 2
+			}
+			$c <- true
+			`));
+			const vars = aiscript.scope.getAll();
+			assert.ok(vars.get('a') != null);
+			assert.ok(vars.get('b') != null);
+			assert.ok(vars.get('c') != null);
+			assert.ok(vars.get('x') == null);
+			assert.ok(vars.get('y') == null);
+		});
+	});
+});
+
 describe('ops', () => {
 	it('==', async () => {
 		eq(await exe('<: (1 == 1)'), BOOL(true));