原創|使用教程|編輯:鄭恭琳|2017-12-29 16:39:21.000|閱讀 1432 次
概述:讓我們繼續在大藍景的背景下討論算法中的語法,從解析器、解析樹、抽象語法樹等開始。
# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
相關鏈接:
在解析的語境中,解析器既可以引用執行整個過程的軟件,也可以引用解析器來分析詞法分析器產生的tokens。這只是解析器處理整個解析過程中最重要和最困難部分的事實的結果。最重要的是,我們指的是用戶最關心的事情,而且會真正看到的。實際上,正如我們所說的,詞法分析器是幫助解析器工作的助手。
無論如何,解析器的輸出是代碼的有組織的結構——通常是一棵樹。樹可以是分析樹或抽象語法樹。它們都是樹,但是它們代表實際編寫的代碼和解析器定義的中間元素的密切程度不同。兩者之間的界限有時可能會模糊。我們將在后面的段落中看到他們的分歧。
選擇樹的形式是因為在不同的細節層次上處理代碼是一種簡單而自然的方式。例如,C#中的一個類有一個正文,這個正文由一個語句組成,block語句是一對大括號中的語句列表,依此類推……
解析器是編譯器或解釋器的基本組成部分,當然也可以是各種其他軟件的一部分。例如,在本文中,我們分析了C#文件以生成圖表。
解析器只能檢查一段代碼的語法正確性,但是編譯器可以在檢查同一段代碼的語義有效性的過程中使用它的輸出。
我們來看一個在語法上正確的代碼,但是在語義上是不正確的。
int x = 10 int sum = x + y
該問題是一個變量(y)永遠不會被定義,因此,如果執行,程序將會失敗。然而,解析器沒有辦法知道這一點,因為它沒有跟蹤變量——它只是看代碼的結構。
編譯器通常會首先遍歷分析樹,并保留所有已定義變量的列表。然后,它再次遍歷分析樹,并檢查所使用的變量是否都被正確定義。但在這個例子中,他們不是,它只會拋出一個錯誤。這是分析樹也可以用來檢查編譯器的語義的一種方法。
無掃描解析器,或者更少見的是無詞法解析器,是執行token化(即tokens中字符序列的轉換)和適當解析的解析器。從理論上講,有一個單獨的詞法分析器和解析器是可取的,因為它允許更清晰地分離目標和創建更多的模塊化分析器。
無語法解析器對于語法分析器和解析器之間的明確區分是困難的或不必要的語言來說是更好的設計。一個例子是標記語言的解析器,其中特殊的標記被插入到文本的海洋中。它還可以方便地處理傳統的文字閱讀困難的語言,如C語言。這是因為無掃描解析器可以更輕松地處理復雜的符號。
理論上講,當代解析是為了處理真正的編程語言而設計的。在實踐中,一些真正的編程語言面臨著挑戰。使用正常的解析生成器工具解析它們可能會更困難。
分析工具傳統上被設計為處理上下文無關的語言,但是有時候,這些語言是上下文敏感的。這可能是為了簡化程序員的生活,或者僅僅是因為糟糕的設計。我記得讀過一個程序員的故事,認為它可以在一個星期內為C語言生成一個解析器,但是后來發現了這么多的角落案例,一年之后,他仍然在為它工作。
上下文敏感元素的一個典型例子是軟關鍵字,即可以在某些地方被認為是關鍵字的字符串,否則可以被用作標識符。
空白在某些語言中起著重要的作用。最著名的例子是Python,其中語句的縮進指示它是否是某個代碼塊的一部分。
在大多數地方,空格是不相關的——即使在Python中,單詞或關鍵字之間的空格也不重要。真正的問題是用于識別代碼塊的縮進。處理這個問題最簡單的方法是檢查行開頭的縮進,并在合適的標記中進行轉換,即當縮進從前一行改變時創建一個token。
在實踐中,詞法分析器中的自定義函數在縮進增大或減小時產生INDENT和DEDENT符號。這些tokens在類似C的語言中起著花括號的作用——它們表示代碼塊的開始和結束。
這種方法使得上下文敏感而不是上下文無關。這使解析變得復雜,你通常不想這樣做,但是你必須這樣做。
另一個常見問題是處理語言實際上可能包含幾個不同的語法的事實。換句話說,相同的源文件可能包含遵循不同語法的代碼段。在解析的上下文中,相同的源文件包含不同的語言。最著名的例子可能是C或C ++預處理器,它實際上是一個相當復雜的語言,可以神奇地出現在任何隨機的C代碼中。
一個更容易處理的情況是注釋,這是目前在許多當代編程語言。除此之外,它們可以用于在代碼到達編譯器之前對其進行處理。他們可以命令注釋處理器以某種方式轉換代碼;例如,在執行注釋之前執行特定的功能。他們更容易管理,因為他們只能出現在特定的地方。
空懸else是鏈接到if-then-else語句的常見問題。由于else子句是可選的,所以一系列的if語句可能是不明確的,例如:
if one then if two then two else ???
目前還不清楚其他人是否屬于第一或第二。
公平地說,這在很大程度上是一個語言設計的問題。大多數解決方案并沒有真正復雜的解析這么多,例如,當它包含else子句時,需要使用endif或要求使用塊來限定if語句。
但是,也有一些語言沒有提供解決方案。也就是說,它們被模糊地設計——例如,你猜對了,C。傳統的方法是將else與最近的if語句相關聯,這使得解析上下文敏感。
有兩個術語是相關的,有時它們可以互換使用:解析樹和抽象語法樹(AST)。從技術上講,解析樹也可以被稱為具體語法樹(CST),因為它應該更具體地反映輸入的實際語法,至少與AST相比。
從概念上講,它們非常相似。他們都是樹型;有一個根節點代表整個源代碼。根具有包含代表越來越小的代碼的子樹的子節點,直到單個tokens(終端)出現在樹中。
不同之處在于抽象層面。一個解析樹可能包含程序中出現的所有tokens,也可能包含一組中間規則。相反,AST是分析樹的拋光版本,其中只保留了與理解代碼有關的信息。我們將在下一節看到一個中間規則的例子。
有些信息可能在AST和分析樹中都不存在。例如,評論和分組符號(即括號)通常不被表示。像注釋這樣的東西對程序來說是多余的,分組符號是由樹的結構隱式定義的。
解析樹是代碼更接近具體的語法。它顯示了解析器實現的許多細節。例如,通常,每個規則對應于節點的特定類型。解析樹通常由用戶在AST中轉換,可能需要解析器生成器的幫助。通用幫助允許您在語法中注釋某些規則,以便從生成的樹中排除相應的節點。如果只有一個分支,那么另一個就是折疊某些節點的選項。
這是有道理的,因為解析樹更容易為解析器生成,因為它是解析過程的直接表示。但是,通過程序的步驟,AST更簡單,更容易處理。它們通常包括您可能想要在樹上執行的所有操作:代碼驗證、解釋、編譯等。
我們來看一個簡單的例子來展示一個分析樹和一個AST的區別。我們先來看一個例子語法。
在這個語法中,我們可以使用符號加(+)或字符串加運算符來定義求和(sum)。想象一下,你必須解析下面的代碼。這些可能是由此產生的分析樹和抽象語法樹。
在AST中,特定操作員的指示消失,剩下的就是要執行的操作。特定的運算符是中間規則的一個例子。
解析器的輸出是一棵樹,但樹也可以用圖形方式表示。這是為了讓開發者更容易理解。一些解析生成器工具可以用DOT語言輸出文件,DOT語言是用來描述圖形的一種語言(樹是一種特殊的圖形)。然后這個文件被送到一個程序,該程序可以從這個文本描述(即)開始創建一個圖形表示。
讓我們看看基于前面的總和示例的DOT文本。
digraph sum { sum -> 10; sum -> 21; }
適當的工具可以創建以下圖形表示。
請繼續關注第4部分。
本站文章除注明轉載外,均為本站原創或翻譯。歡迎任何形式的轉載,但請務必注明出處、不得修改原文相關鏈接,如果存在內容上的異議請郵件反饋至chenjj@fc6vip.cn