Skip to content

Commit 00174ea

Browse files
DanMeonclaude
andcommitted
docs: v0.3.2 spec/ADR 추가 — UTF-16 → codepoint 변환 SSOT 단일화
변경사항: - docs/roadmap/v0.3.2/ir-upstream-utf16-helper.md 신설 (8 결정 사항 / 8 인수조건 / 5 영구 비목표) - docs/design/v0.3.2/ir-upstream-utf16-helper-research.md 신설 (API source / sentinel / fallback_end / 단위 테스트 처리 4 결정 ADR) - docs/roadmap/README.md 활성 spec 인덱스에 v0.3.2 row 추가 (Status: Draft) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5abe405 commit 00174ea

3 files changed

Lines changed: 194 additions & 0 deletions

File tree

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
---
2+
status: Draft
3+
description: "v0.3.2 ir-upstream-utf16-helper ADR — API source / 'u32::MAX' sentinel / 'fallback_end' 인자 / 단위 테스트 처리 4 결정의 근거"
4+
target: v0.3.2
5+
last_updated: 2026-05-03
6+
---
7+
8+
# v0.3.2 ir-upstream-utf16-helper — 설계 의사결정 리서치 요약
9+
10+
[v0.3.2/ir-upstream-utf16-helper.md](../../roadmap/v0.3.2/ir-upstream-utf16-helper.md) §결정 사항 중 외부 독자가 "왜?" 를 던질 만한 4건의 업계 선례·대안·실패 시나리오를 기록한다. spec 본문이 최종 결정을 기술하고, 본 문서는 그 결정의 근거를 담는다.
11+
12+
## 결정 매트릭스
13+
14+
| # | 항목 | 옵션 비교 | 채택 | 1차 근거 |
15+
|---|---|---|---|---|
16+
| 1 | API source | A: 자체 복사본 (`utf16_to_cp`) 보존 / B: 상류 `Paragraph::utf16_pos_to_char_idx()` (PR #494) / C: `helpers::utf16_pos_to_char_idx` (옵션 B 노출 가정) | **B** | 자체 복사본은 본 binding 운영 정책 ("상류 신뢰 + 결함 시 PR") 위반. 상류는 옵션 A 채택 — 옵션 C 는 머지 안 됨 |
17+
| 2 | `u32::MAX` sentinel 처리 | A: 호출부 short-circuit 보존 / B: 호출부 분기 제거, 자연 처리 위임 | **B** | `iter().position(\|&off\| off >= u32::MAX)` 가 항상 `None``unwrap_or(char_offsets.len())` 로 자체 short-circuit 결과와 비트 단위 동일. SSOT 분산 회피 |
18+
| 3 | `fallback_end` 인자 제거 | A: invariant 신뢰하고 인자 제거 / B: 방어적 코딩으로 인자 유지 | **A** | `paragraph.rs:22-24` doc-comment 가 `char_offsets.len() == text.chars().count()` 를 정의로 보장. 상류 contract 의 별도 매개변수 운반은 SSOT 분산 |
19+
| 4 | 자체 단위 테스트 처리 | A: 함수 + 테스트 동시 삭제 / B: `Paragraph::utf16_pos_to_char_idx` 호출 wrapper 테스트 보존 / C: `build_char_runs` 통합 테스트 강화 | **A** | 자체 함수가 사라지면 본체 단위 테스트는 *없는 함수* 검증. 상류 자체 테스트 6건 + fixture 회귀가 이미 끝-단 가드 |
20+
21+
## 1. API source — 자체 복사본 vs 상류 `pub` 메서드
22+
23+
### 팩트
24+
25+
- 자체 복사본 위치: `src/ir.rs:421-436``fn utf16_to_cp(char_offsets: &[u32], utf16: u32, fallback_end: usize) -> usize`
26+
- 알고리즘 본체: `iter().position(|&off| off >= utf16).unwrap_or(fallback_end)` (1줄) + `u32::MAX` short-circuit
27+
- 상류 v0.7.7 까지: `pub(crate) fn utf16_pos_to_char_idx(char_offsets: &[u32], utf16_pos: u32) -> usize` (`helpers.rs:189-192`) — 외부 crate 접근 불가
28+
- 본 binding 이 [docs/upstream/issue-utf16-pos-to-char-idx.md](../../upstream/issue-utf16-pos-to-char-idx.md) 로 옵션 A (Paragraph 인스턴스 메서드) / 옵션 B (helpers `pub`) 두 안 제시
29+
- 상류는 옵션 A 채택, [Task #484](https://github.com/edwardkim/rhwp/issues/484) / [PR #494](https://github.com/edwardkim/rhwp/pull/494) 머지 (cherry-pick @DanMeon 3 commits), v0.7.9 GA
30+
- 현재 상류 메서드: `external/rhwp/src/model/paragraph.rs:818-823``pub fn utf16_pos_to_char_idx(&self, utf16_pos: u32) -> usize`
31+
- 선례: v0.3.1 가 동일 결로 [PR #405 (Task #390)](https://github.com/edwardkim/rhwp/pull/405)`Paragraph::control_text_positions` 채택
32+
33+
### 검증자 반박
34+
35+
- "알고리즘이 1줄인데 SSOT 단일화의 가치가 있나? 상류 docstring 도 'silent drift 위험은 무시 가능' 으로 평가" → 상류 평가는 *상류 자체 안에서의* 동기화 비용. 본 binding 은 *외부 호출자* 입장 — `char_offsets` 의 의미축이 미묘하게 변하면 상류 자체 호출자는 컴파일 / 테스트로 즉시 드러나지만 외부 binding 은 *런타임 결과 어긋남* 으로만 드러남. silent drift 비용 비대칭이 본질
36+
- "v0.3.1 PR #405 (`control_text_positions`) 는 알고리즘이 복잡해서 상류 활용이 자연이지만, 본 case 는 1줄이라 다르지 않나?" → 알고리즘 복잡도와 무관하게 *SSOT 단일화의 정책적 가치* 가 결정 축. cutoff 가 모호해지면 정책 자체가 약화
37+
38+
### 최종 결정
39+
40+
**옵션 B — 상류 `Paragraph::utf16_pos_to_char_idx` 사용**. v0.3.1 결정 1 과 같은 결 — 본 binding 의 "상류 신뢰 + 결함 시 PR" 정책 일관 적용.
41+
42+
### 1차 소스
43+
44+
- 상류 PR: <https://github.com/edwardkim/rhwp/pull/494>
45+
- 상류 Task: <https://github.com/edwardkim/rhwp/issues/484>
46+
- 상류 메서드: `external/rhwp/src/model/paragraph.rs:818-823`
47+
- 본 binding 이 제출한 issue 초안: [docs/upstream/issue-utf16-pos-to-char-idx.md](../../upstream/issue-utf16-pos-to-char-idx.md)
48+
49+
## 2. `u32::MAX` sentinel 처리 — short-circuit 보존 vs 자연 처리
50+
51+
### 팩트
52+
53+
- 자체 함수의 sentinel 분기 (`src/ir.rs:427-429`):
54+
55+
```rust
56+
if utf16 == u32::MAX {
57+
return fallback_end;
58+
}
59+
```
60+
- 호출 패턴: 마지막 char_shape 의 `end_utf16``u32::MAX` 로 두는 sentinel (`src/ir.rs:390-394`) — "이 shape 는 paragraph 끝까지 적용" 의미
61+
- 상류 메서드 본체:
62+
63+
```rust
64+
self.char_offsets.iter().position(|&off| off >= utf16_pos).unwrap_or(self.char_offsets.len())
65+
```
66+
- `utf16_pos = u32::MAX` 입력 시 동작: `char_offsets` 의 모든 entry 는 정상 코드 유닛 인덱스 (실제 텍스트 길이 한도 내) → 어떤 entry 도 `u32::MAX` 이상 불가 → `iter().position` 결과 항상 `None``unwrap_or(char_offsets.len())` 도달 → `char_offsets.len()` 반환
67+
- `char_offsets.len() == text.chars().count() == total_cp == fallback_end` (호출부 invariant) — 비트 단위 동일
68+
69+
### 검증자 반박
70+
71+
- "iter().position 이 모든 entry traverse 하는데 short-circuit 이 효율 우위 아닌가?" → paragraph 당 char_shapes 마지막 1회. char_offsets.len() 보통 < 1000 → O(n) traverse 1회는 마이크로초 단위. PDF 렌더 / SVG 직렬화 / Rust→Python 타입 변환의 millisecond 비용에 비해 무시 가능
72+
- "방어적 코딩으로 short-circuit 유지하는 게 안전하지 않나?" → 코드 두 군데에 동일 의미 분산 보유. 어느 한 쪽이 변경되면 다른 쪽이 silent stale — 방어적 코딩이 도리어 *동기화 부담*. 단일 경로가 SSOT 부합
73+
74+
### 최종 결정
75+
76+
**옵션 B — 호출부 분기 제거, 자연 처리 위임**. 두 경로 비트 단위 동일 + 효율 차이 무시 가능 + SSOT 원칙. 상류 메서드의 docstring 이 "모든 entry 가 작으면 `char_offsets.len()`" 를 명시 — 자연 처리가 *문서화된 contract*.
77+
78+
### 1차 소스
79+
80+
- 자체 sentinel 호출 패턴: `src/ir.rs:390-397` (`build_char_runs`)
81+
- 상류 메서드 docstring: `external/rhwp/src/model/paragraph.rs:803-823`
82+
- `Vec<u32>::iter().position` semantics: <https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.position>
83+
84+
## 3. `fallback_end` 인자 제거 — invariant 신뢰 vs 방어적 명시
85+
86+
### 팩트
87+
88+
- 자체 함수의 fallback 인자 (`src/ir.rs:426`): `fallback_end: usize`
89+
- 호출부 (`src/ir.rs:396-397`): `utf16_to_cp(&para.char_offsets, start_utf16, total_cp)``total_cp = para.text.chars().count()` 명시 전달
90+
- 상류 메서드는 인자 없음 — 항상 `self.char_offsets.len()` 반환
91+
- 상류 contract (`paragraph.rs:22-24` doc-comment): `char_offsets[i] = text[i] 에 해당하는 원본 UTF-16 코드 유닛 인덱스``char_offsets.len() == text.chars().count()` (i ↔ text codepoint 1:1)
92+
- v0.3.1 [AC-12] 는 다른 종류의 contract (`controls.len() == positions.len()`) 에 `assert_eq!` 가드 강제
93+
94+
### 검증자 반박
95+
96+
- "v0.3.1 [AC-12] 와 정책 비대칭이다 — 둘 다 상류 contract 인데 왜 다른 정책?" → contract 종류 차이. v0.3.1 contract 는 *두 별개 컬렉션* 길이 일치 (paragraph.rs:734/765/786/796 *4 분기* 각자 보장 — 한 분기에서 push 빠지면 silent drift, 상류 자체 CI 가 4 분기 각각을 검증하는지 불확실). v0.3.2 contract 는 *한 paragraph 의 내부 정의 일관성* (`char_offsets[i] = text[i]` 의 1:1 정의) — 깨지면 cursor_nav / clipboard / 렌더 *전부 동시 깨짐* → 상류 prod 가 매일 호출 (cursor_nav 3회, clipboard 1회, 렌더 다수) 하는 invariant 라 drift 시 상류 CI 즉시 잡음. 검증 비용 비대칭이 정책 비대칭 정당화
97+
- "그래도 invariant 명시 보유가 self-documenting 아닌가?" → 본 결정 항목 3 + 본 §3 자체 + paragraph.rs:22-24 doc-comment 가 self-documenting. 코드 인자로 contract 운반은 인자 의미를 곁가지로 만들어 *모호함* 추가
98+
99+
### 최종 결정
100+
101+
**옵션 A — invariant 신뢰하고 인자 제거**. `paragraph.rs:22-24` 가 SSOT 인 contract 를 본 binding 이 별도 매개변수로 이중 보유는 SSOT 분산. 호출부 단순화 (`para.utf16_pos_to_char_idx(start_utf16)` — 단일 인자) 가 가독성 우위.
102+
103+
### 1차 소스
104+
105+
- 상류 `char_offsets` 의미 정의: `external/rhwp/src/model/paragraph.rs:22-24`
106+
- 자체 `total_cp` 정의: `src/ir.rs:381` (`para.text.chars().count()`)
107+
- 상류 메서드 시그니처: `external/rhwp/src/model/paragraph.rs:818`
108+
109+
## 4. 자체 단위 테스트 처리 — 삭제 vs wrapper 보존 vs 통합 강화
110+
111+
### 팩트
112+
113+
- 자체 단위 테스트 위치 (`src/ir.rs:876-890`):
114+
- `utf16_to_cp_sentinel_returns_fallback``u32::MAX` 입력 시 fallback_end 반환
115+
- `utf16_to_cp_matches_first_ge` — SMP (2 코드 유닛) 혼용 paragraph first-greater-or-equal
116+
- 상류 자체 단위 테스트: Task #484 Stage 2 에서 6 건 추가 (PR #494 의 일부)
117+
- 본 binding 의 통합 테스트:
118+
- `tests/test_ir_mapper.py``_build_inline_runs` 폴백 정책
119+
- `tests/test_ir_*.py` (전체) — `Document.to_ir()` 출력 회귀 가드 (real HWP fixture 기반)
120+
121+
### 검증자 반박
122+
123+
- "옵션 A 면 회귀 가드 약해지는 것 아닌가?" → fixture 회귀 + 폴백 테스트가 끝-단 가드. 자체 단위 테스트는 *함수 단위* 인데 함수가 사라진 이상 통합 테스트가 직접 결과 검증
124+
- "옵션 B (wrapper 테스트) 가 self-documenting 가치 있나?" → wrapper 테스트는 *상류 메서드 호출했더니 상류 메서드 결과* 의 토톨로지. 상류 자체 6 건이 본질 검증 — 본 binding 반복은 동어반복
125+
- "옵션 C (통합 테스트 강화) 는?" → v0.3.1 baseline 회귀가 이미 byte-equal IR 가드. 별도 강화는 같은 검증 반복
126+
127+
### 최종 결정
128+
129+
**옵션 A — 함수 + 단위 테스트 동시 삭제**. 함수가 사라지면 단위 테스트도 자연스럽게 사라짐. 회귀 가드는 fixture 회귀 + 상류 자체 단위 테스트 6 건 이중 보유.
130+
131+
### 1차 소스
132+
133+
- 자체 단위 테스트: `src/ir.rs:876-890`
134+
- 상류 자체 단위 테스트 추가 commit: <https://github.com/edwardkim/rhwp/commit/36631fd> (`Task #484 Stage 2: 단위 테스트 6건 추가`)
135+
136+
## 참조
137+
138+
- [roadmap/v0.3.2/ir-upstream-utf16-helper.md](../../roadmap/v0.3.2/ir-upstream-utf16-helper.md) — 본 리서치의 결정 요약
139+
- [docs/upstream/issue-utf16-pos-to-char-idx.md](../../upstream/issue-utf16-pos-to-char-idx.md) — 본 binding 이 상류에 제출한 이슈 초안 (v0.3.2 GA 시 in-place Frozen)

docs/roadmap/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ rhwp-python 의 버전별 로드맵 + **활성 spec 인덱스 SSOT**. 모든 spe
2323
| v0.3.0 (IR 확장) | Frozen | [v0.3.0/ir-expansion.md](v0.3.0/ir-expansion.md) | [design/v0.3.0/ir-expansion-research.md](../design/v0.3.0/ir-expansion-research.md) |
2424
| v0.3.0 (CLI) | Frozen | [v0.3.0/cli.md](v0.3.0/cli.md) | [design/v0.3.0/cli-research.md](../design/v0.3.0/cli-research.md) |
2525
| v0.3.1 (IR marker char offset) | Frozen | [v0.3.1/ir-marker-char-offset.md](v0.3.1/ir-marker-char-offset.md) | [design/v0.3.1/ir-marker-char-offset-research.md](../design/v0.3.1/ir-marker-char-offset-research.md) |
26+
| v0.3.2 (IR upstream UTF-16 helper) | Draft | [v0.3.2/ir-upstream-utf16-helper.md](v0.3.2/ir-upstream-utf16-helper.md) | [design/v0.3.2/ir-upstream-utf16-helper-research.md](../design/v0.3.2/ir-upstream-utf16-helper-research.md) |
2627
| v0.7.0 (MCP server) | Draft | [v0.7.0/mcp.md](v0.7.0/mcp.md) | [design/v0.7.0/mcp-research.md](../design/v0.7.0/mcp-research.md) |
2728

2829
## 미착수 작업 계획
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
---
2+
status: Draft
3+
description: "v0.3.2 — UTF-16 → codepoint 변환 SSOT 단일화. 상류 'Paragraph::utf16_pos_to_char_idx' (PR #494) 활용 (schema 변경 없음)"
4+
target: v0.3.2
5+
last_updated: 2026-05-03
6+
---
7+
8+
# v0.3.2 — UTF-16 → codepoint 변환 SSOT 단일화
9+
10+
v0.2.0 IR 매핑은 UTF-16 → codepoint 변환을 자체 복사본 (`utf16_to_cp`) 으로 보유해 왔다. 동일 알고리즘이 상류 `document_core::helpers``pub(crate)` 로 갇혀 있어 외부 호출이 불가했기 때문이다.
11+
12+
상류가 [PR #494 (Task #484)](https://github.com/edwardkim/rhwp/pull/494)`Paragraph::utf16_pos_to_char_idx(&self, utf16_pos: u32) -> usize` 를 v0.7.9 에 `pub` 노출하면서 SSOT 단일화가 가능. 본 spec 은 자체 복사본을 제거해 상류 메서드로 치환한다 — schema 변경 없음, 공개 API 동일, IR 출력 byte-equal. 부수로 짝이 되는 issue draft archive 도 묶는다 (in-place Frozen 전환).
13+
14+
주요 결정의 근거·대안·실패 시나리오는 짝 페어: [ir-upstream-utf16-helper-research.md](../../design/v0.3.2/ir-upstream-utf16-helper-research.md).
15+
16+
## 결정 사항
17+
18+
| 항목 || 근거 |
19+
|---|---|---|
20+
| 1 — API source | 상류 `Paragraph::utf16_pos_to_char_idx` (v0.7.9 GA) | PR #494 가 본 binding 이 제출한 [docs/upstream/issue-utf16-pos-to-char-idx.md](../../upstream/issue-utf16-pos-to-char-idx.md) 옵션 A 채택. v0.3.1 의 `control_text_positions` (PR #405) 와 같은 결. 자체 복사본 보유는 본 binding 운영 정책 ("상류 신뢰 + 결함 시 PR") 위반이라 SSOT 단일화 우선. 자세한 옵션 비교는 ADR §1 |
21+
| 2 — `u32::MAX` sentinel 분기 처리 | 호출부 분기 제거, 자연 처리에 위임 | 상류 메서드의 `iter().position``u32::MAX` 입력 시 자연 None → `unwrap_or(char_offsets.len())` 로 자체 short-circuit 결과와 비트 단위 동일. 자세한 본체 비교는 ADR §2 |
22+
| 3 — `fallback_end` 인자 제거 | 상류 메서드는 항상 `char_offsets.len()` 반환 | `paragraph.rs:22-24` doc-comment 가 `char_offsets.len() == text.chars().count()` 를 정의 자체로 보장. v0.3.1 [AC-12] `assert_eq!` 정책과의 비대칭은 contract 종류 차이 — 정당화는 ADR §3 |
23+
| 4 — 자체 단위 테스트 처리 | 함수 삭제와 동시에 제거 | 자체 함수가 사라지면 본체 단위 테스트는 *없는 함수* 검증. 회귀 가드는 fixture 회귀 (`tests/test_ir_*.py`) + 상류 자체 단위 테스트 6건 (Task #484 Stage 2) 이중 보유. 자세한 옵션 비교는 ADR §4 |
24+
| 5 — 적용 범위 | `src/ir.rs::build_char_runs` 단일 호출자 | 코드베이스 grep — Python `_build_inline_runs` 는 RawCharRun 의 `start_cp/end_cp` 를 그대로 소비. 추가 변환 호출 없음 |
25+
| 6 — 상류 핀 bump | 현재 핀 `0fb3e67` 유지 | 핀 history 에 PR #494 머지 commit `60eaa91` (2026-04-30) 포함 — `cargo build` 가 시그니처 해소로 직접 검증 (AC-1). v0.7.9 GA 흡수는 직교 영역, 본 spec 영구 비목표 |
26+
| 7 — 외부 영향 | schema / API / IR 출력 모두 변경 없음 | 자체 복사본 = 상류 메서드 본체로 알고리즘 동일, fallback 의미축 동일. 사용자 visible 변화 0 |
27+
| 8 — issue archive | `docs/upstream/issue-utf16-pos-to-char-idx.md` in-place Frozen 전환 | v0.3.1 GA 직전 (4/30) PR #494 머지됐으나 v0.3.1 spec 이 archive 를 명시 작업으로 안 넣어 누락. 본 spec 이 묶음 — CONVENTIONS § upstream/ 의 in-place Frozen 분기 (다른 spec 이 본 파일 참조) |
28+
29+
## 인수조건
30+
31+
- **AC-1**`cargo build --release` 가 통과한다 (상류 `Paragraph::utf16_pos_to_char_idx` 시그니처 해소로 핀 `0fb3e67` 이 메서드를 포함함을 컴파일러가 직접 검증)
32+
- **AC-2** — fixture (`external/rhwp/samples/aift.hwp`, `external/rhwp/samples/table-vpos-01.hwpx`) 의 `Document.to_ir()` 출력 `InlineRun.start_cp` / `end_cp` 값이 v0.3.1 GA baseline 과 byte-equal
33+
- **AC-3** — 마지막 char_shape 의 `end_utf16 = u32::MAX` 인 paragraph 에서 출고 `InlineRun.end_cp == para.text.chars().count()` (sentinel 의 자연 처리 결과)
34+
- **AC-4**`SchemaVersion``"1.1"` 유지, `python/rhwp/ir/schema/hwp_ir_v1.json` 본문 변경 없음
35+
- **AC-5**`pytest -m "not slow"` 전체 회귀 통과 (`tests/test_ir_*.py` 포함)
36+
- **AC-6**`docs/upstream/issue-utf16-pos-to-char-idx.md` frontmatter `status: Active``Frozen`, 본문 첫 헤더 위에 `> **RESOLVED 2026-04-30** — 상류 PR #494 …` 한 줄 인용 블록 추가
37+
- **AC-7**`docs/upstream/README.md``issue-utf16-pos-to-char-idx.md` row `Status``Frozen`, `RESOLVED` 컬럼에 `2026-04-30 ([PR #494](https://github.com/edwardkim/rhwp/pull/494))` 채움
38+
- **AC-8**`CHANGELOG.md` `[0.3.2]` 섹션 신설, `### Build` 영역에 SSOT 단일화 명시
39+
40+
## 영구 비목표
41+
42+
- **상류 핀 추가 bump** (`0fb3e67` → v0.7.9 GA `2efba58`) — 직교 영역 변경, 별도 minor 에서 다른 enabling change 와 묶음
43+
- **`document_core::helpers::utf16_pos_to_char_idx` visibility 변경** — 상류 옵션 A 채택으로 옵션 B 검토 가치 소멸
44+
- **다른 PATCH 항목 묶음 (BMP fix / `[async]` extras 키 정리 등)** — 본 spec 은 단일 SSOT 정정 단위, 다른 후보는 별도 spec
45+
- **자체 단위 테스트의 wrapper 형태 보존** — ADR §4 검증자 반박 참조
46+
- **`InlineRun` 의 codepoint → UTF-16 환원 옵션** — UTF-16 노출은 v0.2.0 결정 시점부터 영구 안 함
47+
48+
## 참조
49+
50+
- 짝 페어 (ADR): [ir-upstream-utf16-helper-research.md](../../design/v0.3.2/ir-upstream-utf16-helper-research.md)
51+
- 자체 이슈 초안: [docs/upstream/issue-utf16-pos-to-char-idx.md](../../upstream/issue-utf16-pos-to-char-idx.md) (v0.3.2 GA 시 in-place Frozen)
52+
- 상류 PR: <https://github.com/edwardkim/rhwp/pull/494>
53+
- 상류 Task: <https://github.com/edwardkim/rhwp/issues/484>
54+
- 상류 메서드: `external/rhwp/src/model/paragraph.rs:818``pub fn utf16_pos_to_char_idx`

0 commit comments

Comments
 (0)