內容

名稱

B::Concise - 瀏覽 Perl 語法樹,列印關於運算的簡潔資訊

語法

perl -MO=Concise[,OPTIONS] foo.pl

use B::Concise qw(set_style add_callback);

說明

此編譯器後端會以幾種不同的省空間文字格式,列印 Perl 程式語法樹的內部 OP,這些格式適用於除錯 perl 或其他編譯器後端的內部運作。它可以列印 OP,其順序會出現在 OP 樹中、執行順序中,或以文字近似其樹狀結構,且顯示資訊的格式是可自訂的。它的功能類似於 perl 的 -Dx 除錯旗標或 B::Terse 模組,但它更複雜且更靈活。

範例

以下是兩個輸出(或「呈現」),在相同的程式碼片段上使用 -exec 和 -basic(即預設)格式化慣例。

   % perl -MO=Concise,-exec -e '$a = $b + 42'
   1  <0> enter
   2  <;> nextstate(main 1 -e:1) v
   3  <#> gvsv[*b] s
   4  <$> const[IV 42] s
*  5  <2> add[t3] sK/2
   6  <#> gvsv[*a] s
   7  <2> sassign vKS/2
   8  <@> leave[1 ref] vKP/REFC

在此 -exec 呈現中,每個 opcode 會按顯示的順序執行。標記為「*」的 add opcode 會在更詳細的部分中討論。

第 1 個欄位是 op 的順序編號,從 1 開始,並預設以 36 進位制顯示。在此它們純粹是線性的;在檢視有迴圈和分支的程式碼時,順序非常有幫助。

尖括號中的符號表示 op 的類型,例如;<2> 是 BINOP、<@> 是 LISTOP,而 <#> 是 PADOP,用於執行緒化 Perl。(請參閱 「OP 類別縮寫」)。

opname,如 'add[t1]',後面可能會接續括號或方括號中的 op 特定資訊(例如 '[t1]')。

op 旗標(例如 'sK/2')在 (「OP 旗標縮寫」) 中說明。

   % perl -MO=Concise -e '$a = $b + 42'
   8  <@> leave[1 ref] vKP/REFC ->(end)
   1     <0> enter ->2
   2     <;> nextstate(main 1 -e:1) v ->3
   7     <2> sassign vKS/2 ->8
*  5        <2> add[t1] sK/2 ->6
   -           <1> ex-rv2sv sK/1 ->4
   3              <$> gvsv(*b) s ->4
   4           <$> const(IV 42) s ->5
   -        <1> ex-rv2sv sKRM*/1 ->7
   6           <$> gvsv(*a) s ->7

預設呈現方式為由上而下,因此它們不是按執行順序排列。此表單反映堆疊用於剖析和評估表達式的使用方式;add 運算式作用於樹狀結構中其下方的兩個項。

Nullop 顯示為 ex-opname,其中 opname 是 Perl 優化過的 op。它們顯示的順序編號為「-」,因為它們不會執行(它們不會出現在前一個範例中),它們會在此列印,因為它們反映剖析。

箭頭指向下一個 op 的順序編號;它們不會出現在 -exec 模式中,原因很明顯。

請注意,由於此呈現是在非執行緒化 Perl 上完成的,因此前一個範例中的 PADOP 現在是 SVOP,而且有些(但不是全部)方括號已被圓括號取代。這是提供執行緒化和非執行緒化 Perl 呈現之間一些視覺區別的微妙功能。

選項

未以連字元開頭的引數會視為要呈現的子常式或格式的名稱;如果未指定此類函數,則會呈現程式的主體(在任何子常式之外,不包括已使用或已要求的檔案)。傳遞 BEGINUNITCHECKCHECKINITEND 會導致列印所有對應的特殊區塊。引數必須遵循選項。

選項會影響事物的呈現方式(即列印)。它們在此按其視覺效果呈現,第一個最強。它們會依據它們的交互關係分組;在每個組中,選項是互斥的(除非另有說明)。

Opcode 排序選項

這些選項控制 opcode 的「垂直顯示」。顯示「順序」在本文檔的其他地方也稱為「模式」。

-basic

列印 OP,順序為 OP 樹中出現的順序(前序遍歷,從根開始)。每個 OP 的縮排顯示其在樹中的層級,而行尾的「->」表示執行順序中的下一個 opcode。此模式為預設值,因此此旗標僅為完整性而包含。

-exec

列印 OP,順序為它們通常執行的順序(對於大多數結構,這是樹的後序遍歷,以根結束)。在多數情況下,通常會緊接在給定 OP 之後的 OP 會直接顯示在其下方;交替路徑會以縮排顯示。在迴圈等控制跳出線性路徑的情況中,會產生「goto」行。

-tree

以文字近似值列印 OP,樹的根位於左側,而子項的「由左至右」順序轉換為「由上至下」。由於此模式同時向右和向下延伸,因此不適合大型程式(除非您有非常寬的終端機)。

行式選項

這些選項會選取用於呈現每個 opcode 的行式(或僅為樣式),並決定實際列印到每一行中的資訊。

-concise

使用作者最喜歡的格式化慣例集。當然,這是預設值。

-terse

使用模擬 B::Terse 輸出的格式化慣例。基本模式幾乎與真正的 B::Terse 無法區分,而 exec 模式看起來非常相似,但順序更合乎邏輯,且沒有大括號。B::Terse 沒有 tree 模式,因此 tree 模式僅與 B::Terse 隱約相似。

-linenoise

使用格式化慣例,其中每個 OP 的名稱並非寫成完整形式,而是以一或兩個字元的縮寫表示。這主要是一個笑話。

-debug

使用類似於 CPAN 模組 B::Debug 的格式化慣例;這些格式化慣例一點也不簡潔。

-env

使用從環境變數 B_CONCISE_FORMATB_CONCISE_GOTO_FORMATB_CONCISE_TREE_FORMAT 讀取的格式化慣例。

樹狀結構特定格式化的選項

-compact

使用樹狀結構格式,其中連接節點的線條使用最少的空間(在大部分情況下為一個字元)。這會擠出螢幕上幾行珍貴的欄位。

-loose

使用樹狀結構格式,其中使用較長的邊緣來分隔 OP 節點。這種格式通常看起來比緊湊格式好,特別是在 ASCII 中,而且是預設值。

-vt

使用從 VT100 線條繪製組中繪製的樹狀結構連接字元。如果您的終端機支援,這樣看起來會更好。

-ascii

使用標準 ASCII 字元(例如 +|)繪製樹狀結構。這些字元看起來不如 VT100 字元整潔,但它們幾乎可以在任何終端機(或 less(1) 的水平捲動模式)中使用,而且適合文字文件或電子郵件。這是預設值。

這些是成對互斥的,即緊湊或鬆散、vt 或 ascii。

控制順序編號的選項

-basen

n 為基底列印 OP 順序編號。如果 n 大於 10,11 的數字將是 'a',依此類推。如果 n 大於 36,37 的數字將是 'A',依此類推,直到 62。目前不支援大於 62 的值。預設值為 36。

-bigendian

先列印最高有效位元組的順序編號。這是阿拉伯數字的慣例,也是預設值。

-littleendian

先列印最低有效位元組的順序編號。這顯然與 bigendian 互斥。

其他選項

-src

使用此選項,每個陳述式的呈現(從 nextstate OP 開始)將在產生它的原始程式碼第 1 行之前。例如

1  <0> enter
# 1: my $i;
2  <;> nextstate(main 1 junk.pl:1) v:{
3  <0> padsv[$i:1,10] vM/LVINTRO
# 3: for $i (0..9) {
4  <;> nextstate(main 3 junk.pl:3) v:{
5  <0> pushmark s
6  <$> const[IV 0] s
7  <$> const[IV 9] s
8  <{> enteriter(next->j last->m redo->9)[$i:1,10] lKS
k  <0> iter s
l  <|> and(other->9) vK/1
# 4:     print "line ";
9      <;> nextstate(main 2 junk.pl:4) v
a      <0> pushmark s
b      <$> const[PV "line "] s
c      <@> print vK
# 5:     print "$i\n";
...
-stash="somepackage"

有了這個,「somepackage」將會是必要的,然後檢查 stash,並呈現每個函式。

下列選項是成對互斥的。

-main

即使也指定了子常式,仍將主要程式包含在輸出中。當給定子常式名稱或參考時,通常會抑制此呈現。

-nomain

使用「-main」變更後,這會還原預設行為(通常不需要)。如果未給定子常式名稱/參考,則不論此旗標如何,都會呈現 main。

-nobanner

呈現通常會包含識別函式名稱或字串化子參考的旗幟列。這會抑制旗幟的列印。

TBC:移除字串化的 coderef;雖然它為每個呈現的函式提供「cookie」,但使用的 cookie 應該是 1、2、3 等,而不是隨機十六進位地址。它也會讓兩個不同樹狀結構的字串比較變得複雜。

-banner

還原預設旗幟行為。

-banneris => subref

TBC:一個掛接點(以及設定它的選項),供使用者提供的函式產生符合使用者需求的旗幟。這並非理想,因為呈現狀態變數(這是 concise.t 中自然候選的用途)對使用者來說是不可用的。

選項黏著性

如果在程式中呼叫 Concise 超過一次,您應該知道這些選項是「黏著性」的。這表示您在第一次呼叫中提供的選項會記住第二次呼叫,除非您重新指定或變更它們。

縮寫

簡潔的樣式使用符號傳達最大資訊,並將雜訊降到最低(例如十六進位地址)。只要稍微練習一下,您就可以開始在樹狀結構中看見花朵,而不再只是樹枝。

OP 類別縮寫

這些符號會出現在 op 名稱之前,並指出表示 Perl 程式碼中 op 的 B:: 名稱空間。

0      OP (aka BASEOP)  An OP with no children
1      UNOP             An OP with one child
+      UNOP_AUX         A UNOP with auxillary fields
2      BINOP            An OP with two children
|      LOGOP            A control branch OP
@      LISTOP           An OP that could have lots of children
/      PMOP             An OP with a regular expression
$      SVOP             An OP with an SV
"      PVOP             An OP with a string
{      LOOP             An OP that holds pointers for a loop
;      COP              An OP that marks the start of a statement
#      PADOP            An OP with a GV on the pad
.      METHOP           An OP with method call info

OP 旗標縮寫

OP 旗標可能是公開的或私密的。公開旗標會以一致的方式變更每個 opcode 的行為,並以 0 個或多個單一字元表示。

v      OPf_WANT_VOID    Want nothing (void context)
s      OPf_WANT_SCALAR  Want single value (scalar context)
l      OPf_WANT_LIST    Want list of any length (list context)
                        Want is unknown
K      OPf_KIDS         There is a firstborn child.
P      OPf_PARENS       This operator was parenthesized.
                         (Or block needs explicit scope entry.)
R      OPf_REF          Certified reference.
                         (Return container, not containee).
M      OPf_MOD          Will modify (lvalue).
S      OPf_STACKED      Some arg is arriving on the stack.
*      OPf_SPECIAL      Do something weird for this op (see op.h)

如果為 opcode 設定任何私密旗標,則會在「/」之後顯示。

8  <@> leave[1 ref] vKP/REFC ->(end)
7     <2> sassign vKS/2 ->8

它們是 opcode 特定的,而且出現的頻率低於公開旗標,因此它們以簡短的助記符號表示,而不是單一字元;請參閱 B::Op_private 和 regen/op_private 以取得更多詳細資料。

請注意,「/」之後的數字通常表示引數的數量。在上述的 sassign 範例中,OP 會採用 2 個引數。這些值有時會在執行階段使用:特別是,MAXARG 巨集會使用它們。

格式設定規格

對於每種列風格(「簡潔」、「簡短」、「linenoise」等),有 3 個格式規格控制 OP 的呈現方式。

第一個是「預設」格式,在基本模式和執行模式中都用於列印所有操作碼。第二個,goto 格式,在執行模式中遇到分支時使用。它們不是真正的操作碼,而是插入的,看起來像一個閉合的大括號。樹狀格式是樹狀特定的。

當一行被渲染時,正確的格式規範會被複製並掃描以下項目;資料會被替換,並對每個被渲染的操作碼進行其他操作,例如基本縮排。

可能有 3 種類型的項目被填入;特殊模式、# 變數和逐字文字,逐字文字會被逐字複製。(是的,這是一組 s///g 步驟。)

特殊模式

這些項目是執行縮排和從備選項中選擇文字所使用的基本元素。

(x(exec_text;basic_text)x)

在執行模式中產生 exec_text,或在基本模式中產生 basic_text

(*(text)*)

為每個縮排層級產生一個 text 的副本。

(*(text1;text2)*)

產生比縮排層級少一個副本的 text1,如果縮排層級大於 0,則後接一個 text2 的副本。

(?(text1#varText2)?)

如果 var 的值為 true(不為空或零),則產生被 text1Text2 包圍的 var 的值,否則不產生任何東西。

~

任何數量的波浪符號和周圍的空白都會被壓縮成一個空格。

# 變數

這些 # 變數表示您可能想要作為渲染一部分的操作碼屬性。「#」旨在作為一個私有符號;# 變數的值會內插到樣式行中,就像「read $this」一樣。

這些變數有 3 種形式

#var

假設操作碼存在一個名為「var」的屬性,並將其內插到渲染中。

#varN

產生 var 的值,左對齊以填滿 N 個空格。請注意,這表示雖然您可以有「foo」和「foo2」屬性,但您無法渲染「foo2」,但您可以使用「foo2a」。您最好不要依賴這種行為繼續前進 ;-)

#Var

這種 #var 的 ucfirst 形式會產生一個標籤值形式的自身以供顯示;它將「#Var」轉換為「Var => #var」樣式,然後按照上述說明進行處理。(重要說明:# 變數不能用於條件填充,因為在檢查 #Var 的值之後才會執行 => #var 轉換)。

以下變數由 B::Concise「定義」;當它們用於樣式中時,它們各自的值會插入到每個操作碼的渲染中。

標準樣式僅使用其中一些,其他則提供給您深入了解 optree 機制,如果您想要新增一個使用它們的新樣式(請參閱下方的 "add_style"),您也可以使用 "add_callback" 新增新的。

#addr

OP 的位址,以十六進位表示。

#arg

OP 的 OP 特定資訊(例如 SVOP 的 SV、LOOP 的非區域性離開指標等),以括號括住。

#class

OP 的 B 確定的類別,全部大寫。

#classsym

單一符號,縮寫 OP 的類別。

#coplabel

OP 所在的陳述式或區塊的標籤(若有)。

#exname

OP 的名稱,或 'ex-foo'(如果 OP 是曾經是 foo 的 null)。

#extarg

OP 的目標,或 null OP 的空值。

#firstaddr

OP 的第一個子項目的位址,以十六進位表示。

#flags

OP 的旗標,縮寫為一系列符號。

#flagval

OP 的旗標數值。

#hints

COP 的提示旗標,如果可能,以縮寫名稱呈現。如果不是 COP,則為空字串。以下是使用的符號

 $ strict refs
 & strict subs
 * strict vars
x$ explicit use/no strict refs
x& explicit use/no strict subs
x* explicit use/no strict vars
 i integers
 l locale
 b bytes
 { block scope
 % localise %^H
 < open in
 > open out
 I overload int
 F overload float
 B overload binary
 S overload string
 R overload re
 T taint
 E eval
 X filetest access
 U utf-8

 us      use feature 'unicode_strings'
 fea=NNN feature bundle number
#hintsval

COP 的提示旗標數值,或如果這不是 COP,則為空字串。

#hyphseq

OP 的順序號碼,或如果沒有順序號碼,則為連字號。

#label

如果 OP 是執行模式中其中一個的目標,則為 'NEXT'、'LAST' 或 'REDO',否則為空值。

#lastaddr

OP 的最後一個子項目的位址,以十六進位表示。

#name

OP 的名稱。

#NAME

OP 的名稱,全部大寫。

#next

OP 的下一個 OP 的順序編號。

#nextaddr

OP 的下一個 OP 的地址,以十六進位表示。

#noise

OP 名稱的一或兩個字元縮寫。

#private

OP 的私人標記,如果可能的話,會以縮寫名稱呈現。

#privval

OP 的私人標記的數值。

#seq

OP 的順序編號。請注意,這是由 B::Concise 產生的順序編號。

#opt

OP 是否已由 peephole 最佳化器最佳化。

#sibaddr

OP 的下一個最年輕兄弟的地址,以十六進位表示。

#svaddr

OP 的 SV 的地址(如果有 SV 的話),以十六進位表示。

#svclass

OP 的 SV 的類別(如果有 SV 的話),全部大寫(例如,'IV')。

#svval

OP 的 SV 的值(如果有 SV 的話),以簡短的人類可讀格式表示。

#targ

OP 的 targ 的數值。

#targarg

OP 的 targ 參照的變數名稱(如果有),否則為字母 t,後接 OP 的 targ(以十進位表示)。

#targarglife

#targarg 相同,但後面會接 COP 順序編號,用於界定變數的生命週期(或對於開放範圍中的變數,則為 'end')。

#typenum

OP 的類型數值,以十進位表示。

One-Liner Command 技巧

perl -MO=Concise,bar foo.pl

僅呈現 foo.pl 中的 bar()。若要查看 main,請移除 ',bar'。若要同時查看,請新增 ',-main'

perl -MDigest::MD5=md5 -MO=Concise,md5 -e1

將 md5 識別為 XS 函式。需要匯出,以便 BC 能在 main 中找到它。

perl -MPOSIX -MO=Concise,_POSIX_ARG_MAX -e1

將 _POSIX_ARG_MAX 識別為常數子程式,最佳化為 IV。儘管 POSIX 在不同平台上並非完全一致,但這很可能存在於幾乎所有平台中。

perl -MPOSIX -MO=Concise,a -e 'print _POSIX_SAVED_IDS'

這會呈現一個列印陳述式,其中包含對函式的呼叫。這與呈現一個包含 use 呼叫和單一陳述式的檔案相同,但下一個狀態 ops 中出現的檔案名稱除外。

perl -MPOSIX -MO=Concise,a -e 'sub a{_POSIX_SAVED_IDS}'

這與前一個非常類似,只有前兩個 ops 不同。這個子程式呈現更具代表性,因為單一主程式將有許多子程式。

perl -MB::Concise -e 'B::Concise::compile("-exec","-src", \%B::Concise::)->()'

這會呈現 B::Concise 套件中所有函式,並附有原始程式碼行。它避開 O 架構,以便可以將 stashref 直接傳遞給 B::Concise::compile()。請參閱 -stash 選項,以取得呈現套件的更方便方式。

在 O 架構之外使用 B::Concise

B::Concise 的常見(也是原始)用法是針對範例中提供的簡單程式碼進行命令列呈現。但您也可以從程式碼中使用 B::Concise,並直接且重複呼叫 compile()。這樣一來,您可以避免 O.pm 的編譯時間作業,甚至使用偵錯器逐步執行 B::Concise::compile() 本身。

執行此操作後,您可以透過新增新的呈現樣式,以及選擇性地新增回呼常式(如果從這些(剛新增的)樣式中參照這些常式,則會填入新的變數)來變更 Concise 輸出。

範例:變更 Concise 呈現

use B::Concise qw(set_style add_callback);
add_style($yourStyleName => $defaultfmt, $gotofmt, $treefmt);
add_callback
  ( sub {
        my ($h, $op, $format, $level, $stylename) = @_;
        $h->{variable} = some_func($op);
    });
$walker = B::Concise::compile(@options,@subnames,@subrefs);
$walker->();

set_style()

set_style 接受 3 個引數,並更新組成線性樣式的三個格式規範(基本執行、轉到、樹狀結構)。不過,它有一個小缺點;它不會以新名稱註冊樣式。如果您多次呈現並切換樣式,這可能會成為問題。因此,您可能更喜歡使用 add_style() 和/或 set_style_standard()。

set_style_standard($name)

這會還原標準線性樣式之一:terseconciselinenoisedebugenv,使其生效。它也接受先前使用 add_style() 定義的樣式名稱。

add_style ()

此子常式接受新的樣式名稱和三個樣式引數(如上所述),並建立、註冊和選取新命名的樣式。重新新增樣式會造成錯誤;請呼叫 set_style_standard() 來切換多種樣式。

add_callback ()

如果您的新鑄造樣式參照任何新的 # 變數,您需要定義一個回呼子常式,它會填入(或修改)那些變數。然後,它們可供您在所選樣式中使用。

回呼會針對 Concise 拜訪的每個操作碼呼叫,順序與它們被新增的順序相同。每個子常式會傳遞五個參數。

1. A hashref, containing the variable names and values which are
   populated into the report-line for the op
2. the op, as a B<B::OP> object
3. a reference to the format string
4. the formatting (indent) level
5. the selected stylename

若要定義自己的變數,只需將它們新增到雜湊,或視需要變更現有值。層級和格式會傳遞為純量參考,但不太可能需要變更或甚至使用它們。

執行 B::Concise::compile()

compile 接受在 "OPTIONS" 中描述的選項,以及引數,它們可能是程式碼參考或子常式名稱。

它會建構並傳回一個 $treewalker 程式碼參考,當呼叫時,它會遍歷或走訪,並將所給引數的 optree 呈現到 STDOUT。您可以重複使用它,並且每次都可以變更所使用的呈現樣式;之後,程式碼參考會以新的樣式呈現。

walk_output 讓您可以將列印目的地從 STDOUT 變更為另一個開啟的檔案句柄,或傳遞為參考的字串(除非您使用 -Uuseperlio 建立 perl)。

my $walker = B::Concise::compile('-terse','aFuncName', \&aSubRef); # 1
walk_output(\my $buf);
$walker->();			        # 1 renders -terse
set_style_standard('concise');	# 2
$walker->();  		        # 2 renders -concise
$walker->(@new);			# 3 renders whatever
print "3 different renderings: terse, concise, and @new: $buf\n";

當呼叫 $walker 時,它會遍歷在建立時提供的子常式,並使用目前的樣式呈現它們。您可以在之後以多種不同的方式變更樣式

1. call C<compile>, altering style or mode/order
2. call C<set_style_standard>
3. call $walker, passing @new options

傳遞新選項給 $walker 是在任何預先定義的樣式之間變更的最簡單方式(您新增的那些會自動被辨識為選項),而且是變更呈現順序而不再次呼叫 compile 的唯一方式。不過請注意,呈現狀態仍會在多個 $walker 物件之間共用,所以它們仍必須以協調的方式使用。

B::Concise::reset_sequence()

這個函式(未匯出)讓您重設順序號碼(請注意它們的編號是任意的,其目標是讓人們容易閱讀)。它的目的主要是支援測試,亦即比較兩個相同的匿名子常式的簡潔輸出(但不同的執行個體)。如果沒有重設,B::Concise 會看到它們是不同的 optree,並在輸出中產生不同的順序號碼。

錯誤

呈現錯誤(不存在的函式名稱、不存在的 coderef)會寫入 STDOUT,或您透過 walk_output() 設定的任何地方。

使用各種 *style* 呼叫時發生錯誤,以及 walk_output() 的錯誤參數,會導致 die()。如果您希望捕捉這些錯誤並繼續處理,請使用 eval。

作者

Stephen McCamant,<smcc@CSUA.Berkeley.EDU>。