""" Expression Evaluator -- Learn DAGs & State Machines ==================================================== CLI entry point and interactive REPL. Usage: python main.py "3 + 4 * 2" # evaluate python main.py # REPL mode python main.py --show-tokens --show-ast --trace "expr" # show internals python main.py --dot "3 + 4 * 2" | dot -Tpng -o ast.png python main.py --dot-fsm | dot -Tpng -o fsm.png """ import argparse import sys from tokenizer import tokenize, TokenError from parser import Parser, ParseError from evaluator import evaluate, evaluate_traced, EvalError from visualize import ast_to_dot, fsm_to_dot, ast_to_text def process_expression(expr, args): """Tokenize, parse, and evaluate a single expression.""" try: tokens = tokenize(expr) except TokenError as e: _print_error(expr, e) return if args.show_tokens: print("\nTokens:") for tok in tokens: print(f" {tok}") try: ast = Parser(tokens).parse() except ParseError as e: _print_error(expr, e) return if args.show_ast: print("\nAST (text tree):") print(ast_to_text(ast)) if args.dot: print(ast_to_dot(ast)) return # dot output goes to stdout, skip numeric result if args.trace: try: result, steps = evaluate_traced(ast) except EvalError as e: print(f"Eval error: {e}") return print("\nEvaluation trace (topological order):") for step in steps: print(step) print(f"\nResult: {_format_result(result)}") else: try: result = evaluate(ast) except EvalError as e: print(f"Eval error: {e}") return print(_format_result(result)) def repl(args): """Interactive read-eval-print loop.""" print("Expression Evaluator REPL") print("Type an expression, or 'quit' to exit.") flags = [] if args.show_tokens: flags.append("--show-tokens") if args.show_ast: flags.append("--show-ast") if args.trace: flags.append("--trace") if flags: print(f"Active flags: {' '.join(flags)}") print() while True: try: line = input(">>> ").strip() except (EOFError, KeyboardInterrupt): print() break if line.lower() in ("quit", "exit", "q"): break if not line: continue process_expression(line, args) print() def _print_error(expr, error): """Print an error with a caret pointing to the position.""" print(f"Error: {error}") if hasattr(error, 'position') and error.position is not None: print(f" {expr}") print(f" {' ' * error.position}^") def _format_result(v): """Format a numeric result: show as int when possible.""" if isinstance(v, float) and v == int(v) and abs(v) < 1e15: return str(int(v)) return str(v) def main(): arg_parser = argparse.ArgumentParser( description="Expression Evaluator -- learn DAGs and state machines", epilog="Examples:\n" " python main.py '3 + 4 * 2'\n" " python main.py --show-tokens --trace '-(3 + 4) ^ 2'\n" " python main.py --dot '(2+3)*4' | dot -Tpng -o ast.png\n" " python main.py --dot-fsm | dot -Tpng -o fsm.png", formatter_class=argparse.RawDescriptionHelpFormatter, ) arg_parser.add_argument( "expression", nargs="?", help="Expression to evaluate (omit for REPL mode)", ) arg_parser.add_argument( "--show-tokens", action="store_true", help="Display tokenizer output", ) arg_parser.add_argument( "--show-ast", action="store_true", help="Display AST as indented text tree", ) arg_parser.add_argument( "--trace", action="store_true", help="Show step-by-step evaluation trace", ) arg_parser.add_argument( "--dot", action="store_true", help="Output AST as graphviz dot (pipe to: dot -Tpng -o ast.png)", ) arg_parser.add_argument( "--dot-fsm", action="store_true", help="Output tokenizer FSM as graphviz dot", ) args = arg_parser.parse_args() # Special mode: just print the FSM diagram and exit if args.dot_fsm: print(fsm_to_dot()) return # REPL mode if no expression given if args.expression is None: repl(args) else: process_expression(args.expression, args) if __name__ == "__main__": main()