有一种通用的方法可以用最少的括号来漂亮地打印表达式。首先为您的表达语言定义一个明确的语法,该语法编码优先级和关联性规则。例如,假设我的语言具有三个二元运算符(*、+、@)和一个一元运算符(~),那么我的语法可能看起来像
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 ')'
语法的解析树包含所有必要的(和不必要的)括号,并且不可能构造一个扁平化导致歧义表达式的解析树。例如,字符串没有解析树
1 @ 2 @ 3
因为'@'是非关联的并且总是需要括号。另一方面,字符串
1 @ (2 @ 3)
有解析树
E(E0(E1( E2(E3(Num(1)))
'@'
E2(E3( '('
E0(E1(E2(E3(Num(2)))
'@'
E2(E3(Num(3)))))
')')))
因此,问题被简化为将抽象语法树强制转换为解析树的问题。通过尽可能避免将 AST 节点强制为原子表达式来获得最小数量的括号。这很容易以系统的方式完成:
维护一个由指向 AST 中当前节点的指针和正在扩展的当前产品组成的对。用根 AST 节点和“E”产生式初始化该对。在每种情况下,对于 AST 节点的可能形式,尽可能多地扩展语法以对 AST 节点进行编码。这将为每个 AST 子树留下一个未扩展的语法生成。在每个(子树,生产)对上递归地应用该方法。
例如,如果 AST 是(* (+ 1 2) 3)
,则进行如下操作:
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 ] )
')' )))
...
该算法当然可以以不那么明确的方式实现,但是该方法可以用来指导实现而不至于发疯:)。