어셈블리 문장의 구성 문서의 검증 코드다. if __name__ == "__main__": 아래가 자체 테스트다.

"""어셈블리 문장의 구성 검증 (MASM이 없어 파이썬으로 규칙을 흉내 냈다).

문서: 08.어셈블리 문장의 구성
주장:
  1. 정수 상수: 30d = 30, 6Ah = 106, 42 = 42, 1101b = 13, 0A5h = 165. 꼬리 글자가 진법을 정한다.
  2. A5h는 글자로 시작해서 식별자 규칙에 걸린다. 그래서 앞에 0을 붙여 0A5h로 쓴다.
  3. 정수 식: 16 / 5 = 3, -(3 + 4) * (6 - 1) = -35, -3 + 4 * 6 - 1 = 20, 25 mod 3 = 1.
  4. 식별자: 1~247자, 첫 글자는 글자·_·@·?·$, 대소문자를 가리지 않는다.
  5. 문장 가르기: [레이블:] 니모닉 [피연산자] [; 주석]. 문서의 여섯 문장.
  6. add eax,36 * 25 의 상수식은 900.
"""
import re

RADIX = {"h": 16, "d": 10, "b": 2}


def is_identifier(s):
    return re.fullmatch(r"[A-Za-z_@?$][A-Za-z0-9_@?$]{0,246}", s) is not None


def parse_int(tok):
    """MASM 정수 상수. 숫자로 시작해야 한다. 꼬리 글자 h/d/b가 없으면 10진수."""
    t = tok.lower()
    sign = 1
    if t[0] in "+-":
        sign = -1 if t[0] == "-" else 1
        t = t[1:]
    if not t[0].isdigit():
        raise ValueError("숫자로 시작하지 않는다: " + tok)
    if t[-1] == "h":
        return sign * int(t[:-1], 16)
    if t[-1] in "bd" and all(c in "01" for c in t[:-1]) and t[-1] == "b":
        return sign * int(t[:-1], 2)
    if t[-1] == "d":
        return sign * int(t[:-1], 10)
    return sign * int(t, 10)


def trunc_div(a, b):
    q = abs(a) // abs(b)
    return q if (a >= 0) == (b >= 0) else -q


def eval_expr(text):
    """우선순위: 괄호 > 단항 +,- > *, /, MOD > 이항 +,-"""
    toks = re.findall(r"\d+[A-Za-z0-9]*|mod|[()+\-*/]", text.lower())
    pos = [0]

    def peek():
        return toks[pos[0]] if pos[0] < len(toks) else None

    def take():
        pos[0] += 1
        return toks[pos[0] - 1]

    def primary():
        t = take()
        if t == "(":
            v = add()
            assert take() == ")"
            return v
        return parse_int(t)

    def unary():
        if peek() in ("+", "-"):
            return -unary() if take() == "-" else unary()
        return primary()

    def mul():
        v = unary()
        while peek() in ("*", "/", "mod"):
            op = take()
            r = unary()
            if op == "*":
                v = v * r
            elif op == "/":
                v = trunc_div(v, r)
            else:
                v = v - trunc_div(v, r) * r
        return v

    def add():
        v = mul()
        while peek() in ("+", "-"):
            v = v + mul() if take() == "+" else v - mul()
        return v

    v = add()
    assert pos[0] == len(toks)
    return v


def split_statement(line):
    """(레이블, 니모닉, 피연산자 목록, 주석)"""
    comment = None
    if ";" in line:
        line, comment = line.split(";", 1)
        comment = comment.strip()
    line = line.strip()
    label = None
    m = re.match(r"([A-Za-z_@?$][A-Za-z0-9_@?$]*):\s*(.*)", line)
    if m:
        label, line = m.group(1), m.group(2)
    if not line:
        return label, None, [], comment
    parts = line.split(None, 1)
    mnem = parts[0]
    ops = [o.strip() for o in parts[1].split(",")] if len(parts) > 1 else []
    return label, mnem, ops, comment


def main():
    assert [parse_int(t) for t in ("30d", "6Ah", "42", "1101b", "0A5h")] == [30, 106, 42, 13, 165]
    print("[OK] 정수 상수: 30d, 6Ah, 42, 1101b, 0A5h = 30, 106, 42, 13, 165")

    assert is_identifier("A5h") and not is_identifier("0A5h")
    try:
        parse_int("A5h")
        raise AssertionError
    except ValueError:
        pass
    print("[OK] A5h는 식별자 모양이라 상수로 읽히지 않는다. 0A5h는 상수다")

    cases = {"16 / 5": 3, "-(3 + 4) * (6 - 1)": -35, "-3 + 4 * 6 - 1": 20, "25 mod 3": 1}
    for text, want in cases.items():
        assert eval_expr(text) == want, text
    print("[OK] 정수 식 네 개: 3, -35, 20, 1")

    assert all(is_identifier(s) for s in ("myArray", "L1", "_main", "@count", "?x", "$first", "val1"))
    assert not any(is_identifier(s) for s in ("1stValue", "my-var", "", "a b"))
    assert is_identifier("a" * 247) and not is_identifier("a" * 248)
    assert "MyArray".lower() == "myarray"
    print("[OK] 식별자 규칙: 첫 글자, 길이 1~247")

    p13 = {
        "stc ; set Carry flag": (None, "stc", [], "set Carry flag"),
        "inc eax ; register": (None, "inc", ["eax"], "register"),
        "inc myByte ; memory": (None, "inc", ["myByte"], "memory"),
        "add ebx,ecx ; register, register": (None, "add", ["ebx", "ecx"], "register, register"),
        "sub myByte,25 ; memory, constant": (None, "sub", ["myByte", "25"], "memory, constant"),
        "add eax,36 * 25 ; register, constant-expression":
            (None, "add", ["eax", "36 * 25"], "register, constant-expression"),
    }
    for line, want in p13.items():
        assert split_statement(line) == want, line
    assert [len(v[2]) for v in p13.values()] == [0, 1, 1, 2, 2, 2]
    assert eval_expr("36 * 25") == 900
    print("[OK] 여섯 문장: 피연산자 0, 1, 1, 2, 2, 2개. 36 * 25 = 900")

    assert split_statement("L1: add ax,cx   ; 더한다") == ("L1", "add", ["ax", "cx"], "더한다")
    assert split_statement("target: mov eax,5") == ("target", "mov", ["eax", "5"], None)
    # 카드
    assert eval_expr("-5 + 3 * 4 mod 5") == -3 and eval_expr("(2 + 3) * 4 / 3") == 6
    assert parse_int("0FFh") == 255 and parse_int("11111111b") == 255 and parse_int("255d") == 255
    print("[OK] 레이블이 붙은 문장, 카드 값(-3, 6, 255)")
    print("ALL CHECKS PASSED")


if __name__ == "__main__":
    main()