/* ============================================================================= * ui_template_equation_text.ts * 부품 「수식」 글 꼴(801-1 · 요구 662) — 수식 나무 ↔ 키보드 한 줄 식의 한 곳(화면 · DOM 없음 · 시험 `test_801_1_equation_inline`). * textOf = 나무 → 키보드 식(변수 = 열쇠 · 표 찾기 · 요소 · 로직 부르기 = 서버 `입력글`(별칭 꼴) › 원문 식 · 만약 = 세 갈래 모두) · `swap` 마디는 그 글로 * leavesOf = 보이는 잎(수 · 글 · 변수 · 블록) 차례 — 값 식 그림의 숫자 차례와 같음(탄 갈래만 · 반올림 감싸개는 첫 인자만) * 보이기 규칙 = `taken`(만약 = 탄 갈래만) · `HIDDEN`(반올림 = 자리 감싸개 숨김) — 셈 · 식은 그대로(그림만) * ========================================================================== */ import type { EqNode as Base } from "./ui_template_equation"; /** 서버 마디 + 키보드 글(`입력글` · 별칭 꼴 · 없으면 식) */ type EqNode = Base & { 입력글?: string }; /** 그림에서 감싸개를 숨기는 함수(첫 인자만 보임) */ export const HIDDEN = new Set(["반올림"]); /** 만약 — 탄 갈래 마디(모르면 null = 세 갈래 모두 보임) */ export const taken = (n: EqNode): EqNode | null => n.걸림 === 1 ? (n.참 ?? null) : n.걸림 === 2 ? (n.거짓 ?? null) : null; const isOp = (n: EqNode | undefined): boolean => n?.꼴 === "연산"; const wrap = (text: string, yes: boolean): string => (yes ? `(${text})` : text); /** 나무 → 키보드 한 줄 식 — 엔진 묶음 그대로(분수 · 퍼센트가 연산 오른쪽이면 괄호) */ export function textOf(n: EqNode | undefined, swap?: Map): string { if (!n) return ""; const own = swap?.get(n); if (own !== undefined) return own; const t = (x: EqNode | string | undefined): string => typeof x === "string" ? x : textOf(x, swap); switch (n.꼴) { case "수": { const raw = typeof n.값 === "number" || typeof n.값 === "string" ? String(n.값) : ""; return /^-?\d+(\.\d+)?(e-?\d+)?$/i.test(raw) ? raw : (n.글 ?? "").replace(/,/g, ""); } case "글": return n.따옴 ? `'${n.글 ?? ""}'` : (n.글 ?? ""); case "변수": return n.갈래 === "입력" || n.갈래 === "중간" ? (n.열쇠 ?? "") : n.입력글 || n.식 || n.열쇠 || ""; case "블록": return n.입력글 || n.식 || n.열쇠 || ""; case "연산": { const right = n.우?.꼴 === "분수" || n.우?.꼴 === "퍼센트"; return `${t(n.좌)} ${n.기호 ?? ""} ${wrap(t(n.우), right)}`; } case "분수": { const top = n.위 as EqNode; const low = n.아래 as EqNode; const head = wrap(t(top), isOp(top) && !["×", "÷"].includes(top.기호 ?? "")); if (n.사슬 && low?.꼴 === "연산") return `${head} / ${wrap(t(low.좌), isOp(low.좌))} / ${wrap(t(low.우), isOp(low.우))}`; return `${head} / ${wrap(t(low), isOp(low) || low?.꼴 === "분수")}`; } case "괄호": return `(${t(n.안)})`; case "퍼센트": return `${t(n.안)} / 100`; case "음": return `-${t(n.안)}`; case "조건": return `만약(${t(n.조건)}, ${t(n.참)}, ${t(n.거짓)})`; case "함수": return `${n.이름 ?? ""}(${(n.인자 ?? []).map((a) => t(a)).join(", ")})`; default: return n.글 ?? ""; } } /** 보이는 잎 차례 — 값 식 그림(`renderEq`)의 수 · 글 차례와 같음 */ export function leavesOf(n: EqNode | undefined, out: EqNode[] = []): EqNode[] { if (!n) return out; const go = (x: EqNode | string | undefined): void => { if (x && typeof x !== "string") leavesOf(x, out); }; switch (n.꼴) { case "수": case "글": case "변수": case "블록": out.push(n); break; case "조건": { const pick = taken(n); if (pick) go(pick); else [n.조건, n.참, n.거짓].forEach(go); break; } case "함수": (HIDDEN.has(n.이름 ?? "") ? (n.인자 ?? []).slice(0, 1) : (n.인자 ?? [])).forEach(go); break; default: [n.좌, n.우, n.위, n.아래, n.안].forEach(go); } return out; }