wip(801-1): 수식 그 자리 고침 · 한 마디 줄 · 로직 부름 기호 · 반올림 · 안 탄 갈래 숨김 — 요구 664 로 멈춤(메인 안 합침)

- 서버: 한 마디 줄(표 찾기 · 요소 · 로직 부름 하나) = 줄 기호 · 「적용 · 값 · 기준」 칸 글 대신 조건 값 기호 · 로직 부름 기호(master_equation_sign) · 값 식 숫자 단위 · 있는 계산 변수 연결 바꿈 = 그 줄 식 · 식 속 원자 키보드 글(입력글)
- 화면: 박스 없는 조판 · 반올림 감싸개 · 안 탄 만약 숨김(감싸개 글로 구조 지킴) · 보이는 식 그 자리 고침(contenteditable · 원자 data-tok) · 값 숫자 그 자리 고침(valueEdit)
- 시험 test_801_1 7 통과 · 넓은 왕복 199 로직 중 13 걸림(식 300 자 상한 · 요소 EQ/MO 글 · 표 칸 Q 기호 맞물림) · test_796_2 옛 createFormulaCell 기대 안 고침(멈춤)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-07 17:50:36 +09:00
co-authored by Claude Opus 5.5
parent 90478a43c3
commit 43784a4c09
13 changed files with 784 additions and 96 deletions
@@ -28,6 +28,7 @@ from pathlib import Path
import master_auto as ma
import master_cut as mcut
import master_equation_sign as msign
import master_equation_view as view
import master_formula as mf
import master_material as mm
@@ -345,7 +346,8 @@ def values(tree: dict) -> dict:
if kind in ("변수", "블록"):
value = tree.get("값")
got = _num(value) if value is not None else {"꼴": "글", "글": "?"}
return {**got, "열쇠": tree.get("열쇠"), "갈래": tree.get("갈래") or "블록"}
unit = tree.get("단위") or tree.get("결과단위") or "" # 값 식 숫자 단위(801-4)
return {**got, "열쇠": tree.get("열쇠"), "갈래": tree.get("갈래") or "블록", "단위": unit}
if kind == "조건":
if tree.get("걸림"):
return values(tree["참" if tree["걸림"] == 1 else "거짓"])
@@ -462,7 +464,7 @@ def export(master, key: str, given: dict | None = None, depth: int = 0) -> dict:
total = {k: cut.get(k) for k in mf.BUNDLE}
if depth == 0:
sub = {"소계": subtotals(row, cut, total)}
out = msym.apply( # 기호 먼저(800-1) → 정의는 기호 씌운 줄로(800-2)
out = msym.apply( # 기호 먼저(800-1) → 한 마디 줄 · 로직 부름 기호(801) → 정의(800-2)
{
"열쇠": row.get("키"),
"이름": row.get("이름") or "",
@@ -474,6 +476,7 @@ def export(master, key: str, given: dict | None = None, depth: int = 0) -> dict:
**sub,
}
)
msign.settle(out)
if depth == 0:
out["정의"] = view.definitions(out_lines, row, out["변수표"], _unused(row))
return _clean(out)
@@ -414,7 +414,13 @@ def apply(master, row: dict, edits: list, words=None) -> tuple[dict, list[str],
_show(new, key, ed, why)
elif job == "연결":
add, bad = link.plan(master, ed)
_add(new, add, why, spots, words) if add else why.append(bad)
mid = key in {str(s.get("이름")) for s in new.get("중간") or []}
if add and mid and add.get("식"): # 있는 계산 변수 연결 바꿈(801) = 그 줄 식
_set_formula(new, {"자리": f"중간:{key}", "식": add["식"]}, why, spots, words)
elif add and mid:
why.append(f"「{key}」 계산 변수는 입력으로 못 바꿈 — 표 · 요소 · 로직 · 상수")
else:
_add(new, add, why, spots, words) if add else why.append(bad)
else:
why.append(f"모르는 할일 「{job}」")
except (mf.FormulaError, KeyError, TypeError, ValueError) as e:
@@ -0,0 +1,99 @@
# -*- coding: utf-8 -*-
"""수식 마디 기호 다듬기(PLAN 801-1 · 801-2 · 요구 662-4) — 기호 씌운(`master_symbol.apply`) 뒤 · 정의 영역 앞.
보이기만 — 셈 · 엔진 열쇠 · 식(`식` 칸) 안 바꿈.
- 한 마디 줄: 중간 · 결과 줄 식이 표 찾기 · 요소 · 로직 부르기 한 마디뿐이면 그 마디 = 줄 기호 · 줄 `한마디`
(화면은 「q = 0.7」 — 표 칸 글 「적용」 · 로직 이름은 정의 영역에만).
- 로직 부르기 마디 = 기호(부른 로직 결과 기호 + 아래 = 로직 이름 앞말) — 이름은 정의 영역 · 펼침 머리.
- 뜻 없는 표 칸 글(`PLAIN` — 적용 · 값 · 기준)로 지은 표 찾기 기호 = 그 행 조건 값(없으면 표 이름)으로.
- 한 로직 안 겹침 = 아래첨자 끝 숫자(`master_symbol.apply` 와 같은 꼴).
"""
from __future__ import annotations
import master_symbol as msym
PLAIN = {"적용", "값", "기준"}
LEAF = ("찾기", "요소")
def _sign(n: dict) -> str:
return f"{n.get('기호') or ''}_{n.get('아래') or ''}^{n.get('위') or ''}"
def _nodes(tree, out: list[dict]) -> list[dict]:
"""변수 · 블록 마디(블록 속 나무는 따로 다듬음 — 안 들어감)."""
if isinstance(tree, list):
for one in tree:
_nodes(one, out)
elif isinstance(tree, dict):
if tree.get("꼴") in ("변수", "블록"):
out.append(tree)
for k, v in tree.items():
if k != "나무" and isinstance(v, (dict, list)):
_nodes(v, out)
return out
def lone(line: dict) -> bool:
"""줄 식이 표 찾기 · 요소 · 로직 부르기 한 마디뿐인 중간 · 결과 줄."""
tree = line.get("이름식") or {}
one = tree.get("꼴") == "블록" or (tree.get("꼴") == "변수" and tree.get("갈래") in LEAF)
return line.get("갈래") in ("중간", "결과") and one and isinstance(line.get("결과"), dict)
def _block_sign(node: dict) -> tuple[str, str, str]:
sub = node.get("나무") or {}
last = next((x for x in reversed(sub.get("줄") or []) if x.get("갈래") == "결과"), None)
head = (last or {}).get("결과") or {}
sign = head.get("기호") if msym.is_symbol(str(head.get("기호") or "")) else ""
sign = sign or msym.rule(str(node.get("결과단위") or ""), "결과")[0]
return sign, msym.short(str(node.get("이름") or node.get("열쇠") or "")), ""
def _find_sign(node: dict) -> tuple[str, str, str]:
said = [str(v) for v in (node.get("행") or {}).values() if isinstance(v, str) and v]
name = said[-1] if said else str(node.get("표이름") or "")
node["칸글"] = True # 정의 이름 = 표 · 조건 값(칸 글 아님)
node["이름"] = " ".join(x for x in (str(node.get("표이름") or ""), *said[-1:]) if x)
if msym.is_symbol(name):
return name, "", "표"
sign, low, _ = msym.lookup(name, "찾기")
return sign, low, "표"
def settle(out: dict) -> dict:
"""내보낸 수식 한 벌(한 깊이) 다듬기 — 제자리 고침 · 같은 것 돌려줌."""
lines = out.get("줄") or []
for line in lines:
if lone(line):
line["한마디"] = True
head = line["결과"]
line["이름식"] |= {k: head.get(k) or "" for k in ("기호", "아래", "위")}
nodes = _nodes(lines, [])
heads = [x["결과"] for x in lines if isinstance(x.get("결과"), dict)]
used = {_sign(n) for n in [*nodes, *heads, *(out.get("변수표") or [])]}
done: dict[str, tuple[str, str, str]] = {}
for line in lines:
skip = line["이름식"] if line.get("한마디") else None
for node in _nodes(line.get("이름식"), []):
if node is skip:
continue
if node.get("꼴") == "블록":
want = _block_sign
elif node.get("갈래") == "찾기" and node.get("칸") in PLAIN:
want = _find_sign
else:
continue
key = str(node.get("식") or node.get("열쇠"))
if key not in done:
used.discard(_sign(node))
sign, low, up = want(node)
n, base = 1, low
while _sign({"기호": sign, "아래": low, "위": up}) in used:
n += 1
low = base[: msym.SUBMAX - len(str(n))] + str(n)
used.add(_sign({"기호": sign, "아래": low, "위": up}))
done[key] = (sign, low, up)
node["기호"], node["아래"], node["위"] = done[key]
return out
@@ -76,8 +76,8 @@ def definitions(lines: list[dict], row: dict, rows: list[dict], unused) -> list[
leaf[str(line.get("열쇠"))] = {"갈래": "상수", "글": tree.get("글")}
elif tree.get("갈래") in ("찾기", "요소") or tree.get("꼴") == "블록": # 한 마디 식
leaf[str(line.get("열쇠"))] = tree
found = ([head] if head and line.get("갈래") in ("중간", "결과") else []) + visible(
tree, []
found = ([head] if head and line.get("갈래") in ("중간", "결과") else []) + (
[] if line.get("한마디") else visible(tree, []) # 한 마디 줄 = 줄 변수 하나(801)
)
for node in found:
key = str(node.get("열쇠") or "")
@@ -97,7 +97,7 @@ def definitions(lines: list[dict], row: dict, rows: list[dict], unused) -> list[
if kind == "블록" and not leaf.get(key):
link = "로직 부르기" # 이름이 곧 그 로직 — 되풀이 안 함
name = (look.get("이름") or key) if mine else look.get("이름") or ""
name = node.get("칸") or name if kind == "찾기" else name
name = node.get("칸") or name if kind == "찾기" and not node.get("칸글") else name
one = {
"열쇠": key if mine else "",
"기호": look.get("기호") or name, # 줄 참조도 씌운 기호(master_symbol 한 곳)