LL(1) 파싱 표 예제 사다리 문서의 문제 4 풀이 코드다. if __name__ == "__main__": 아래가 자체 테스트다.

"""LL(1) 파싱 표 예제 사다리의 답을 확인한다.

실행: python3 20_ll1-table-ladder_p4.py  (표준 라이브러리만 쓴다. 다른 파일을 불러오지 않는다.)
"""


def G(text):
    """'A -> x B | ε' 꼴의 줄들을 {비단말: [생성 규칙(기호 튜플), ...]}로 바꾼다. ε은 빈 튜플."""
    g = {}
    for line in text.strip().splitlines():
        head, body = line.split("->")
        alts = g.setdefault(head.strip(), [])
        for alt in body.split("|"):
            syms = tuple(s for s in alt.split() if s != "ε")
            alts.append(syms)
    return g


def show(g):
    return {A: [" ".join(p) or "ε" for p in ps] for A, ps in g.items()}


EPS = "ε"
END = "$"


def first_of(seq, first, g):
    """기호열 seq의 FIRST. first는 비단말별 FIRST 집합."""
    out = set()
    for s in seq:
        if s not in g:
            out.add(s)
            return out
        out |= first[s] - {EPS}
        if EPS not in first[s]:
            return out
    out.add(EPS)
    return out


def first_sets(g):
    """더 늘어나지 않을 때까지 규칙을 되풀이해 모든 비단말의 FIRST를 구한다."""
    first = {A: set() for A in g}
    changed = True
    while changed:
        changed = False
        for A, prods in g.items():
            for p in prods:
                f = first_of(p, first, g)
                if not f <= first[A]:
                    first[A] |= f
                    changed = True
    return first


def follow_sets(g, start, first):
    """더 늘어나지 않을 때까지 세 규칙을 되풀이해 모든 비단말의 FOLLOW를 구한다."""
    follow = {A: set() for A in g}
    follow[start].add(END)
    changed = True
    while changed:
        changed = False
        for B, prods in g.items():
            for p in prods:
                for i, A in enumerate(p):
                    if A not in g:
                        continue
                    fb = first_of(p[i + 1:], first, g)
                    add = fb - {EPS}
                    if EPS in fb:
                        add |= follow[B]
                    if not add <= follow[A]:
                        follow[A] |= add
                        changed = True
    return follow


def ll1_table(g, start):
    """표[(비단말, 단말)] = 그 칸에 들어가는 생성 규칙들의 목록. 두 개 이상이면 충돌이다."""
    first = first_sets(g)
    follow = follow_sets(g, start, first)
    table = {}
    for A, prods in g.items():
        for p in prods:
            f = first_of(p, first, g)
            cols = f - {EPS}
            if EPS in f:
                cols |= follow[A]
            for x in cols:
                table.setdefault((A, x), []).append(p)
    return table


def conflicts(table):
    return {k: v for k, v in table.items() if len(v) > 1}


def tab(text, start):
    t = ll1_table(G(text), start)
    return {k: [" ".join(p) or EPS for p in v] for k, v in t.items()}


if __name__ == "__main__":
    e = EPS
    # 문제 1
    assert tab("S -> ( L ) | a\nL -> S L'\nL' -> , S L' | ε", "S") == {
        ("S", "("): ["( L )"], ("S", "a"): ["a"],
        ("L", "("): ["S L'"], ("L", "a"): ["S L'"],
        ("L'", ","): [", S L'"], ("L'", ")"): [e]}

    # 문제 2
    assert tab("S -> A B\nA -> a A | ε\nB -> b B | c", "S") == {
        ("S", "a"): ["A B"], ("S", "b"): ["A B"], ("S", "c"): ["A B"],
        ("A", "a"): ["a A"], ("A", "b"): [e], ("A", "c"): [e],
        ("B", "b"): ["b B"], ("B", "c"): ["c"]}

    # 문제 3
    assert tab("S -> a B D h\nB -> c C\nC -> b C | ε\nD -> E F\nE -> g | ε\nF -> f | ε", "S") == {
        ("S", "a"): ["a B D h"], ("B", "c"): ["c C"],
        ("C", "b"): ["b C"], ("C", "g"): [e], ("C", "f"): [e], ("C", "h"): [e],
        ("D", "g"): ["E F"], ("D", "f"): ["E F"], ("D", "h"): ["E F"],
        ("E", "g"): ["g"], ("E", "f"): [e], ("E", "h"): [e],
        ("F", "f"): ["f"], ("F", "h"): [e]}

    # 문제 4: 매달린 else. [S', e] 칸에 규칙이 둘 들어간다
    t4 = tab("S -> i E t S S' | a\nS' -> e S | ε\nE -> b", "S")
    assert t4 == {("S", "i"): ["i E t S S'"], ("S", "a"): ["a"],
                  ("S'", "e"): ["e S", e], ("S'", END): [e], ("E", "b"): ["b"]}
    g4 = G("S -> i E t S S' | a\nS' -> e S | ε\nE -> b")
    first = first_sets(g4)
    assert first == {"S": {"i", "a"}, "S'": {"e", e}, "E": {"b"}}
    assert follow_sets(g4, "S", first) == {"S": {END, "e"}, "S'": {END, "e"}, "E": {"t"}}

    print("ALL CHECKS PASSED")