Merge remote-tracking branch 'origin/dev' into main_desktop_1
# Conflicts: # M01_MasterData/M01_MasterData_UI_LogicLab.ts # M01_MasterData/M01_MasterData_UI_LogicLab_Style.css # M01_MasterData/M01_MasterData_UI_Logic_Api.ts # resources/master_data/ref/_검증_별칭_복사.md
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""줄로 적은 식 → 변수 뽑기 · 검사 · 단위 맞춤 · 로직 틀로 옮기기 (`_화면_계약.md` 7장).
|
||||
"""줄로 적은 식 → 변수 뽑기 · 검사 · 단위 맞춤 · 로직 틀로 옮기기 (`_화면_계약.md` 8장).
|
||||
|
||||
수량 = 길이 * 폭
|
||||
자재비 = 수량 * 시멘트
|
||||
|
||||
· 줄마다 「이름 = 식」 · 빈 줄과 `#` 는 건너뜀 · 마지막 줄 왼쪽이 결과 변수
|
||||
· 줄마다 「이름 = 식」 또는 「식 = 이름」 · 빈 줄과 `#` 는 건너뜀 · 마지막 줄의 값 이름이 결과 변수
|
||||
· 식 글자는 `_틀.md` 8장 범위만 — 읽기는 `master_formula` 가 함
|
||||
· 표 찾기는 글자로 치지 않음 — 변수 하나로 두고 「무엇」 을 `표찾기` 로 정할 때 이음
|
||||
· 값은 안 씀 — 검사만 하고 `옮기기` 가 로직 한 줄을 만듦(저장은 `Store_Make.logic_new`)
|
||||
@@ -32,8 +32,9 @@ ALIAS = {
|
||||
"M": ("m", 1),
|
||||
}
|
||||
SQUARED = {2: "㎡", 3: "㎥"}
|
||||
DIMLESS = ("%", "할") # 비율은 단위가 없음 — 「1 + 증가율 / 100」 이 안 걸리게
|
||||
_ASSIGN = re.compile(r"^\s*([\w가-힣]+)\s*=(?!=)\s*(.*)$")
|
||||
# 단위가 없는 맨 수 — 횟수 · 배율 · 비율. 빈 글도 같음(「없음」 을 고른 것과 같게 봄)
|
||||
DIMLESS = ("%", "할", "없음", "맨수", "맨 수", "-")
|
||||
_NAME_ONLY = re.compile(r"^[\w가-힣]+$")
|
||||
|
||||
|
||||
# ── 자리를 적어 두는 읽기 ──────────────────────────────────────────────
|
||||
@@ -224,18 +225,51 @@ def find_text(pick: dict) -> str:
|
||||
|
||||
|
||||
# ── 읽기 · 검사 ───────────────────────────────────────────────────────
|
||||
def _is_name(text: str) -> bool:
|
||||
"""값 이름 하나 — 맨 수는 이름이 아님."""
|
||||
return bool(_NAME_ONLY.fullmatch(text)) and not mf._NUMBER.fullmatch(text)
|
||||
|
||||
|
||||
def _split(raw: str) -> int:
|
||||
"""줄을 가를 등호 자리 — 괄호 안과 비교 기호(`==` `<=` `>=` `!=`)는 건너뜀 · 없으면 -1."""
|
||||
depth = 0
|
||||
for i, ch in enumerate(raw):
|
||||
if ch in "([{":
|
||||
depth += 1
|
||||
elif ch in ")]}":
|
||||
depth -= 1
|
||||
elif ch == "=" and depth == 0:
|
||||
if raw[i - 1 : i] in ("<", ">", "!", "=") or raw[i + 1 : i + 2] == "=":
|
||||
continue
|
||||
return i
|
||||
return -1
|
||||
|
||||
|
||||
def _cut(text: str):
|
||||
"""(줄 번호, 왼쪽 이름, 식, 식이 시작하는 자리) — 빈 줄과 `#` 는 건너뜀."""
|
||||
"""(줄 번호, 이름, 식, 식이 시작하는 자리, 까닭) — 빈 줄과 `#` 는 건너뜀.
|
||||
|
||||
「이름 = 식」 과 「식 = 이름」 을 다 받음 — 한쪽이 값 이름 하나면 그쪽이 이름.
|
||||
"""
|
||||
for at, raw in enumerate(str(text).split("\n"), 1):
|
||||
if not raw.strip() or raw.lstrip().startswith("#"):
|
||||
continue
|
||||
m = _ASSIGN.match(raw)
|
||||
yield (
|
||||
at,
|
||||
(m.group(1) if m else ""),
|
||||
(m.group(2).strip() if m else raw.strip()),
|
||||
(m.start(2) if m else len(raw) - len(raw.lstrip())),
|
||||
)
|
||||
cut = _split(raw)
|
||||
if cut < 0:
|
||||
yield (
|
||||
at,
|
||||
"",
|
||||
raw.strip(),
|
||||
len(raw) - len(raw.lstrip()),
|
||||
"「이름 = 식」 이 아님 — 등호가 없음",
|
||||
)
|
||||
continue
|
||||
left, right = raw[:cut], raw[cut + 1 :]
|
||||
if _is_name(left.strip()):
|
||||
yield at, left.strip(), right.strip(), cut + 1 + len(right) - len(right.lstrip()), ""
|
||||
elif _is_name(right.strip()): # 「A * B = C」 — 오른쪽이 이름
|
||||
yield at, right.strip(), left.strip(), len(left) - len(left.lstrip()), ""
|
||||
else:
|
||||
yield at, "", raw.strip(), cut, "양쪽이 다 식 — 한쪽은 값 이름 하나여야 함"
|
||||
|
||||
|
||||
def _order(defined: dict, deps: dict) -> list[str]:
|
||||
@@ -255,14 +289,14 @@ def _order(defined: dict, deps: dict) -> list[str]:
|
||||
return out
|
||||
|
||||
|
||||
def _scan(text: str, 정함: dict | None = None) -> dict:
|
||||
def _scan(text: str, 정함: dict | None = None, 머리: dict | None = None) -> dict:
|
||||
"""줄 가르기 · 변수 · 검사 · 단위 — `read` 와 `옮기기` 가 같이 씀(나무를 들고 있음)."""
|
||||
정함 = 정함 or {}
|
||||
lines, 문제 = [], []
|
||||
for at, name, body, head in _cut(text):
|
||||
for at, name, body, head, why in _cut(text):
|
||||
one = {"줄": at, "이름": name, "식": body, "앞": head}
|
||||
if not name:
|
||||
문제.append(_trouble(at, head, "못읽음", "「이름 = 식」 이 아님"))
|
||||
if why:
|
||||
문제.append(_trouble(at, head, "못읽음", why))
|
||||
elif not body:
|
||||
문제.append(_trouble(at, head, "못읽음", f"「{name}」 오른쪽이 빔"))
|
||||
else:
|
||||
@@ -343,6 +377,7 @@ def _scan(text: str, 정함: dict | None = None) -> dict:
|
||||
got = _unit(one["나무"], units, one["줄"], one["앞"], 문제)
|
||||
units[name] = pinned if pinned is not None else got
|
||||
|
||||
덧줄후보 = _extra_names(by, 결과, 정함, 머리) if 결과 in by and 결과 not in loops else set()
|
||||
변수 = []
|
||||
for name in sorted(
|
||||
set(쓰임) | set(정의), key=lambda n: (min([쓰임.get(n, [10**9])[0], 정의.get(n, 10**9)]), n)
|
||||
@@ -359,16 +394,18 @@ def _scan(text: str, 정함: dict | None = None) -> dict:
|
||||
"무엇": spec.get("무엇") if 갈래 == "미정" else None,
|
||||
"단위": unit_text(units.get(name)),
|
||||
"비목": spec.get("비목"),
|
||||
"정할것": _todo(갈래, spec, units.get(name)),
|
||||
"정할것": _todo(갈래, spec, units.get(name), name in 덧줄후보),
|
||||
}
|
||||
)
|
||||
return {"줄": lines, "변수": 변수, "결과": 결과, "문제": 문제, "차례": order, "정의": 정의}
|
||||
|
||||
|
||||
def _todo(갈래: str, spec: dict, unit) -> list[str]:
|
||||
def _todo(갈래: str, spec: dict, unit, 덧줄: bool = False) -> list[str]:
|
||||
"""그 변수에 아직 안 정한 것 — 무엇 · 요소·찾기·값 · 단위 · 비목."""
|
||||
if 갈래 != "미정":
|
||||
if 갈래 == "비목합":
|
||||
return []
|
||||
if 갈래 == "정의됨": # 식이 있는 값 — 덧줄이 될 항이면 비목만 고르면 됨
|
||||
return ["비목"] if 덧줄 and spec.get("비목") not in COST_ITEMS else []
|
||||
what, need = spec.get("무엇"), []
|
||||
if what not in WHAT:
|
||||
need.append("무엇")
|
||||
@@ -386,9 +423,9 @@ def _todo(갈래: str, spec: dict, unit) -> list[str]:
|
||||
return need
|
||||
|
||||
|
||||
def read(text: str, 정함: dict | None = None) -> dict:
|
||||
def read(text: str, 정함: dict | None = None, 머리: dict | None = None) -> dict:
|
||||
"""줄로 적은 식 → `{줄, 변수, 결과, 문제, 다됨}` — 화면이 그대로 받는 모양(나무는 뺌)."""
|
||||
got = _scan(text, 정함)
|
||||
got = _scan(text, 정함, 머리)
|
||||
다됨 = not got["문제"] and not any(v["정할것"] for v in got["변수"])
|
||||
return {
|
||||
"줄": [{k: v for k, v in one.items() if k != "나무"} for one in got["줄"]],
|
||||
@@ -408,22 +445,40 @@ def _factors(node) -> list:
|
||||
return _factors(node[2]) + _factors(node[3]) if node[0] == "bin" and node[1] == "*" else [node]
|
||||
|
||||
|
||||
def _spread(node, by: dict, 정함: dict, 편: set) -> list:
|
||||
"""결과 항 — 항이 이름 하나이고 비목을 안 정했으면 그 식으로 펼침.
|
||||
def _spread(node, by: dict, 정함: dict, 편: set, 뿌리: str = "") -> list:
|
||||
"""결과 항 — (항, 펼치기 전 이름). 항이 이름 하나이고 비목을 안 정했으면 그 식으로 펼침.
|
||||
|
||||
호표 줄을 항 안에서 찾으려는 것.
|
||||
호표 줄을 항 안에서 찾으려는 것 · 못 펼친 항의 `뿌리` 가 덧줄이 될 값 이름.
|
||||
"""
|
||||
out = []
|
||||
for term in _terms(node):
|
||||
name = term[1] if term[0] == "name" else ""
|
||||
if name in by and name not in 편 and not (정함.get(name) or {}).get("비목"):
|
||||
편.add(name)
|
||||
out += _spread(by[name]["나무"], by, 정함, 편)
|
||||
out += _spread(by[name]["나무"], by, 정함, 편, name)
|
||||
else:
|
||||
out.append(term)
|
||||
out.append((term, 뿌리))
|
||||
return out
|
||||
|
||||
|
||||
def _has_element(term, 정함: dict) -> bool:
|
||||
return any(
|
||||
f[0] == "name" and (정함.get(f[1]) or {}).get("무엇") == "마스터요소"
|
||||
for f in _factors(term)
|
||||
)
|
||||
|
||||
|
||||
def _extra_names(by: dict, 결과: str, 정함: dict, 머리: dict | None) -> set[str]:
|
||||
"""결과 식의 항 가운데 마스터요소가 없어 덧줄이 될 값 이름 — 비목을 골라야 함."""
|
||||
if 머리 is not None and not str(머리.get("결과단위") or "").startswith("원"):
|
||||
return set() # 돈 아닌 로직은 덧줄이 없음
|
||||
return {
|
||||
뿌리
|
||||
for term, 뿌리 in _spread(by[결과]["나무"], by, 정함, set())
|
||||
if 뿌리 and not _has_element(term, 정함)
|
||||
}
|
||||
|
||||
|
||||
def _kind_of(요소) -> str:
|
||||
if isinstance(요소, dict):
|
||||
return "재료"
|
||||
@@ -447,13 +502,16 @@ def 옮기기(text: str, 정함: dict | None = None, 머리: dict | None = None)
|
||||
돈 아닌 로직은 호표 없이 `중간` + `결과` 식.
|
||||
"""
|
||||
정함, 머리 = 정함 or {}, dict(머리 or {})
|
||||
got = _scan(text, 정함)
|
||||
got = _scan(text, 정함, 머리)
|
||||
문제 = list(got["문제"])
|
||||
for v in got["변수"]:
|
||||
if v["정할것"]:
|
||||
문제.append(
|
||||
_trouble(
|
||||
v["처음"], 0, "덜정함", f"「{v['이름']}」 에 {' · '.join(v['정할것'])} 안 정함"
|
||||
v["처음"],
|
||||
0,
|
||||
"덜정함",
|
||||
f"「{v['이름']}」 에 아직 안 정한 것 — {' · '.join(v['정할것'])}",
|
||||
)
|
||||
)
|
||||
if 문제:
|
||||
@@ -475,7 +533,7 @@ def 옮기기(text: str, 정함: dict | None = None, 머리: dict | None = None)
|
||||
|
||||
덧줄이름, 편 = set(), set()
|
||||
if 돈:
|
||||
for term in _spread(by[결과]["나무"], by, 정함, 편):
|
||||
for term, 뿌리 in _spread(by[결과]["나무"], by, 정함, 편):
|
||||
facts = _factors(term)
|
||||
picks = [
|
||||
f
|
||||
@@ -520,6 +578,15 @@ def 옮기기(text: str, 정함: dict | None = None, 머리: dict | None = None)
|
||||
{"이름": name, "식": _expr(by[name]["나무"]), "비목": 정함[name]["비목"]}
|
||||
)
|
||||
덧줄이름.add(name)
|
||||
elif 뿌리 in by: # 덧줄이 될 값 — 막지 않고 비목만 짚음
|
||||
문제.append(
|
||||
_trouble(
|
||||
by[뿌리]["줄"],
|
||||
0,
|
||||
"비목",
|
||||
f"덧줄 「{뿌리}」 의 비목을 골라 주세요 — {' · '.join(COST_ITEMS)}",
|
||||
)
|
||||
)
|
||||
else:
|
||||
문제.append(
|
||||
_trouble(
|
||||
|
||||
Reference in New Issue
Block a user