FIRST·FOLLOW 예제 사다리 문서의 문제 4 풀이 코드다. if __name__ == "__main__": 아래가 자체 테스트다.

"""FIRST·FOLLOW 예제 사다리의 답을 확인한다.

실행: python3 19_first-follow-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 ff(text, start):
    g = G(text)
    first = first_sets(g)
    return first, follow_sets(g, start, first)


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

    # 문제 2
    first, follow = ff("S -> A B\nA -> a A | ε\nB -> b B | c", "S")
    assert first == {"S": {"a", "b", "c"}, "A": {"a", e}, "B": {"b", "c"}}
    assert follow == {"S": {END}, "A": {"b", "c"}, "B": {END}}

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

    # 문제 4
    first, follow = ff("S -> A B | c S d\nA -> a A | ε\nB -> b | ε", "S")
    assert first == {"S": {"a", "b", "c", e}, "A": {"a", e}, "B": {"b", e}}
    assert follow == {"S": {END, "d"}, "A": {"b", END, "d"}, "B": {END, "d"}}

    print("ALL CHECKS PASSED")