# parser.py import ply.yacc as yacc from lexer import tokens # --- AST node types --- class Label: def __init__(self, name): self.name = name def __repr__(self): return f"Label({self.name!r})" class Instr: def __init__(self, op, args): self.op = op # "mov", "add", "jmp" self.args = args # list of operands def __repr__(self): return f"Instr({self.op!r}, {self.args!r})" # --- Grammar --- def p_program(p): """program : lines""" p[0] = p[1] def p_lines_multi(p): """lines : lines line""" # p[1] is the list so far, p[2] is a single line (Label or Instr) p[0] = p[1] if p[2] is not None: p[0].append(p[2]) def p_lines_single(p): """lines : line""" # start list with the single line (unless it's None; but we never produce None here) p[0] = [p[1]] if p[1] is not None else [] def p_line(p): """line : label | instruction""" p[0] = p[1] def p_label(p): """label : IDENT COLON""" p[0] = Label(p[1]) def p_instruction_mov(p): """instruction : MOV REGISTER COMMA NUMBER""" # mov A, #123 p[0] = Instr("mov", [p[2], p[4]]) def p_instruction_add(p): """instruction : ADD REGISTER COMMA REGISTER""" # add B, A p[0] = Instr("add", [p[2], p[4]]) def p_instruction_jmp(p): """instruction : JMP IDENT""" # jmp label p[0] = Instr("jmp", [p[2]]) def p_error(p): if p is None: raise SyntaxError("Syntax error at EOF") else: raise SyntaxError(f"Syntax error at {p.value!r} (type {p.type})") parser = yacc.yacc()