Skip to content
Part IA Michaelmas, Lent Term

Parsing and Pretty Printing Regular Expressions

Parsing: String to AST

The Problem

Given a concrete string like a|b*c, produce the abstract syntax tree.

Precedence Resolution

The string a|b*c should parse as a(bc)a|(b*c), not (ab)c(a|b)*c.

This is implemented by a parsing relation with precedence levels.

Parsing Relation

Levels

Define parsing at different precedence levels:

  • parseUnion: handles |
  • parseConcat: handles concatenation
  • parseStar: handles *
  • parseAtom: handles atoms and parentheses

Grammar (Simplified)

Union  ::= Concat ('|' Concat)*
Concat ::= Star+
Star   ::= Atom '*'*
Atom   ::= ε | ∅ | symbol | '(' Union ')'

Rules for Parsing

Atoms: parseAtom(a,Syma)parseAtom(ε,Null)parseUnion(s,R)parseAtom((s),R)\frac{}{\text{parseAtom}(a, \text{Sym}_a)} \quad \frac{}{\text{parseAtom}(\varepsilon, \text{Null})} \quad \frac{\text{parseUnion}(s, R)}{\text{parseAtom}((s), R)}

Star: parseAtom(s,R)parseStar(s,R)parseStar(s,R)parseStar(s,Star(R))\frac{\text{parseAtom}(s, R)}{\text{parseStar}(s, R)} \quad \frac{\text{parseStar}(s, R)}{\text{parseStar}(s*, \text{Star}(R))}

Concatenation: parseStar(s,R)parseConcat(s,R)parseConcat(s1,R1)parseStar(s2,R2)parseConcat(s1s2,Concat(R1,R2))\frac{\text{parseStar}(s, R)}{\text{parseConcat}(s, R)} \quad \frac{\text{parseConcat}(s_1, R_1) \quad \text{parseStar}(s_2, R_2)}{\text{parseConcat}(s_1 s_2, \text{Concat}(R_1, R_2))}

Union: parseConcat(s,R)parseUnion(s,R)parseUnion(s1,R1)parseConcat(s2,R2)parseUnion(s1s2,Union(R1,R2))\frac{\text{parseConcat}(s, R)}{\text{parseUnion}(s, R)} \quad \frac{\text{parseUnion}(s_1, R_1) \quad \text{parseConcat}(s_2, R_2)}{\text{parseUnion}(s_1|s_2, \text{Union}(R_1, R_2))}

Example Parse

String: a|b*

  1. Parse as Union
  2. Left part: a parses as Syma_a
  3. Right part: b*
    • b parses as Symb_b
    • b* parses as Star(Symb_b)
  4. Result: Union(Syma_a, Star(Symb_b))
    Union
    /   \
   a    Star
         |
         b

Parentheses Disambiguate

With Parentheses

(a|b)*c parses as:

    Concat
    /    \
  Star    c
    |
  Union
  /   \
 a     b

Without Parentheses

a|b*c parses as a|(b*c):

    Union
    /   \
   a   Concat
        /   \
      Star   c
        |
        b

Pretty Printing: AST to String

The Problem

Given an AST, produce a readable concrete string.

Strategy

  1. Recursively convert subtrees to strings
  2. Add parentheses when child has lower precedence than parent
  3. Minimize parentheses for readability

Precedence Table

ConstructorPrecedence
Star3 (highest)
Concat2
Union1 (lowest)

Rules

pprint(R,p)=pretty print R at precedence level p\text{pprint}(R, p) = \text{pretty print } R \text{ at precedence level } p

Add parentheses if p>prec(R)p > \text{prec}(R).

Atoms: Never need parentheses around themselves.

Star: pprint(R,p)=pprint(R,3)+\text{pprint}(R^*, p) = \text{pprint}(R, 3) + *

Concat: pprint(RS,p)=pprint(R,2)+pprint(S,2)\text{pprint}(RS, p) = \text{pprint}(R, 2) + \text{pprint}(S, 2), parenthesise if p>2p > 2.

Union: pprint(RS,p)=pprint(R,1)++pprint(S,1)\text{pprint}(R|S, p) = \text{pprint}(R, 1) + | + \text{pprint}(S, 1), parenthesise if p>1p > 1.

Example Pretty Prints

Simple

  • pprint(a)=a\text{pprint}(a) = a
  • pprint(a)=a\text{pprint}(a^*) = a*
  • pprint(ab)=ab\text{pprint}(ab) = ab
  • pprint(ab)=ab\text{pprint}(a|b) = a|b

With Parentheses

  • pprint((ab))=(ab)\text{pprint}((a|b)^*) = (a|b)* (union under star needs parens)
  • pprint(ab)=ab\text{pprint}(a^*b^*) = a*b* (no parens needed)
  • pprint((ab)(cd))=(ab)(cd)\text{pprint}((a|b)(c|d)) = (a|b)(c|d) (unions in concat need parens)

Round-Trip Property

Parsing then Pretty Printing

For any string ss that parses to AST RR:

parse(s)=Rpprint(R)=s\text{parse}(s) = R \Rightarrow \text{pprint}(R) = s'

where ss' is semantically equivalent and may be ss or a normalised form.

Pretty Printing then Parsing

For any AST RR:

parse(pprint(R))=R\text{parse}(\text{pprint}(R)) = R

The pretty-printed string should re-parse to the same AST.


Summary

  • Parsing converts concrete strings to abstract syntax trees
  • Precedence determines how strings are structured into trees
  • Pretty printing converts ASTs back to readable strings
  • Parentheses are added only when necessary for correctness