Repository: syuilo/aiscript (ts)

Issue:
Functions equivalent to `codePointAt` and `fromCodePoint` in ECMAScript.
## Summary

When I tried to manipulate some characters in AiScript, I encountered a problem that AiScript doesn't have any functionality to get the code point of a character.
I wish I could do `codePointAt` and `fromCodePoint` in AiScript like in ECMAScript.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint

## 概要

AiScriptで文字を操作する時、文字のコードポイントを取得出来ず不便に感じました。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint
と同じことがAiScriptでも出来るようになったらいいなと思いました。

Pull request description:
Enchase コードポイント系のメソッドを追加
<!-- ℹ お読みください
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? -->
- Str:from_codepoint
- Str#codepoint_at
を追加しました。

# Why
<!-- なぜそうするのか？ どういう意図なのか？ 何が困っているのか？ -->
<!-- Why do you do it? What are your intentions? What is the problem? -->
resolve #264 

# Additional info (optional)
<!-- テスト観点など -->
<!-- Test perspective, etc -->
テストも書いておきました( ´∀｀)

Patch:
diff --git a/docs/std.md b/docs/std.md
index c05d079..7d4bfe1 100644
--- a/docs/std.md
+++ b/docs/std.md
@@ -141,6 +141,11 @@ arr.sortの比較関数として使用できます。
 a > b ならば -1、a == b ならば 0、a < b ならば 1 を返します。
 arr.sortの比較関数として使用できます。
 
+### #Str:from_codepoint(codepoint: num): str
+unicodeのコードポイントから文字を生成します。
+
+_codepoint_ は 0 以上、10FFFF<sub>16</sub> 以下である必要があります。
+
 ### @(_v_: str).to_num(): num | null
 値を表す数値を取得します。  
 
@@ -166,6 +171,11 @@ arr.sortの比較関数として使用できます。
 
 ### @(_v_: str).lower(): str
 
+### @(_v_: str).codepoint_at(_i_: num): num | null
+インデックスにある文字のコードポイントを取得します。
+
+文字が存在しない場合は null が返されます。
+
 ## :: Arr
 
 ### @(_v_: arr).len(): num
diff --git a/src/interpreter/lib/std.ts b/src/interpreter/lib/std.ts
index 678207b..a7ffea5 100644
--- a/src/interpreter/lib/std.ts
+++ b/src/interpreter/lib/std.ts
@@ -449,6 +449,7 @@ export const std: Record<string, Value> = {
 			return NUM(1);
 		}
 	}),
+
 	'Str:gt': FN_NATIVE(([a, b]) => {
 		assertString(a);
 		assertString(b);
@@ -460,6 +461,12 @@ export const std: Record<string, Value> = {
 			return NUM(1);
 		}
 	}),
+
+	'Str:from_codepoint': FN_NATIVE(([codePoint]) => {
+		assertNumber(codePoint);
+
+		return STR(String.fromCodePoint(codePoint.value));
+	}),
 	//#endregion
 
 	//#region Arr
diff --git a/src/interpreter/primitive-props.ts b/src/interpreter/primitive-props.ts
index 9808be0..2db90ca 100644
--- a/src/interpreter/primitive-props.ts
+++ b/src/interpreter/primitive-props.ts
@@ -68,6 +68,14 @@ export const PRIMITIVE_PROPS = {
 			const char = chars[i.value];
 			return char ? STR(char) : NULL;
 		}),
+
+		codepoint_at: (target: VStr): VFn => FN_NATIVE(([i], _) => {
+			assertNumber(i);
+
+			const res = target.value.charCodeAt(i.value);
+
+			return Number.isNaN(res) ? NULL : NUM(res);
+		}),
 	},
 
 	arr: {


Test patch:
diff --git a/test/index.ts b/test/index.ts
index 387a93c..0d17ef8 100644
--- a/test/index.ts
+++ b/test/index.ts
@@ -2713,6 +2713,25 @@ describe('std', () => {
 			`);
 			eq(res, STR('\n'));
 		});
+
+		test.concurrent('from_codepoint', async () => {
+			const res = await exe(`
+			<: Str:from_codepoint(65)
+			`);
+			eq(res, STR('A'));
+		});
+
+		test.concurrent('codepoint_at', async () => {
+			let res = await exe(`
+			<: "aiscript".split().map(@(x, _) { x.codepoint_at(0) })
+			`);
+			eq(res, ARR([97, 105, 115, 99, 114, 105, 112, 116].map(x => NUM(x))));
+
+			res = await exe(`
+			<: "".codepoint_at(0)
+			`);
+			eq(res, NULL);
+		});
 	});
 
 	describe('Json', () => {