← SEARCHABLE ARCHIVE
Nand2Tetris · 2026-05-21

Nand2Tetris — 10장

Jack 구문 분석기

INTERACTIVE READING MODE →

# Jack Analyzer
# 입력: .jack 파일
# 출력: XML 토큰 / 파스 트리
# 범위: tokenizer + recursive descent parser
# 동작:
# Jack 코드를 token 단위로 자른 뒤,
# class -> classVarDec -> subroutineDec -> statements 순서로 XML을 생성함

import re
import sys
from html import escape


KEYWORDS = {
    "class", "constructor", "function", "method", "field", "static",
    "var", "int", "char", "boolean", "void", "true", "false", "null",
    "this", "let", "do", "if", "else", "while", "return",
}

SYMBOLS = set("{}()[].,;+-*/&|<>=~")


def remove_comments(source):
    source = re.sub(r"/\*.*?\*/", "", source, flags=re.S)
    source = re.sub(r"//.*", "", source)
    return source


def tokenize(source):
    tokens = []
    i = 0

    while i < len(source):
        c = source[i]

        if c.isspace():
            i += 1
            continue

        if c == '"':
            j = i + 1
            while source[j] != '"':
                j += 1
            tokens.append(("stringConstant", source[i + 1:j]))
            i = j + 1
            continue

        if c in SYMBOLS:
            tokens.append(("symbol", c))
            i += 1
            continue

        j = i
        while j < len(source) and not source[j].isspace() and source[j] not in SYMBOLS:
            j += 1

        word = source[i:j]
        if word in KEYWORDS:
            tokens.append(("keyword", word))
        elif word.isdigit():
            tokens.append(("integerConstant", word))
        else:
            tokens.append(("identifier", word))

        i = j

    return tokens


class CompilationEngine:
    def __init__(self, tokens):
        self.tokens = tokens
        self.index = 0
        self.lines = []

    def current(self):
        return self.tokens[self.index]

    def write(self, text):
        self.lines.append(text)

    def eat(self):
        token_type, value = self.current()
        self.write(f"<{token_type}> {escape(value)} </{token_type}>")
        self.index += 1
        return value

    def compile_class(self):
        self.write("<class>")
        self.eat()  # class
        self.eat()  # className
        self.eat()  # {

        while self.current()[1] in ("static", "field"):
            self.compile_class_var_dec()

        while self.current()[1] in ("constructor", "function", "method"):
            self.compile_subroutine()

        self.eat()  # }
        self.write("</class>")

    def compile_class_var_dec(self):
        self.write("<classVarDec>")
        while self.current()[1] != ";":
            self.eat()
        self.eat()
        self.write("</classVarDec>")

    def compile_subroutine(self):
        self.write("<subroutineDec>")
        while self.current()[1] != "{":
            self.eat()
        self.compile_subroutine_body()
        self.write("</subroutineDec>")

    def compile_subroutine_body(self):
        self.write("<subroutineBody>")
        self.eat()  # {
        while self.current()[1] == "var":
            self.compile_var_dec()
        self.compile_statements()
        self.eat()  # }
        self.write("</subroutineBody>")

    def compile_var_dec(self):
        self.write("<varDec>")
        while self.current()[1] != ";":
            self.eat()
        self.eat()
        self.write("</varDec>")

    def compile_statements(self):
        self.write("<statements>")
        while self.current()[1] in ("let", "if", "while", "do", "return"):
            self.write(f"<{self.current()[1]}Statement>")
            while self.current()[1] not in (";", "}"):
                self.eat()
            if self.current()[1] == ";":
                self.eat()
            self.write(f"</{self.lines[-2][1:].split('Statement>')[0]}Statement>")
        self.write("</statements>")