- 남김: San_Est.tokens · num · NUMRE(NativeEst 가 씀) · San_Eval.run_stmate · stmate_block(STC · STmate) · San_Emit.stmate - 시험 고침: 802_4b1 · 797_1 = 세 줄 → 묶음 한 줄(원파일 길 꼴) · 797_2 = 블록 번은 엑셀 둘 꼴에서 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
92 lines
3.0 KiB
Python
92 lines
3.0 KiB
Python
"""EST 식 글 토큰(PLAN 793-1 · 812-1 · eval 없음) — 원파일 산근 변환(`M02_Doc_NativeEst`)이 쓰는 수 · 토큰 읽기만.
|
||
|
||
옛 엑셀 글 추정 사슬(줄 꼴 · 역할 · 값 역색인 · `parse_block`)은 812-1 엑셀 읽기기(`M02_San_Xlsx`)로 바뀌어 걷음.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import re
|
||
|
||
from M02_MasterTemplete import M02_San_Expr as ex
|
||
|
||
NUMRE = r"\d{1,3}(?:,\d{3})+(?:\.\d*)?|\d+(?:\.\d*)?|\.\d+"
|
||
EPS = ex.EPS
|
||
OPS = {"×": "*", "x": "*", "X": "*", "*": "*", "÷": "/", "/": "/", "+": "+", "-": "-"}
|
||
OPS |= {"-": "-", "−": "-"}
|
||
_UNIT = re.compile(r"\s*(?:[A-Za-z가-힣㎡㎥㎏ℓ㎝㎜°˚’'\"Øø]+(?:\s*/\s*[A-Za-z가-힣㎡㎥㎏ℓ]+)?)")
|
||
|
||
|
||
def num(s: str) -> float:
|
||
return float(s.replace(",", "").rstrip(".") or 0)
|
||
|
||
|
||
def tokens(seg: str) -> list[tuple] | None:
|
||
"""식 조각 → Expr 토큰 · 숫자 식이 아니면(기호 · 수 둘 잇달아 · 연산 없음) None."""
|
||
s = seg.replace("(", "(").replace(")", ")").replace("x", "x")
|
||
prev = None
|
||
while prev != s: # 수 없는 괄호 글 「(품할증)」
|
||
prev = s
|
||
s = re.sub(r"\([^()\d]*\)", " ", s)
|
||
out: list[tuple] = []
|
||
i, want, depth = 0, True, 0
|
||
while i < len(s):
|
||
ch = s[i]
|
||
if ch.isspace():
|
||
i += 1
|
||
continue
|
||
m = re.match(NUMRE, s[i:])
|
||
if m and (i == 0 or not (s[i - 1].isalpha() and s[i - 1].isascii())):
|
||
if not want:
|
||
return None
|
||
out.append(("수", num(m.group(0))))
|
||
i += len(m.group(0))
|
||
want = False
|
||
while i < len(s): # 단위 글 「인」 「㎡」 「본/일」
|
||
m2 = _UNIT.match(s[i:])
|
||
if m2 and m2.group(0).strip() and not re.fullmatch(r"\s*[xX]", m2.group(0)):
|
||
i += len(m2.group(0))
|
||
continue
|
||
break
|
||
continue
|
||
if ch == "%":
|
||
if want:
|
||
return None
|
||
out.append(("%",))
|
||
i += 1
|
||
continue
|
||
if ch in OPS:
|
||
if ch in "xX" and want:
|
||
return None
|
||
if ch == "/" and not re.match(r"\s*[\d(.]", s[i + 1 :]):
|
||
return None
|
||
if ch in "--−" and want:
|
||
out.append(("연산", "-"))
|
||
i += 1
|
||
continue
|
||
if want:
|
||
return None
|
||
out.append(("연산", OPS[ch]))
|
||
i += 1
|
||
want = True
|
||
continue
|
||
if ch == "(":
|
||
if not want:
|
||
return None
|
||
depth += 1
|
||
out.append(("(",))
|
||
i += 1
|
||
continue
|
||
if ch == ")":
|
||
depth -= 1
|
||
if depth < 0 or want:
|
||
return None
|
||
out.append((")",))
|
||
i += 1
|
||
continue
|
||
if ch.isalpha() or ch == "_":
|
||
return None
|
||
i += 1 # 장식(₩ 등)
|
||
if depth != 0 or want or not any(t[0] in ("연산", "%") for t in out):
|
||
return None
|
||
return out
|