There is a generic approach to pretty printing expressions with minimal parentheses. Begin by defining an unambiguous grammar for your expression language which encodes precedence and associativity rules. For example, say I have a language with three binary operators (*, +, @) and a unary operator (~), then my grammar might look like
E -> E0
E0 -> E1 '+' E0 (+ right associative, lowest precedence)
E0 -> E1
E1 -> E1 '*' E2 (* left associative; @ non-associative; same precedence)
E1 -> E2 '@' E2
E1 -> E2
E2 -> '~' E2 (~ binds the tightest)
E2 -> E3
E3 -> Num (atomic expressions are numbers and parenthesized expressions)
E3 -> '(' E0 ')'
Parse trees for the grammar contain all necessary (and unnecessary) parentheses, and it is impossible to construct a parse tree whose flattening results in an ambiguous expression. For example, there is no parse tree for the string
1 @ 2 @ 3
because '@' is non-associative and always requires parentheses. On the other hand, the string
1 @ (2 @ 3)
has parse tree
E(E0(E1( E2(E3(Num(1)))
'@'
E2(E3( '('
E0(E1(E2(E3(Num(2)))
'@'
E2(E3(Num(3)))))
')')))
The problem is thus reduced to the problem of coercing an abstract syntax tree to a parse tree. The minimal number of parentheses is obtained by avoiding coercing an AST node to an atomic expression whenever possible. This is easy to do in a systematic way:
Maintain a pair consisting of a pointer to the current node in the AST and the current production being expanded. Initialize the pair with the root AST node and the 'E' production. In each case for the possible forms of the AST node, expand the grammar as much as necessary to encode the AST node. This will leave an unexpanded grammar production for each AST subtree. Apply the method recursively on each (subtree, production) pair.
For example, if the AST is (* (+ 1 2) 3)
, then proceed as follows:
expand[ (* (+ 1 2) 3); E ] --> E( E0( E1( expand[(+ 1 2) ; E1]
'*'
expand[3 ; E2] ) ) )
expand[ (+ 1 2) ; E1 ] --> E1(E2(E3( '('
E0( expand[ 1 ; E1 ]
'+'
expand[ 2 ; E0 ] )
')' )))
...
The algorithm can of course be implemented in a much less explicit way, but the method can be used to guide an implementation without going insane :).