Python AST Transformer for Simple Expression Simplification
This challenge requires you to build a Python program that transforms an Abstract Syntax Tree (AST) of simple arithmetic expressions. The goal is to simplify expressions by evaluating constant arithmetic operations. This is a fundamental task in compilers and interpreters, allowing for code optimization and analysis.
Problem Description
You will be given a Python AST representing a simple arithmetic expression. Your task is to create a transformer that traverses this AST and replaces nodes representing constant arithmetic operations (like 2 + 3 or 5 * 10) with their evaluated results.
Key Requirements:
- AST Traversal: You must use Python's built-in
astmodule to parse Python code into an AST and then traverse it. - Constant Folding: Identify nodes where binary operations (
ast.Add,ast.Sub,ast.Mult,ast.Div) involve only constant numeric operands (integers or floats). - Evaluation: Evaluate these constant binary operations and replace the entire operation node with a new
ast.Constantnode containing the computed result. - Handling Non-Constants: If an operation involves non-constant operands (variables, function calls, etc.), it should remain unchanged.
- Output: The transformer should return a modified AST. For demonstration purposes, you will then unparse this modified AST back into Python code.
Expected Behavior:
Given an expression like x + (2 * 3) + 5, the transformer should identify 2 * 3, evaluate it to 6, and replace the subtree with a Constant(6). The expression would then conceptually become x + 6 + 5. If a subsequent pass or further transformation were to occur, 6 + 5 could also be simplified. For this challenge, a single pass is sufficient.
Edge Cases to Consider:
- Division by zero: Your evaluation logic should handle potential
ZeroDivisionErrorexceptions. If an error occurs, the operation node should remain unchanged. - Floating-point arithmetic: Ensure correct handling of floating-point numbers and their operations.
- Nested constant expressions: The transformer should be able to handle expressions like
(2 + 3) * (5 - 2).
Examples
Example 1:
Input Python code:
result = 10 + 5 * 2
Input AST (simplified representation for clarity):
Module
body:
Assign
targets:
Name(id='result')
value:
BinOp(left=Constant(value=10), op=Add, right=BinOp(left=Constant(value=5), op=Mult, right=Constant(value=2)))
Output Python code after transformation:
result = 10 + 10
Explanation:
The transformer evaluates 5 * 2 to 10 and replaces the multiplication subtree with Constant(10). The 10 + 10 part remains as is because the outer + operation isn't fully constant before the inner expression is processed. A subsequent pass would be needed to evaluate 10 + 10.
Example 2:
Input Python code:
a = 7 - (3 + 4)
Input AST (simplified representation for clarity):
Module
body:
Assign
targets:
Name(id='a')
value:
BinOp(left=Constant(value=7), op=Sub, right=BinOp(left=Constant(value=3), op=Add, right=Constant(value=4)))
Output Python code after transformation:
a = 7 - 7
Explanation:
The transformer evaluates 3 + 4 to 7 and replaces the addition subtree with Constant(7).
Example 3: (Division by Zero)
Input Python code:
safe_division = 10 / 2
unsafe_division = 5 / 0
Input AST (simplified representation for clarity):
Module
body:
Assign
targets:
Name(id='safe_division')
value:
BinOp(left=Constant(value=10), op=Div, right=Constant(value=2))
Assign
targets:
Name(id='unsafe_division')
value:
BinOp(left=Constant(value=5), op=Div, right=Constant(value=0))
Output Python code after transformation:
safe_division = 5.0
unsafe_division = 5 / 0
Explanation:
10 / 2 is evaluated to 5.0. For 5 / 0, a ZeroDivisionError would occur during evaluation, so the original BinOp node is preserved.
Constraints
- The input will be a valid Python string containing only simple arithmetic expressions.
- Expressions will involve integers and floats.
- The transformer should handle
ast.Add,ast.Sub,ast.Mult, andast.Divoperations. - You do not need to handle exponentiation (
ast.Pow), modulo (ast.Mod), or bitwise operations. - The transformation should be performed in a single pass. If an expression results in a constant, it should be evaluated. If it results in an operation that could be further simplified by a subsequent pass (like
10 + 10), it should remain as is in this single pass.
Notes
- The
astmodule is your primary tool. Familiarize yourself withast.parse(),ast.NodeTransformer, andast.unparse(). - You'll likely want to override the
visit_BinOpmethod in yourast.NodeTransformersubclass. - When evaluating, be mindful of the difference between integer division (
//) and float division (/) if you were to extend this challenge. For this problem, only standard division (/) is relevant, which results in a float. - Consider how to check if an operand is a constant value (i.e., an
ast.Constantnode). - The
ast.Constantnode in Python 3.8+ replacesast.Numandast.Str. Ensure your solution is compatible with modern Python versions.