""" Main entry point for the Mini Compiler. Usage: python compiler.py [--phase lex|parse|semantic|all] Runs the full compilation pipeline (or a single phase) on a .cl source file and prints the result to stdout. """ import argparse import sys import os # Make the src/ package importable regardless of working directory sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "src")) from lexer import Lexer, LexerError from parser import Parser, ParseError from semantic import SemanticAnalyzer, SemanticError def read_source(path: str) -> str: """Read source code from a file, with a friendly error on failure.""" try: with open(path, "r", encoding="utf-8") as f: return f.read() except FileNotFoundError: print(f"Error: File not found: '{path}'", file=sys.stderr) sys.exit(1) except IOError as e: print(f"Error reading file: {e}", file=sys.stderr) sys.exit(1) def run_lexer(source: str) -> list: """Milestone 1: Tokenize source and return token list.""" lexer = Lexer(source) try: tokens = lexer.tokenize() except LexerError as e: print(f"[Lexer Error] {e}", file=sys.stderr) sys.exit(1) print("=== TOKEN STREAM ===") for tok in tokens: print(f" {tok}") return tokens def run_parser(source: str): """Milestone 2: Parse source into an AST and return the root node.""" lexer = Lexer(source) try: tokens = lexer.tokenize() except LexerError as e: print(f"[Lexer Error] {e}", file=sys.stderr) sys.exit(1) parser = Parser(tokens) try: ast = parser.parse() except ParseError as e: print(f"[Parse Error] {e}", file=sys.stderr) sys.exit(1) print("=== ABSTRACT SYNTAX TREE ===") print(ast.pretty()) return ast def run_semantic(source: str): """Milestone 3: Run semantic analysis and report errors or success.""" lexer = Lexer(source) try: tokens = lexer.tokenize() except LexerError as e: print(f"[Lexer Error] {e}", file=sys.stderr) sys.exit(1) parser = Parser(tokens) try: ast = parser.parse() except ParseError as e: print(f"[Parse Error] {e}", file=sys.stderr) sys.exit(1) analyzer = SemanticAnalyzer() try: analyzer.analyze(ast) except SemanticError as e: print(f"[Semantic Error] {e}", file=sys.stderr) sys.exit(1) print("=== SEMANTIC ANALYSIS ===") print(" No errors found.") print() print(" Symbol Table (global scope):") for name, info in analyzer.global_scope.symbols.items(): print(f" {name:20s} : {info['type']}") return ast def main(): arg_parser = argparse.ArgumentParser( description="Mini Compiler — CL Language", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: python compiler.py samples/hello.cl python compiler.py samples/hello.cl --phase lex python compiler.py samples/hello.cl --phase parse python compiler.py samples/hello.cl --phase semantic """ ) arg_parser.add_argument("source", help="Path to the .cl source file") arg_parser.add_argument( "--phase", choices=["lex", "parse", "semantic", "all"], default="all", help="Which compilation phase to run (default: all)" ) args = arg_parser.parse_args() source = read_source(args.source) if args.phase == "lex": run_lexer(source) elif args.phase == "parse": run_parser(source) elif args.phase == "semantic": run_semantic(source) else: # "all" print(f"Compiling: {args.source}\n") run_lexer(source) print() run_parser(source) print() run_semantic(source) print() print("Compilation successful.") if __name__ == "__main__": main()