fix: 수식 토크나이저 keyword prefix-split 구현 (#576)#579
Conversation
`timesm` → `times` + `m`, `simZ` → `sim` + `Z` 등 알려진 키워드 접두사를 인식하여 분할한다. 기존 read_command는 연속 alphanumeric 시퀀스를 하나의 토큰으로 반환했으나, HWP 수식 스크립트에서 키워드와 식별자가 공백 없이 붙어 있는 경우 올바르게 분리되지 않았다. 변경: - symbols.rs: is_known_command(), longest_keyword_prefix_len() 추가 - tokenizer.rs: read_command()에서 longest-prefix 매칭 후 분할 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the equation tokenizer to split a known command keyword from the front of a contiguous alphanumeric token (for example timesm → times + m) so the equation parser can render operator/style/symbol commands correctly when they are attached to following identifiers.
Changes:
- Added
is_known_command()andlongest_keyword_prefix_len()insymbols.rsto centralize keyword detection across symbols, functions, decorations, styles, and structure commands. - Updated
Tokenizer::read_command()to keep exact known commands intact, otherwise split off the longest known keyword prefix and rewind the remaining characters. - Added tokenizer and symbol tests covering prefix-splitting, exact-match preservation, and unknown-token behavior.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/renderer/equation/tokenizer.rs |
Applies longest-known-keyword prefix splitting during command tokenization and adds regression tests for the new behavior. |
src/renderer/equation/symbols.rs |
Adds shared helpers for known-command detection and longest-prefix lookup, plus unit tests for the new lookup behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // 알려진 키워드 접두사가 있으면 분할: 키워드만 반환하고 나머지는 되감기 | ||
| if let Some(prefix_len) = symbols::longest_keyword_prefix_len(&value) { | ||
| self.pos = start + prefix_len; | ||
| let prefix = value[..prefix_len].to_string(); | ||
| return Token::new(TokenType::Command, prefix, start); |
| let upper = cmd.to_ascii_uppercase(); | ||
| is_structure_command(&upper) || FUNCTIONS.contains_key(upper.as_str()) |
|
@oksure 님, @planet6897 님 — 두 PR (#578 + #579) 이 동일 이슈 #576 의 다른 접근으로 동시 등록되어 있습니다. 메인테이너 검토 결과 양쪽 통합 이 가장 정합한 방향으로 판단됩니다. 두 분의 협업 PR 을 부탁드리는 첫 시도로 본 안내를 드립니다. 두 PR 의 정합한 영역 비교
통합 시 본질두 영역 모두 가치 있고 본질적으로 보완:
일반화 알고리즘 (PR #579) 의 잠재 회귀: PR #579 의
PR #578 의 광범위 sweep 결과로 이런 회귀가 실제 fixture 에서 발현되는지 검증되어 4 키워드만 영향이 있다는 것이 확정됨. 통합 PR 제안다음 통합 영역 권장:
협업 PR 자원 요청본 안내는 두 컨트리뷰터의 커뮤니케이션 통한 PR 처리 시도 의 첫 사례입니다. 둘 중 한 분이 통합 PR 을 자원해주시거나, 함께 작업하실 방식 (예: PR #579 base 위에 PR #578 의 sweep 검증 영역 추가) 알려주시면 그에 따라 진행하겠습니다. PR #578 / #579 의 OPEN 상태는 통합 PR 결정까지 유지합니다 (close 안 함). 본 사이클의 매우 활발한 컨트리뷰션 (3 컨트리뷰터, 10+ PR 처리 중) 의 진보 사례로 두 분의 협업을 부탁드립니다. |
|
감사합니다. 통합 PR을 자원합니다. Copilot 리뷰에서 지적된
@planet6897 님, PR #578 의 테스트 코드와 sweep 결과 참고하겠습니다. |
Copilot 리뷰 + 메인테이너 피드백 반영: - `alphabet` → `alpha`+`bet` 회귀 방지 - `sinx` → `sin`+`x` 분할 방지 is_splittable_keyword()를 도입하여 prefix-split 대상을 OPERATORS, BIG_OPERATORS, SPECIAL_SYMBOLS, FONT_STYLES로 한정. 그리스 문자, 함수, 장식, 구조 명령어는 변수명 충돌 위험으로 제외. PR edwardkim#578 (@planet6897) sweep 결과와 일치: 실제 영향 키워드는 times/sim/TIMES/SIM 4개 (모두 OPERATORS). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Copilot 리뷰 + 메인테이너 피드백 반영하여 2543722 push 했습니다. 변경 내용
회귀 검증
@planet6897 님의 158 fixture sweep 결과(영향 키워드 4개: |
|
@edwardkim 님, 통합 제안 감사합니다. 검토 결과 제 #579의 일반화 인프라( planet6897 님의 #578이 158 fixture sweep으로 안전성 검증 완료된 상태이니, #578 먼저 머지 진행해주시고 제 #579는 close 하겠습니다. 일반화 인프라는 오분리 가드 설계 보강 후 별도 PR로 다시 올리겠습니다. |
PR #578 (Task #576, @planet6897 / Jaeook Ryu) 1차 검토: - 본질 결함: tokenizer.rs::read_command (L104) prefix-split list 가 bold/it/rm 만 처리 → times/sim/TIMES/SIM 결합 식별자 단일 토큰화 (예: "a timesm" → "timesm" italic) - 본질 정정: list 확장 bold/it/rm → bold/it/rm/times/sim/TIMES/SIM (광범위 sweep 158 fixture / 563 scripts 결과 4 키워드만 발현) - 회귀 위험 0 — alpha/over/sqrt 등 다른 keyword 는 공백 구분 보존 - 단일 파일 본질 (tokenizer.rs +53 LOC + 6 신규 unit tests) PR #579 영역: 5/4 협업 PR 권유 후 @oksure self-close → PR #578 단독 처리 정합, @planet6897 답신 학습 의지 표명. 본 환경 임시 검증: - cherry-pick 충돌 0 - cargo test --lib --release 1140 passed (회귀 0) - task576 unit tests 6/6 passed (alpha 회귀 차단 가드 포함) - svg_snapshot 6/6 / clippy 0건 권장 처리: 옵션 A — 핀셋 cherry-pick + 결정적 검증 + 광범위 sweep + 작업지시자 시각 판정 (exam_science page 3/4).
…키워드 prefix-split — @planet6897 / Jaeook Ryu 1 commit + 시각 판정 ★ 통과) PR #578 (Task #576, @planet6897 PR / Jaeook Ryu commit author): - 본질 결함: tokenizer.rs::read_command (L104) prefix-split list 가 bold/it/rm 만 처리 → times/sim/TIMES/SIM 결합 식별자 단일 토큰화 (예: "a timesm" → "timesm" italic) - 본질 정정: list 확장 bold/it/rm → bold/it/rm/times/sim/TIMES/SIM - 광범위 sweep (158 fixture / 563 scripts) 결과 4 키워드만 발현 → alpha/over/sqrt 등 회귀 위험 0 - 단일 파일 본질 (tokenizer.rs +53 LOC + 6 신규 unit tests) PR #579 영역: 5/4 협업 PR 권유 후 @oksure self-close → PR #578 단독 처리 정합. 검증: - cargo test --lib --release 1140 passed (회귀 0) - task576 unit tests 6/6 passed (alpha 회귀 차단 가드 포함) - svg_snapshot 6/6 / clippy 0 / build --release - WASM 4,583,156 bytes (v0.7.10 baseline +1,691 bytes — tokenizer.rs +53 LOC 정합) - 광범위 페이지네이션 회귀 sweep: 164 fixture (158 hwp + 6 hwpx) / 1,614 페이지 / 페이지 수 회귀 0 - exam_science byte 차이: page 3/4 만 의도된 정정 (page 1/2 byte-identical) 작업지시자 웹 시각 판정 ★ 통과. closes #576.
문제
{a timesm}스크립트에서 토크나이저가timesm을 하나의 Command 토큰으로 반환.파서에서 알려진 키워드로 인식되지 않아 이탤릭 텍스트 "timesm"으로 렌더링됨.
동일 패턴:
simZ→ "simZ" (기대값:∼+Z)원인
read_command()가 연속 alphanumeric 시퀀스를 무조건 하나의 토큰으로 반환.HWP 수식 스크립트에서 키워드와 식별자가 공백 없이 결합되는 경우를 처리하지 못함.
해결
Longest keyword prefix-split 방식 적용:
symbols.rs에is_known_command(),longest_keyword_prefix_len()추가tokenizer.rs의read_command()에서:검증
cargo test전체 통과 (기존 12 + 신규 6 = 18 tokenizer 테스트, symbols 2 추가)cargo clippy -- -D warnings경고 0Closes #576