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.
Lexical token 2026-10-14
Parsing 2026-10-14
The recognition process performed by a parser, producing a structural description of text according to a programming language syntax.
Past exam of the computer science course of the University of Oxford 2015 Compilers 1 a Solution 2026-10-31
Use an abstract syntax tree with the following constructors. Here
Expr denotes the separate abstract syntax tree type for a program expression, Name identifies a variable assignment target, and the square brackets denote an ordered list.Stmt = Assign(Name, Expr)
| Seq(Stmt, Stmt)
| Case(Expr, [(Expr, Stmt)])The list in
Case must be nonempty. Its first field is the controlling program expression; each pair contains a label program expression and the corresponding body. Keep the pairs in source order and keep repeated labels: this is an ordered search, with the first successful equality test choosing the body. A label is an abstract syntax tree for a program expression, not a previously evaluated integer or a key in a map data structure. Evaluating or reordering labels during compilation would change the specified programming language semantics.Seq(s,t) executes s before t. The delimiters begin and end need no surviving constructor: the parser uses them to determine the abstract syntax tree's grouping and then returns the enclosed tree. There are no declarations in these blocks that would require a separate binding environment. A list of statements could replace binary Seq; below we choose right-associated binary trees so that the semantic actions are precise.The essential representation is an ordered, nonempty list of expression/body pairs, together with assignment and sequencing nodes. In constructor notation,
Past exam of the logic and set theory course of the University of Oxford 2008 A 1 e ii Solution 2026-10-31
A useful consequence of the formation rules is that logical formulas form a prefix-free set of strings. To see this, describe a deterministic parser that reads one logical formula from its starting position. On a logical variable it consumes that one symbol. On a negation symbol it recursively reads one following logical formula. On an opening parenthesis it recursively reads a left logical formula, requires a logical implication symbol, recursively reads a right logical formula, and requires the closing parenthesis. Induction on formation proves that, if a valid logical formula begins at the current position, this procedure consumes exactly that logical formula and stops at its end.
Suppose now that both and are logical formulas and . The same parser, started at their common first symbol, must stop both at the end of and at the end of . Its deterministic stopping position cannot differ, so is empty. HenceThis argument applies to symbol strings; it does not use truth values or logical equivalence.
Use an auxiliary parser that returns both a value and the unconsumed suffix. The arithmetic helper is:For the integer-valued expression language, this chooses integer division with A number consumes one token. An operator consumes the two successive complete expressions that follow it, so nested operators are handled by recursion. Structural induction on the expression proves that
applyOp :: Op -> Int -> Int -> Int
applyOp Add x y = x + y
applyOp Sub x y = x - y
applyOp Mul x y = x * y
applyOp Div x y = x `div` ydiv's rounding convention. The result is evaluated under the intended arithmetic model; bounded-Int overflow is separate from parsing.evalP :: Polish -> Int
evalP input =
case parse input of
(value, []) -> value
_ -> error "trailing tokens"
where
parse :: Polish -> (Int, Polish)
parse [] = error "missing operand"
parse (Number n : rest) = (n, rest)
parse (Operator op : rest) =
let (left, rest1) = parse rest
(right, rest2) = parse rest1
in (applyOp op left right, rest2)parse consumes exactly its Polish notation and returns its value. Keeping the suffix is essential: neither operand has a fixed token count. Division by zero need not be treated separately under the stated input convention.Compose the three parsing stages using the supplied fold, and accept only when no suffix remains:Every failed intermediate parser is converted to
eval :: String -> Maybe Int
eval input = foldMaybe first Nothing (parseInt input)
where
first (a,rest) = foldMaybe (second a) Nothing (parseOp rest)
second a (op,rest) = foldMaybe (finish a op) Nothing (parseInt rest)
finish a op (b,rest)
| null rest = Just (apply op a b)
| otherwise = Nothing
apply '+' a b = a+b
apply '-' a b = a-b
apply '*' a b = a*b
apply '/' a b = a `div` b
apply _ _ _ = error "unreachable: parseOp rejected this operator"Nothing by foldMaybe. Checking null rest rejects both a second operation and trailing letters. In particular, integer division makes eval "34/4" = Just 8. As permitted by the question, division by zero is outside the specified inputs; the bounded-Int proviso also applies to arithmetic results.In the grammar , no logical formula is a proper initial segment of another. A recursive parser reads one variable, or a negation followed by one formula, or a parenthesized pair of formulas separated by implication. Structural induction shows that this parser consumes precisely one whole formula. Its deterministic stopping position proves prefix-freeness. Consequently any formula substring in a valid formula coincides with the formula of a syntax subtree.
Programming language syntax 2026-10-13
The rules for forming programs in a programming language. A parser checks and structures text according to these rules; an abstract syntax tree records the resulting constructs.