ya-kansuji 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,19 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.0.0] - 2026-07-14
8
+
9
+ ### Security
10
+
11
+ - `toBigInt` / `toNumber` に入力長の上限 `MAX_INPUT_LENGTH`(16384 コードユニット)を導入。
12
+ 過大な入力によるリソース枯渇(DoS)を防ぐため、上限超過時は `RangeError` を投げる。
13
+ 値そのものに上限はなく、上限が掛かるのは入力の長さのみ。
14
+ 従来 `toBigInt` は例外を投げなかったが、過長入力に対しては投げるようになった(破壊的変更)。
15
+
16
+ ### Added
17
+
18
+ - `MAX_INPUT_LENGTH` を公開エクスポートに追加。
19
+
7
20
  ## [0.1.0] - 2026-07-14
8
21
 
9
22
  初回リリース。Ruby gem [ya_kansuji](https://github.com/sugi/ya_kansuji) 1.1.0 の TypeScript/JavaScript 移植版。
@@ -15,5 +28,6 @@
15
28
  - `registerFormatter` / `getFormatter` によるフォーマッタプラグイン機構。
16
29
  - ESM・CommonJS・IIFE(CDN 用)ビルドと型定義を同梱。
17
30
 
18
- [Unreleased]: https://github.com/sugi/ya-kansuji-js/compare/v0.1.0...HEAD
31
+ [Unreleased]: https://github.com/sugi/ya-kansuji-js/compare/v1.0.0...HEAD
32
+ [1.0.0]: https://github.com/sugi/ya-kansuji-js/compare/v0.1.0...v1.0.0
19
33
  [0.1.0]: https://github.com/sugi/ya-kansuji-js/releases/tag/v0.1.0
package/README.md CHANGED
@@ -48,6 +48,18 @@ toBigInt('一無量大数') // => 1000000000000000000000000000000000000000000000
48
48
  toNumber('一無量大数') // throws RangeError
49
49
  ```
50
50
 
51
+ #### 入力長の上限
52
+
53
+ `toBigInt` / `toNumber` が受け付ける入力は最大 `MAX_INPUT_LENGTH`(16384 コードユニット)です。これを超える入力には `RangeError` を投げます。上限は入力文字列の**長さ**に対するもので、変換後の**値**に上限はありません(無量大数を超える大きさも扱えます)。この上限により、任意長テキストを渡してもパースの最悪計算コストが有界になります。
54
+
55
+ ```ts
56
+ import { MAX_INPUT_LENGTH, toBigInt } from 'ya-kansuji'
57
+
58
+ MAX_INPUT_LENGTH // => 16384
59
+ toBigInt('一'.repeat(MAX_INPUT_LENGTH)) // OK
60
+ toBigInt('一'.repeat(MAX_INPUT_LENGTH + 1)) // throws RangeError
61
+ ```
62
+
51
63
  先頭の「マイナス」は負の符号として解釈します。ASCII の `-` や Unicode の `−` (U+2212) は符号として扱いません。
52
64
 
53
65
  ```ts
@@ -134,14 +146,14 @@ npm 経由のインストールなしに、CDN から直接読み込むことも
134
146
 
135
147
  ```html
136
148
  <!-- script タグ (グローバル YaKansuji) -->
137
- <script src="https://cdn.jsdelivr.net/npm/ya-kansuji@0.1.0/dist/index.iife.min.js"></script>
149
+ <script src="https://cdn.jsdelivr.net/npm/ya-kansuji@1.0.0/dist/index.iife.min.js"></script>
138
150
  <script>
139
151
  console.log(YaKansuji.toKan(1234)); // => 千二百三十四
140
152
  </script>
141
153
 
142
154
  <!-- ES Modules -->
143
155
  <script type="module">
144
- import { toNumber } from 'https://cdn.jsdelivr.net/npm/ya-kansuji@0.1.0/+esm';
156
+ import { toNumber } from 'https://cdn.jsdelivr.net/npm/ya-kansuji@1.0.0/+esm';
145
157
  console.log(toNumber('九億6千万卌一')); // => 960000041
146
158
  </script>
147
159
  ```
package/dist/index.cjs CHANGED
@@ -106,6 +106,8 @@ function createUnitValues(units, base) {
106
106
  const UNIT_EXP3_VALUES = createUnitValues(UNIT_EXP3, 10n);
107
107
  const UNIT_EXP4_VALUES = createUnitValues(UNIT_EXP4, 10000n);
108
108
  const MAX_SAFE_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);
109
+ /** toBigInt / toNumber が受け付ける入力の最大長(UTF-16 コードユニット数)。 */
110
+ const MAX_INPUT_LENGTH = 16384;
109
111
  function clean(str) {
110
112
  let result = "";
111
113
  for (const c of str) {
@@ -121,16 +123,20 @@ function clean(str) {
121
123
  * 数値表現だけを読み取る(例: `toBigInt('五x六')` は `5n`)。マッチしなければ `0n` を返す。
122
124
  * 先頭の「マイナス」は負号として扱うが、ASCII の `-` や U+2212(−)は負号として扱わない。
123
125
  * 値の上限はなく、無量大数(10^68)を超える大きさもそのまま扱える。
126
+ * 入力長が {@link MAX_INPUT_LENGTH} を超える場合は `RangeError` を投げる。
124
127
  *
125
128
  * @param str 変換対象の文字列
126
129
  * @returns テキストが表す整数値
130
+ * @throws {RangeError} 入力長が {@link MAX_INPUT_LENGTH} を超える場合
127
131
  * @example
128
132
  * toBigInt('一〇二四') // => 1024n
129
133
  * toBigInt('マイナス千二百') // => -1200n
130
134
  * toBigInt('一無量大数') // => 10n ** 68n
131
135
  */
132
136
  function toBigInt(str) {
133
- const cleaned = clean(String(str));
137
+ const s = String(str);
138
+ if (s.length > 16384) throw new RangeError(`kansuji input exceeds maximum length of ${MAX_INPUT_LENGTH} UTF-16 code units`);
139
+ const cleaned = clean(s);
134
140
  const matched = KANSUJI_REGEXP.exec(cleaned);
135
141
  if (!matched) return 0n;
136
142
  const matchedText = matched[0];
@@ -181,6 +187,7 @@ function toBigInt(str) {
181
187
  * @param str 変換対象の文字列
182
188
  * @returns テキストが表す数値
183
189
  * @throws {RangeError} 値が安全整数の範囲を超える場合
190
+ * @throws {RangeError} 入力長が {@link MAX_INPUT_LENGTH} を超える場合
184
191
  * @example
185
192
  * toNumber('一〇二四') // => 1024
186
193
  * toNumber('一無量大数') // throws RangeError
@@ -387,6 +394,7 @@ function toKan(num, formatter = "simple", options = {}) {
387
394
  return fn(value, options);
388
395
  }
389
396
  //#endregion
397
+ exports.MAX_INPUT_LENGTH = MAX_INPUT_LENGTH;
390
398
  exports.UNIT_EXP3 = UNIT_EXP3;
391
399
  exports.UNIT_EXP4 = UNIT_EXP4;
392
400
  exports.getFormatter = getFormatter;
package/dist/index.d.cts CHANGED
@@ -1,4 +1,6 @@
1
1
  //#region src/parse.d.ts
2
+ /** toBigInt / toNumber が受け付ける入力の最大長(UTF-16 コードユニット数)。 */
3
+ declare const MAX_INPUT_LENGTH = 16384;
2
4
  /**
3
5
  * 漢数字・大字・全角数字が混じったテキストを bigint に変換する。
4
6
  *
@@ -6,9 +8,11 @@
6
8
  * 数値表現だけを読み取る(例: `toBigInt('五x六')` は `5n`)。マッチしなければ `0n` を返す。
7
9
  * 先頭の「マイナス」は負号として扱うが、ASCII の `-` や U+2212(−)は負号として扱わない。
8
10
  * 値の上限はなく、無量大数(10^68)を超える大きさもそのまま扱える。
11
+ * 入力長が {@link MAX_INPUT_LENGTH} を超える場合は `RangeError` を投げる。
9
12
  *
10
13
  * @param str 変換対象の文字列
11
14
  * @returns テキストが表す整数値
15
+ * @throws {RangeError} 入力長が {@link MAX_INPUT_LENGTH} を超える場合
12
16
  * @example
13
17
  * toBigInt('一〇二四') // => 1024n
14
18
  * toBigInt('マイナス千二百') // => -1200n
@@ -24,6 +28,7 @@ declare function toBigInt(str: string): bigint;
24
28
  * @param str 変換対象の文字列
25
29
  * @returns テキストが表す数値
26
30
  * @throws {RangeError} 値が安全整数の範囲を超える場合
31
+ * @throws {RangeError} 入力長が {@link MAX_INPUT_LENGTH} を超える場合
27
32
  * @example
28
33
  * toNumber('一〇二四') // => 1024
29
34
  * toNumber('一無量大数') // throws RangeError
@@ -145,4 +150,4 @@ declare function judicV(num: bigint): string;
145
150
  */
146
151
  declare function judicH(num: bigint): string;
147
152
  //#endregion
148
- export { type KansujiFormatter, type KansujiFormatterOptions, UNIT_EXP3, UNIT_EXP4, getFormatter, gov, judicH, judicV, lawyer, registerFormatter, simple, toBigInt, toKan, toNumber };
153
+ export { type KansujiFormatter, type KansujiFormatterOptions, MAX_INPUT_LENGTH, UNIT_EXP3, UNIT_EXP4, getFormatter, gov, judicH, judicV, lawyer, registerFormatter, simple, toBigInt, toKan, toNumber };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  //#region src/parse.d.ts
2
+ /** toBigInt / toNumber が受け付ける入力の最大長(UTF-16 コードユニット数)。 */
3
+ declare const MAX_INPUT_LENGTH = 16384;
2
4
  /**
3
5
  * 漢数字・大字・全角数字が混じったテキストを bigint に変換する。
4
6
  *
@@ -6,9 +8,11 @@
6
8
  * 数値表現だけを読み取る(例: `toBigInt('五x六')` は `5n`)。マッチしなければ `0n` を返す。
7
9
  * 先頭の「マイナス」は負号として扱うが、ASCII の `-` や U+2212(−)は負号として扱わない。
8
10
  * 値の上限はなく、無量大数(10^68)を超える大きさもそのまま扱える。
11
+ * 入力長が {@link MAX_INPUT_LENGTH} を超える場合は `RangeError` を投げる。
9
12
  *
10
13
  * @param str 変換対象の文字列
11
14
  * @returns テキストが表す整数値
15
+ * @throws {RangeError} 入力長が {@link MAX_INPUT_LENGTH} を超える場合
12
16
  * @example
13
17
  * toBigInt('一〇二四') // => 1024n
14
18
  * toBigInt('マイナス千二百') // => -1200n
@@ -24,6 +28,7 @@ declare function toBigInt(str: string): bigint;
24
28
  * @param str 変換対象の文字列
25
29
  * @returns テキストが表す数値
26
30
  * @throws {RangeError} 値が安全整数の範囲を超える場合
31
+ * @throws {RangeError} 入力長が {@link MAX_INPUT_LENGTH} を超える場合
27
32
  * @example
28
33
  * toNumber('一〇二四') // => 1024
29
34
  * toNumber('一無量大数') // throws RangeError
@@ -145,4 +150,4 @@ declare function judicV(num: bigint): string;
145
150
  */
146
151
  declare function judicH(num: bigint): string;
147
152
  //#endregion
148
- export { type KansujiFormatter, type KansujiFormatterOptions, UNIT_EXP3, UNIT_EXP4, getFormatter, gov, judicH, judicV, lawyer, registerFormatter, simple, toBigInt, toKan, toNumber };
153
+ export { type KansujiFormatter, type KansujiFormatterOptions, MAX_INPUT_LENGTH, UNIT_EXP3, UNIT_EXP4, getFormatter, gov, judicH, judicV, lawyer, registerFormatter, simple, toBigInt, toKan, toNumber };
@@ -1,3 +1,3 @@
1
1
  var YaKansuji=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});let t=[`十`,`百`,`千`],n=[`万`,`億`,`兆`,`京`,`垓`,`𥝱`,`穣`,`溝`,`澗`,`正`,`載`,`極`,`恒河沙`,`阿僧祇`,`那由他`,`不可思議`,`無量大数`],r=`〇一二三四五六七八九`,i=`${r}0123456789零壱壹弌弐貳貮参參弎肆伍陸漆質柒捌玖拾什陌佰阡仟萬秭`,a=Array.from(i),o=Array.from(`01234567890123456789011122233345677789十十百百千千万𥝱`),s=new Map(a.map((e,t)=>[e,o[t]])),c=new Set(`,.,.、. .
2
- .\v.\f.\r. .\xA0. . . . . . . . . . . . .\u2028.\u2029. . . .`.split(`.`)),l=[0n,1n,2n,3n,4n,5n,6n,7n,8n,9n],u=new Map([[`卄`,20n],[`廿`,20n],[`卅`,30n],[`丗`,30n],[`卌`,40n],[`皕`,200n]]),d=[...t,...n.filter(e=>Array.from(e).length===1)],f=n.filter(e=>Array.from(e).length>1),p=[...new Set([...a,...o,...d,...u.keys()])].join(``),m=`${f.join(`|`)}|[${p}]`,h=RegExp(`(?:マイナス)?(?:${m})+`,`u`),g=new RegExp(m,`gu`);function _(e,t){let n=t;return new Map(e.map(e=>{let r=[e,n];return n*=t,r}))}let v=_(t,10n),y=_(n,10000n),b=BigInt(2**53-1);function x(e){let t=``;for(let n of e){let e=s.get(n)??n;c.has(e)||(t+=e)}return t}function S(e){let t=x(String(e)),n=h.exec(t);if(!n)return 0n;let r=n[0];if(r===void 0)return 0n;let i=0n,a=0n,o=null;for(let e of r.match(g)??[])if(e>=`1`&&e<=`9`){let t=l[e.charCodeAt(0)-48];o=(o??0n)*10n+t}else if(e===`0`)o!==null&&(o*=10n);else{let t=u.get(e);if(t!==void 0)i+=t,o=null;else{let t=y.get(e);if(t!==void 0)o!==null&&(i+=o,o=null),i===0n&&(i=1n),a+=i*t,i=0n;else{let t=v.get(e);t!==void 0&&(o??=1n,i+=o*t,o=null)}}}o!==null&&(i+=o);let s=a+i;return r.startsWith(`マイナス`)?-s:s}function C(e){let t=S(e);if(t>b||t<-b)throw RangeError(`kansuji value exceeds Number.MAX_SAFE_INTEGER: ${e}`);return Number(t)}let w=10000n,T=[``,...n],E=T.length,D=w**BigInt(E),O=E*4;function k(e){if(e<0n||e>=D)throw RangeError(`Kansuji formatters require a bigint between 0 and 10^${O} - 1`);let t=[],n=e,r=0;do{let e=T[r];if(e===void 0)throw RangeError(`Kansuji formatters require a bigint between 0 and 10^${O} - 1`);t.push([Number(n%w),e]),n/=w,r++}while(n>0n);return t.reverse()}function A(e){if(e===0n)return`0`;let t=[];for(let[n,r]of k(e))n!==0&&t.push(`${n}${r}`);return t.join(`, `)}function j(e,t,n){if(e===0n)return t;let r=``,i=!0;for(let[t,n]of k(e))t!==0&&(r+=(i?String(t):String(t).padStart(4,`0`))+n,i=!1);return n(r)}function M(e){return j(e,`〇`,e=>e.replace(/[0-9]/g,e=>r.charAt(Number(e))))}function N(e){return j(e,`0`,e=>e.replace(/[0-9]/g,e=>String.fromCharCode(e.charCodeAt(0)+65248)))}function P(e){if(e===0n)return`0`;let t=``;for(let[n,r]of k(e)){if(n===0)continue;let e=String(n);t+=(n>=1e3?`${e[0]},${e.slice(1)}`:e)+r}return t}let F=[1,10,100,1e3];function I(e){if(e===0n)return`零`;let n=``;for(let[i,a]of k(e))if(i!==0){for(let e=t.length;e>=0;e--){let a=t[e-1]??``,o=Math.floor(i/(F[e]??1))%10;o!==0&&(n+=o===1&&a!==``?a:r.charAt(o)+a)}n+=a}return n}let L=new Map([[`simple`,I],[`gov`,A],[`lawyer`,P],[`judic_v`,M],[`judic_h`,N]]);function R(e,t){if(typeof t!=`function`)throw TypeError(`Registering invalid formatter.`);L.set(e,t)}function z(e){return L.get(e)}function B(e,t=`simple`,n={}){let r;if(typeof e==`bigint`)r=e;else if(Number.isSafeInteger(e))r=BigInt(e);else throw RangeError(`toKan requires a safe integer or bigint: ${e}`);if(r<0n)return`マイナス${B(-r,t,n)}`;let i=typeof t==`function`?t:L.get(t);if(!i)throw TypeError(`Unable to find formatter ${String(t)}`);return i(r,n)}return e.UNIT_EXP3=t,e.UNIT_EXP4=n,e.getFormatter=z,e.gov=A,e.judicH=N,e.judicV=M,e.lawyer=P,e.registerFormatter=R,e.simple=I,e.toBigInt=S,e.toKan=B,e.toNumber=C,e})({});
2
+ .\v.\f.\r. .\xA0. . . . . . . . . . . . .\u2028.\u2029. . . .`.split(`.`)),l=[0n,1n,2n,3n,4n,5n,6n,7n,8n,9n],u=new Map([[`卄`,20n],[`廿`,20n],[`卅`,30n],[`丗`,30n],[`卌`,40n],[`皕`,200n]]),d=[...t,...n.filter(e=>Array.from(e).length===1)],f=n.filter(e=>Array.from(e).length>1),p=[...new Set([...a,...o,...d,...u.keys()])].join(``),m=`${f.join(`|`)}|[${p}]`,h=RegExp(`(?:マイナス)?(?:${m})+`,`u`),g=new RegExp(m,`gu`);function _(e,t){let n=t;return new Map(e.map(e=>{let r=[e,n];return n*=t,r}))}let v=_(t,10n),y=_(n,10000n),b=BigInt(2**53-1),x=16384;function S(e){let t=``;for(let n of e){let e=s.get(n)??n;c.has(e)||(t+=e)}return t}function C(e){let t=String(e);if(t.length>16384)throw RangeError(`kansuji input exceeds maximum length of ${x} UTF-16 code units`);let n=S(t),r=h.exec(n);if(!r)return 0n;let i=r[0];if(i===void 0)return 0n;let a=0n,o=0n,s=null;for(let e of i.match(g)??[])if(e>=`1`&&e<=`9`){let t=l[e.charCodeAt(0)-48];s=(s??0n)*10n+t}else if(e===`0`)s!==null&&(s*=10n);else{let t=u.get(e);if(t!==void 0)a+=t,s=null;else{let t=y.get(e);if(t!==void 0)s!==null&&(a+=s,s=null),a===0n&&(a=1n),o+=a*t,a=0n;else{let t=v.get(e);t!==void 0&&(s??=1n,a+=s*t,s=null)}}}s!==null&&(a+=s);let c=o+a;return i.startsWith(`マイナス`)?-c:c}function w(e){let t=C(e);if(t>b||t<-b)throw RangeError(`kansuji value exceeds Number.MAX_SAFE_INTEGER: ${e}`);return Number(t)}let T=10000n,E=[``,...n],D=E.length,O=T**BigInt(D),k=D*4;function A(e){if(e<0n||e>=O)throw RangeError(`Kansuji formatters require a bigint between 0 and 10^${k} - 1`);let t=[],n=e,r=0;do{let e=E[r];if(e===void 0)throw RangeError(`Kansuji formatters require a bigint between 0 and 10^${k} - 1`);t.push([Number(n%T),e]),n/=T,r++}while(n>0n);return t.reverse()}function j(e){if(e===0n)return`0`;let t=[];for(let[n,r]of A(e))n!==0&&t.push(`${n}${r}`);return t.join(`, `)}function M(e,t,n){if(e===0n)return t;let r=``,i=!0;for(let[t,n]of A(e))t!==0&&(r+=(i?String(t):String(t).padStart(4,`0`))+n,i=!1);return n(r)}function N(e){return M(e,`〇`,e=>e.replace(/[0-9]/g,e=>r.charAt(Number(e))))}function P(e){return M(e,`0`,e=>e.replace(/[0-9]/g,e=>String.fromCharCode(e.charCodeAt(0)+65248)))}function F(e){if(e===0n)return`0`;let t=``;for(let[n,r]of A(e)){if(n===0)continue;let e=String(n);t+=(n>=1e3?`${e[0]},${e.slice(1)}`:e)+r}return t}let I=[1,10,100,1e3];function L(e){if(e===0n)return`零`;let n=``;for(let[i,a]of A(e))if(i!==0){for(let e=t.length;e>=0;e--){let a=t[e-1]??``,o=Math.floor(i/(I[e]??1))%10;o!==0&&(n+=o===1&&a!==``?a:r.charAt(o)+a)}n+=a}return n}let R=new Map([[`simple`,L],[`gov`,j],[`lawyer`,F],[`judic_v`,N],[`judic_h`,P]]);function z(e,t){if(typeof t!=`function`)throw TypeError(`Registering invalid formatter.`);R.set(e,t)}function B(e){return R.get(e)}function V(e,t=`simple`,n={}){let r;if(typeof e==`bigint`)r=e;else if(Number.isSafeInteger(e))r=BigInt(e);else throw RangeError(`toKan requires a safe integer or bigint: ${e}`);if(r<0n)return`マイナス${V(-r,t,n)}`;let i=typeof t==`function`?t:R.get(t);if(!i)throw TypeError(`Unable to find formatter ${String(t)}`);return i(r,n)}return e.MAX_INPUT_LENGTH=x,e.UNIT_EXP3=t,e.UNIT_EXP4=n,e.getFormatter=B,e.gov=j,e.judicH=P,e.judicV=N,e.lawyer=F,e.registerFormatter=z,e.simple=L,e.toBigInt=C,e.toKan=V,e.toNumber=w,e})({});
3
3
  globalThis.YaKansuji = YaKansuji;
package/dist/index.js CHANGED
@@ -105,6 +105,8 @@ function createUnitValues(units, base) {
105
105
  const UNIT_EXP3_VALUES = createUnitValues(UNIT_EXP3, 10n);
106
106
  const UNIT_EXP4_VALUES = createUnitValues(UNIT_EXP4, 10000n);
107
107
  const MAX_SAFE_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);
108
+ /** toBigInt / toNumber が受け付ける入力の最大長(UTF-16 コードユニット数)。 */
109
+ const MAX_INPUT_LENGTH = 16384;
108
110
  function clean(str) {
109
111
  let result = "";
110
112
  for (const c of str) {
@@ -120,16 +122,20 @@ function clean(str) {
120
122
  * 数値表現だけを読み取る(例: `toBigInt('五x六')` は `5n`)。マッチしなければ `0n` を返す。
121
123
  * 先頭の「マイナス」は負号として扱うが、ASCII の `-` や U+2212(−)は負号として扱わない。
122
124
  * 値の上限はなく、無量大数(10^68)を超える大きさもそのまま扱える。
125
+ * 入力長が {@link MAX_INPUT_LENGTH} を超える場合は `RangeError` を投げる。
123
126
  *
124
127
  * @param str 変換対象の文字列
125
128
  * @returns テキストが表す整数値
129
+ * @throws {RangeError} 入力長が {@link MAX_INPUT_LENGTH} を超える場合
126
130
  * @example
127
131
  * toBigInt('一〇二四') // => 1024n
128
132
  * toBigInt('マイナス千二百') // => -1200n
129
133
  * toBigInt('一無量大数') // => 10n ** 68n
130
134
  */
131
135
  function toBigInt(str) {
132
- const cleaned = clean(String(str));
136
+ const s = String(str);
137
+ if (s.length > 16384) throw new RangeError(`kansuji input exceeds maximum length of ${MAX_INPUT_LENGTH} UTF-16 code units`);
138
+ const cleaned = clean(s);
133
139
  const matched = KANSUJI_REGEXP.exec(cleaned);
134
140
  if (!matched) return 0n;
135
141
  const matchedText = matched[0];
@@ -180,6 +186,7 @@ function toBigInt(str) {
180
186
  * @param str 変換対象の文字列
181
187
  * @returns テキストが表す数値
182
188
  * @throws {RangeError} 値が安全整数の範囲を超える場合
189
+ * @throws {RangeError} 入力長が {@link MAX_INPUT_LENGTH} を超える場合
183
190
  * @example
184
191
  * toNumber('一〇二四') // => 1024
185
192
  * toNumber('一無量大数') // throws RangeError
@@ -386,4 +393,4 @@ function toKan(num, formatter = "simple", options = {}) {
386
393
  return fn(value, options);
387
394
  }
388
395
  //#endregion
389
- export { UNIT_EXP3, UNIT_EXP4, getFormatter, gov, judicH, judicV, lawyer, registerFormatter, simple, toBigInt, toKan, toNumber };
396
+ export { MAX_INPUT_LENGTH, UNIT_EXP3, UNIT_EXP4, getFormatter, gov, judicH, judicV, lawyer, registerFormatter, simple, toBigInt, toKan, toNumber };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ya-kansuji",
3
- "version": "0.1.0",
3
+ "version": "1.0.0",
4
4
  "description": "Yet another Japanese kansuji library — converts Japanese numerals (漢数字・大字・全角) to/from numbers",
5
5
  "keywords": [
6
6
  "kansuji",
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { toBigInt, toNumber } from './parse.js'
1
+ export { MAX_INPUT_LENGTH, toBigInt, toNumber } from './parse.js'
2
2
  export { UNIT_EXP3, UNIT_EXP4 } from './constants.js'
3
3
  export {
4
4
  getFormatter,
package/src/parse.ts CHANGED
@@ -42,6 +42,8 @@ function createUnitValues(units: readonly string[], base: bigint): Map<string, b
42
42
  const UNIT_EXP3_VALUES = createUnitValues(UNIT_EXP3, 10n)
43
43
  const UNIT_EXP4_VALUES = createUnitValues(UNIT_EXP4, 10_000n)
44
44
  const MAX_SAFE_BIGINT = BigInt(Number.MAX_SAFE_INTEGER)
45
+ /** toBigInt / toNumber が受け付ける入力の最大長(UTF-16 コードユニット数)。 */
46
+ export const MAX_INPUT_LENGTH = 16384
45
47
 
46
48
  function clean(str: string): string {
47
49
  let result = ''
@@ -59,16 +61,22 @@ function clean(str: string): string {
59
61
  * 数値表現だけを読み取る(例: `toBigInt('五x六')` は `5n`)。マッチしなければ `0n` を返す。
60
62
  * 先頭の「マイナス」は負号として扱うが、ASCII の `-` や U+2212(−)は負号として扱わない。
61
63
  * 値の上限はなく、無量大数(10^68)を超える大きさもそのまま扱える。
64
+ * 入力長が {@link MAX_INPUT_LENGTH} を超える場合は `RangeError` を投げる。
62
65
  *
63
66
  * @param str 変換対象の文字列
64
67
  * @returns テキストが表す整数値
68
+ * @throws {RangeError} 入力長が {@link MAX_INPUT_LENGTH} を超える場合
65
69
  * @example
66
70
  * toBigInt('一〇二四') // => 1024n
67
71
  * toBigInt('マイナス千二百') // => -1200n
68
72
  * toBigInt('一無量大数') // => 10n ** 68n
69
73
  */
70
74
  export function toBigInt(str: string): bigint {
71
- const cleaned = clean(String(str))
75
+ const s = String(str)
76
+ if (s.length > MAX_INPUT_LENGTH) {
77
+ throw new RangeError(`kansuji input exceeds maximum length of ${MAX_INPUT_LENGTH} UTF-16 code units`)
78
+ }
79
+ const cleaned = clean(s)
72
80
  const matched = KANSUJI_REGEXP.exec(cleaned)
73
81
  if (!matched) return 0n
74
82
  const matchedText = matched[0]
@@ -123,6 +131,7 @@ export function toBigInt(str: string): bigint {
123
131
  * @param str 変換対象の文字列
124
132
  * @returns テキストが表す数値
125
133
  * @throws {RangeError} 値が安全整数の範囲を超える場合
134
+ * @throws {RangeError} 入力長が {@link MAX_INPUT_LENGTH} を超える場合
126
135
  * @example
127
136
  * toNumber('一〇二四') // => 1024
128
137
  * toNumber('一無量大数') // throws RangeError