Abstract syntax tree 2026-10-13
An abstract syntax tree is a rooted tree recording the meaningful syntactic pieces of a program and how they fit together. A program identifier such as
x, a literal value such as 2, and a node representing addition have different roles. Their arrangement answers structural questions: which program expression supplies an operand, which name is a variable assignment target, and which construct encloses another. This makes an abstract syntax tree useful to a compiler, an interpreter, and the type system that checks a program.The word "abstract" describes what is omitted. Lexical analysis first recognizes lexical tokens, such as
x, =, 2, +, 3, *, 4. A concrete parse tree can retain grammar derivation structure, including punctuation and intermediate context-free grammar categories such as expression, term and factor. An abstract syntax tree usually keeps the operation and its operands without all those grammar scaffolds. Parentheses can determine its shape without surviving as separate nodes. A parser may construct the abstract syntax tree directly; it need not first allocate a concrete parse tree.An abstract syntax tree records programming language syntax, but does not by itself supply all programming language semantics. In
a+b, the names do not reveal their current values, and + might perform addition of integers or concatenate strings. The programming language rules, name bindings, and operand types determine the meaning. In Python, successfully calling ast.parse also does not guarantee that later compilation will accept the result: parsing return 42 succeeds, but compiling that statement outside a function fails. [ref]ast — Abstract syntax trees, Python 3.14 documentation. ast.parse: parsing performs no scoping checks; the return 42 example
An abstract syntax tree is not machine code. Its nodes describe language constructs. A compiler can check and transform this structure before translating it into executable instructions; an interpreter can evaluate an appropriate tree directly. The little evaluator below demonstrates this distinction without pretending to implement an entire programming language.
Abstract syntax tree precedence example 2026-10-13
Consider Python programs
x=2+3*4 and x=(2+3)*4. Operator precedence places multiplication inside the right operand of addition in the first abstract syntax tree. The parentheses instead place addition inside the left operand of multiplication in the second. Their right-hand sides evaluate to and . No rule that simply reads all operators left to right would recover both structures. [ref]https://docs.python.org/3.14/reference/expressions.html#operator-precedence. Python language reference, section 6.17, multiplicative operators bind more tightly than additive operators
Two abstract syntax trees for variable assignment: operator precedence gives
. Details. x=2+3*4 the value , while parentheses give x=(2+3)*4 the value These are schematic views of Python trees:
Module is omitted, and the Add/Mult and Store markers are written inside their owning nodes. In the actual schema, BinOp has left, op and right fields; Assign has a list of targets and a value. Name(id='x', ctx=Store()) marks the name as a variable assignment target. Reading x in a program expression instead uses Load. A variable assignment is a statement, not an equation asserting that two values are equal. [ref]A restricted recursive evaluator can follow those fields. It accepts only integer literal values and binary addition, subtraction and multiplication; the surrounding variable assignment remains a separate action. Using
type(value) is int excludes Boolean constants, even though Python treats bool as a subclass of int.import ast
OPS = {ast.Add: lambda a, b: a + b,
ast.Sub: lambda a, b: a - b,
ast.Mult: lambda a, b: a * b}
def evaluate(node):
if isinstance(node, ast.Constant) and type(node.value) is int:
return node.value
if isinstance(node, ast.BinOp) and type(node.op) in OPS:
return OPS[type(node.op)](evaluate(node.left), evaluate(node.right))
raise ValueError("outside the integer-expression language")
for source in ("x=2+3*4", "x=(2+3)*4"):
tree = ast.parse(source, optimize=0)
statement = tree.body[0]
print(statement.targets[0].id, evaluate(statement.value))
# x 14
# x 20The children in a rooted tree have ordered roles:
left and right cannot be interchanged. For subtraction, 8-3 gives while 3-8 gives . Operator associativity also matters: 8-3-1 parses as (8-3)-1, giving , whereas 8-(3-1) gives . Here the syntax groups operators to the left; this does not assert the mathematical associativity of subtraction. Operator precedence determines grouping, rather than a general permission to reorder execution. For example, Python evaluates the calls in f()+g()*h() in the order f, g, h, although the multiplication result must be ready before the outer addition. [ref]https://docs.python.org/3.14/reference/expressions.html#evaluation-order. Python language reference, sections 6.16 and 6.17: left-to-right evaluation and left grouping of additive operators
Abstract syntax tree transformation 2026-10-13
An abstract syntax tree transformation replaces or rearranges nodes. It can simplify a program expression, insert instrumentation, or translate a language construct into simpler constructs. When intended to preserve programming language semantics, it must preserve the relevant observable behavior, including values, side effects, and failures. A changed tree can be structurally valid while changing the program's meaning.
Abstract syntax tree traversal 2026-10-13
An abstract syntax tree traversal systematically visits its nodes. A depth-first traversal of a tree that processes each child before its parent is called postorder. For the program expression
2+3*4, the schematic postorder is 2, 3, 4, Mult, Add. This order lets an evaluator obtain each operand value before applying its operation. A traversal of the entire Python schema additionally visits marker nodes such as Add and Store; this shorter list concerns only the expression nodes drawn in the figures. Visiting an abstract syntax tree is a structural operation, and is not a general model of execution: a conditional may select only one branch, while a structural visitor examines both. Constant folding 2026-10-13
Constant folding replaces a program expression whose value is known during translation by a literal value. For
x=2+3*4, first replace the multiplication subtree by Constant(value=12), then replace the addition subtree by Constant(value=14). The variable assignment and its Store target survive: the result still assigns to x. This illustrates why a bottom-up abstract syntax tree traversal is convenient: replacing one child can make its parent eligible for the next replacement.Here is a deliberately small Python transformer. Calling
generic_visit first transforms the children; returning a node replaces the visited node. The location helpers retain or supply the metadata expected for compilation. [ref]ast — Abstract syntax trees, Python 3.14 documentation. NodeTransformer, generic_visit, copy_location and fix_missing_locations
class FoldIntegers(ast.NodeTransformer):
def visit_BinOp(self, node):
self.generic_visit(node)
if (isinstance(node.left, ast.Constant)
and isinstance(node.right, ast.Constant)
and type(node.left.value) is int
and type(node.right.value) is int
and type(node.op) in OPS):
value = OPS[type(node.op)](node.left.value, node.right.value)
return ast.copy_location(ast.Constant(value=value), node)
return node
tree = ast.parse("x=2+3*4", optimize=0)
folded = ast.fix_missing_locations(FoldIntegers().visit(tree))
namespace = {}
exec(compile(folded, "<folded example>", "exec"), namespace)
assert namespace["x"] == 14
assert ast.unparse(folded) == "x = 14"This transformer folds literal integer operands only. It leaves
a+3*4 as a+12, without inventing a value for the program identifier a. It leaves calls intact: replacing 0*f() by 0 could discard a side effect or a failure. Nor does it reassociate floating-point arithmetic, where changing parentheses can change rounding. For the supported integer arithmetic, a short structural induction explains correctness: a retained literal value has the same value; at each folded binary node, the already-correct child values are combined by exactly the original operation in the original left/right order. This claim concerns the supported fragment's values, not unrestricted equivalence of arbitrary Python programs. Variable assignment 2026-10-13
A program action that associates a value with a target name. For Python
x=2+3*4, evaluate the right-hand program expression and bind x to its result. [ref]https://docs.python.org/3.14/reference/simple_stmts.html#assignment-statements. Python language reference, section 7.2: assignment to an identifier and evaluation of the right-hand side

