컴파일과 인터프리트 비교 문서의 검증 코드다. if __name__ == "__main__": 아래가 자체 테스트다.

# 컴파일과 인터프리트 비교: "언제, 얼마만큼 번역하는가"를 작은 장난감 언어로 세어 본다
# 실행: python3 02_compilation-vs-interpretation_verify.py   (표준 라이브러리만 쓴다)
# 장난감 언어: 한 줄에 문장 하나. "x = 식", "print 식", "repeat n" ... "end".
# 번역 = 문장의 글자를 읽어 파이썬 함수 하나로 바꾸는 일. 번역 횟수를 센다.

PROGRAM = """total = 0
repeat 1000
total = total + 2
end
print total"""

BROKEN = """print 1
print 2
x = = 3
print 4"""


class Counter:
    def __init__(self):
        self.translations = 0


def translate(line, counter):
    """문장 하나를 번역한다. 문법이 틀리면 SyntaxError."""
    counter.translations += 1
    if line.startswith("print "):
        code = compile(line[6:], "<toy>", "eval")
        return lambda env, out: out.append(eval(code, {}, env))
    name, _, expr = line.partition(" = ")
    if not name.isidentifier():
        raise SyntaxError(line)
    code = compile(expr, "<toy>", "eval")
    return lambda env, out: env.__setitem__(name, eval(code, {}, env))


def interpret(source):
    """실행하려는 문장을 그때마다 번역해 바로 실행한다."""
    lines, counter, env, out = source.split("\n"), Counter(), {}, []
    def block(i):
        while i < len(lines) and lines[i] != "end":
            if lines[i].startswith("repeat "):
                n = int(lines[i].split()[1])
                for _ in range(n):
                    j = block(i + 1)
                i = j + 1
            else:
                translate(lines[i], counter)(env, out)
                i += 1
        return i
    try:
        block(0)
        error = None
    except SyntaxError as e:
        error = str(e)
    return out, counter.translations, error


def compile_program(source):
    """프로그램 전체를 먼저 번역해 둔다. 틀린 줄이 있으면 실행할 것을 만들지 않는다."""
    lines, counter = source.split("\n"), Counter()
    def block(i):
        ops = []
        while i < len(lines) and lines[i] != "end":
            if lines[i].startswith("repeat "):
                n = int(lines[i].split()[1])
                body, j = block(i + 1)
                ops.append(("repeat", n, body))
                i = j + 1
            else:
                ops.append(("stmt", translate(lines[i], counter)))
                i += 1
        return ops, i
    try:
        target, _ = block(0)
    except SyntaxError as e:
        return None, counter.translations, str(e)
    return target, counter.translations, None


def run_target(target):
    env, out = {}, []
    def go(ops):
        for op in ops:
            if op[0] == "repeat":
                for _ in range(op[1]):
                    go(op[2])
            else:
                op[1](env, out)
    go(target)
    return out


if __name__ == "__main__":
    # 같은 프로그램, 같은 출력
    out_i, n_i, err_i = interpret(PROGRAM)
    target, n_c, err_c = compile_program(PROGRAM)
    assert err_i is None and err_c is None
    assert out_i == run_target(target) == [2000]
    # 번역 횟수: 인터프리트는 실행한 문장 수만큼(1 + 1000 + 1), 컴파일은 문장 수만큼(3)
    assert n_i == 1002 and n_c == 3
    # 번역해 둔 것은 다시 번역하지 않고 몇 번이고 돌릴 수 있다
    assert run_target(target) == [2000] and run_target(target) == [2000]
    # 틀린 줄이 있을 때: 인터프리트는 그 줄 앞까지 실행하고 멈춘다. 컴파일은 아무것도 실행하지 않는다
    out_i, _, err_i = interpret(BROKEN)
    target, _, err_c = compile_program(BROKEN)
    assert out_i == [1, 2] and err_i is not None
    assert target is None and err_c is not None
    print("ALL CHECKS PASSED")