[英]Pretty Printing AST with Minimal Parentheses
我正在為 JavaScript AST 實現一個漂亮的打印機,我想問一下是否有人知道一種“正確的”算法,可以根據 operator precedence 和associativity自動將表達式與最小括號括起來。 我在谷歌上沒有找到任何有用的材料。
似乎很明顯的是,父級具有更高優先級的運算符應該用括號括起來,例如:
(x + y) * z // x + y has lower precedence
但是,也有一些運算符不是關聯的,在這種情況下仍然需要括號,例如:
x - (y - z) // both operators have the same precedence
我想知道后一種情況的最佳規則是什么。 對於除法和減法來說是否足夠,如果 rhs 子表達式具有小於或等於的優先級,則應該用括號括起來。
我偶然發現了你的問題,我自己也在尋找答案。 雖然我還沒有找到規范的算法,但我發現,就像你說的那樣,單獨的運算符優先級不足以最小化括號表達式。 我嘗試在 Haskell 中編寫一個 JavaScript 漂亮的打印機,雖然我發現編寫一個強大的解析器很乏味,所以我改變了具體的語法: https : //gist.github.com/kputnam/5625856
除了優先級之外,您還必須考慮運算符關聯性。 像/
和-
這樣的二元運算被解析為左關聯。 但是,賦值=
、求冪^
和相等==
是右結合的。 這意味着表達式Div (Div ab) c
可以寫成a / b / c
而不帶括號,但Exp (Exp ab) c
必須被寫成(a ^ b) ^ c
。
您的直覺是正確的:對於左結合運算符,如果左操作數的表達式與其父級的綁定不緊密,則應將其加括號。 如果右操作數的表達式與其父級綁定的緊密或不緊密,則應將其括起來。 所以Div (Div ab) (Div cd)
不需要左子表達式周圍的括號,但右子表達式需要: a / b / (c / d)
。
接下來,一元運算符,特別是可以是二元或一元的運算符,如否定和減法-
、強制和加法+
等,可能需要根據具體情況進行處理。 例如Sub a (Neg b)
應該打印為a - (-b)
,即使一元否定比減法綁定更緊密。 我想這取決於您的解析器, a - -b
可能不會含糊不清,只是丑陋。
我不確定可以是前綴和后綴的一元運算符應該如何工作。 在++ (a ++)
和(++ a) ++
等表達式中,其中一個運算符的綁定必須比另一個更緊密,否則++ a ++
會產生歧義。 但我懷疑即使其中一個不需要括號,為了可讀性,您可能還是想添加括號。
這取決於特定語法的規則。 我認為您對具有不同優先級的運算符以及減法和除法的運算符都正確。
然而,冪運算通常被區別對待,因為它的右手操作數首先被評估。 所以你需要
(a ** b) ** c
當 c 是根的右孩子時。
括號的走向取決於語法規則的定義。 如果你的語法是這樣的形式
exp = sub1exp ;
exp = sub1exp op exp ;
sub1exp = sub1exp ;
sub1exp = sub1exp op1 sub2exp ;
sub2exp = sub3exp ;
sub2exp = sub3exp op2 sub2exp ;
sub3exp = ....
subNexp = '(' exp ')' ;
op1 和 op2 是非關聯的,如果子樹根也是 op1,你想把 op1 的右子樹放在括號里,如果左子樹有根 op2,你想把 op2 的左子樹放在括號里。
有一種使用最少括號來漂亮打印表達式的通用方法。 首先為您的表達式語言定義一個明確的語法,該語法對優先級和關聯性規則進行編碼。 例如,假設我的語言包含三個二元運算符(*、+、@)和一個一元運算符(~),那么我的語法可能看起來像
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 ] )
')' )))
...
該算法當然可以以不太明確的方式實現,但該方法可用於指導實現而不會發瘋:)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.