有一种通用的方法可以使用最少的括号来美化表达式。首先,定义一个明确的语法,用于编码优先级和结合规则。例如,假设我有一种语言,其中包含三个二元运算符(*,+,@)和一个一元运算符(~),那么我的语法可能如下所示:
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 ] )
')' )))
...
当然,算法可以以更不明显的方式实现,但是该方法可用于指导实现而不会让人发疯 :)