摘要:詞法分析對構成源程序的字符流進行掃描然后根據構詞規則識別單詞也稱單詞符號或符號。語義分析是編譯過程的一個邏輯階段語義分析的任務是對結構上正確的源程序進行上下文有關性質的審查進行類型審查,審查抽象語法樹是否符合該編程語言的規則。
1. 文章的內容和主題
我對編譯器的深入了解起源于一條推特中的問題:Angular是如何用Angular預先編譯器(AOT)對靜態代碼進行解析工作的。在進行一些debugging后,我發現AOT非常依賴TypeScript編譯器,所以我開始對它進行反編譯(reverse-engineer)。有趣的是,大部分編譯器都使用一樣的規則,這些規則被廣泛的認為是編譯器理論。在理解編譯器的內部機制時,對這些理論一窺究竟是非常有必要的。
接下來我將描述對每個編譯器的第一階段都非常重要的詞法分析
這篇文章盡量少的參入理論和教條主義,不過大部分依然是理論性的。在最后一章,我將展示TypeScript scanner是如何工作的并提供相關的鏈接。
TypeScript 語法是基于ECMAScript 規范的,我希望讀者們能夠保持足夠的好奇心查看文章中的鏈接,并且熟練掌握這些規范。 如果你能做到這些,你就會知道這些語法,并且在JavaScript的新特新被寫入MDN之前就學習到了。如果你讀完了這篇文章,可以通過理解裝飾器(decorator)規范里描述的裝飾器的語法特性來測試自己。
這篇文章比較長,因此你不需要一次性全部讀完。一點一點的讀這篇文章,有足夠的時間記住文章里的內容。如果你一直想知道ECMAScript 規范或者想弄清楚編譯器是如何工作的,那就開始讀這篇文章吧!
編譯器就是把一個用一種編程語言寫成的程序編譯成另一種語言的電腦程序。編譯器首先需要理解原來的輸入的編程語言 ,然后把它編譯成目標語言。由于這兩種不同的特性,需要把編譯器的功能分成兩大塊:前端(a front-end)和后端(a back-end.)。前段處理輸入源程序,后端處理輸出目標代碼。
編譯器可以看成是一個由多個階段構成的流水線結構,上一步的結果輸入到下一步,然后下一步再優化代碼并且轉化成這一步的需要的代碼,最后又傳給下一步。前端包括三個主要的階段就是詞法分析,語法分析和語義分析。
詞法分析對構成源程序的字符流進行掃描然后根據構詞規則識別單詞(也稱單詞符號或符號)。
語法分析是編譯過程的一個邏輯階段。語法分析的任務是在詞法分析的基礎上將單詞序列組合成各類語法短語,并生成抽象語法書(AST).語法分析程序判斷源程序在結構上是否正確。
語義分析是編譯過程的一個邏輯階段. 語義分析的任務是對結構上正確的源程序進行上下文有關性質的審查, 進行類型審查,審查抽象語法樹是否符合該編程語言的規則。
這篇文章主要目的在于介紹詞法分析。
3. 形式語言的語法在我們開始談詞法分析之前,我們需要聊一點自然語言和形式語言(Formal language
是用精確的數學或機器可處理的公式定義的語言)和他們的語法。像英語和法語這樣的自然語言通常用于日常交流,而且自然發展而來的。形式語言,一方面。是由人類設計用來特殊的用途的——比如編程語言用來表示計算機的語言,數學符號表示數字之間的關系等等。
無論是自然語言還是形式語言都可以用語法來描述。語法指該語言中的句子、短語、詞匯的邏輯、結構特征以及構成方式,而語法包括對語法規律進行的總結描述或對語言使用的規范或限定。自然語言的語法是非常復雜的,并通過經驗主義的方式來研究的。另一方面,形式語言通常都是簡單的,并根據我們的需求定義的。取決于我們可以通過怎樣的方式分辨幾種語法來定義規則。
詞法描述了一種語言的詞匯結構,就是語言中每個單詞(符號)。比如, 和 d都是JavaScript 的字母,但是語法并沒有定義在正常語句中后面跟d的規則,所以當你執行d的代碼的時候,我們會得到無效符號的語法錯誤:
d Uncaught SyntaxError: Invalid or unexpected token
語法定義了語句的結構,就是單詞符號在一條語句中組合方式。例如,JavaScript詞法定義的 var和const,在語法中沒有var后面跟著const,所有當下面這樣使用時就會出現語法錯誤:
var const Uncaught SyntaxError: Unexpected token const
上面的結構根據ECMAScript語法規范是無效的,所以編譯器并不會識別var后面跟著const這樣的語句。
3. 詞法分析詞法分析是編譯器在處理源代碼時三個階段中的第一個階段。詞法分析的作用就是把源代碼分解成被稱為是標記(token)的子字符串,并且對每個標記進行分類,進行詞法分析的程序或者函數叫作詞法分析器(lexical
analyzer,簡稱lexer),也叫掃描器(scanner)。它們讀取輸入字符流,按照詞法生成標記,這個過程叫做標記化(tokenization)。如果一組字符串沒有匹配的規則掃描器就會報錯。這就是我們例子中d出現報錯的原因。
掃描器對每一個被識別的標記都會按語法分配一個語句范疇(syntactic category)。這個范疇或者說ECMAScript的標記種類非常廣泛,包括但不限于識別碼(Identifier),數字文字(NumericLiteral),字符串文字(StringLiteral )和各種不同的像const、let、if這樣的關鍵字。
所以詞法分析階段的輸出通常是由帶有對應類型的標記和帶有詞位的子字符串組成的隊列:
{class: SyntaxKind.ConstKeyword, lexeme: ‘const’}
如果你對ECMAScript 定義的標記類型的感興趣,可以查看SyntaxKind的列舉。
詞法分析器可以掃描整個源代碼然后輸出完整的標記隊列,或者緩慢的掃描一次輸出一個標記。掃描器把在解析前將整個源代碼轉化成標記序列而消耗不必要的內存是不常見的。所以掃描器只有在代碼需要被解析時才工作,TypeScript 掃描器也一樣。TS掃描器在另一方面也非常有趣。JavaScript 語法只定義了一些語言結構,如常用表達和模板文字,這將導致解析的歧義,所以需要掃描器根據解析上下文來識別不同的字符集。
由于解析上下文是由解析器定義的,當請求一個標記時,TS掃描器可以被稱為解析驅動。我會在多個目標符號部分詳解這個復雜的問題。
我們用JavaScript在定義一個變量這個例子來演示語法規則是如何工作的。在JavaScript中,我們可以像下面這樣用const來定義一個變量:
const v = 3
我們簡單的假設初始值是一個數字。當你看這段代碼時,可以清楚的看到const定義了一個變量v,用=給這個變量分配了一個數字3的初始值。
顯然。掃描器并不是這樣工作的。由于ECMAScript 用Unicode 符號定義了程序碼,所以編譯中的這段代碼看起來是這樣的:
c o n s t v = 3 99, 111, 110, 115, 116, 32, 118, 32, 61, 32, 51
Now its job is to split the expression into tokens and categorize them so the following list of tokens is produced:
現在編譯器的工作就是對這段表達式分割成標記,并且對它們進行分類,然后就生成了下面的這組符號:
{class: SyntaxKind.ConstKeyword, lexeme: "const"} {class: SyntaxKind.Identifier, lexeme: "v"} {class: SyntaxKind.EqualsToken, lexeme: "="} {class: SyntaxKind.NumericLiteral, lexeme: "3"}
如果用let替代const第一個標記應為SyntaxKind.LetKeyword。
5.常規語法ECMAScript 就是解析用Unicode 的符號作為標記的規則的正常語法。根據Chomsky對語法的分類,常規語法是最受約束的并且最缺乏表達能力的語法。它僅適合于描述標記是如何被組合的,但不能描述句子的結構。然而,一個語法規則越不自由越容易描述和解析。因為我們如此關心定義和解析標記,所以這是一個理想的語法。
這個系列的下一篇文章我們將會了解上下文無關文法(context-free grammar)。這類語法允許遞歸的結構,并且用來定義程序的結構。
值得注意的是,很多教育資料在解釋掃描器并不用常規語法,而是用常規表達定義定義常規規范。但是,由于ECMAScript 用了常規語法,我會在這篇文章中解釋它。
Now, let’s try to see how we can construct the grammar and the rules that help TypeScript identify the list of tokens I showed above. Here it is again and we need to define rules for recognizing each token in the statement:
現在,讓我們嘗試看看我們怎樣構建語法和規則來幫助TypeScript我在上面列出的標記。下面又是我們需要在表達式中識別的每一個符號:
const v = 3 {class: SyntaxKind.ConstKeyword, lexeme: "const"} {class: SyntaxKind.Identifier, lexeme: "v"} {class: SyntaxKind.EqualsToken, lexeme: "="} {class: SyntaxKind.NumericLiteral, lexeme: "3"}
語法中的每一項規則是用生產方式來定義的。生產方式是可以遞歸生成新的符號序列的替代規則。在JavaScript 中我們可以用const或者let來聲明一個變量,于是我們可以用關鍵字符號定義下面的規則:
Keyword :: const let
這個關鍵字符號的規則有兩個結果,這兩個結果表示符號關鍵字可以是let或者const字符串。合成變量的關鍵字被稱作非終結的,意味著他有結果并且可以被替代。這個替代性通常被認為能被分解成更小的單位。const和let所產生的結果被稱為終結符,不能被分解成更小的單位。沒有結果的終端符號在源碼中找到。非終結符是可以被取代的符號。一個形式文法中必須有一個起始符號;這個起始符號屬于非終結符的集合。ECMAScript定義了許多其他的非終結符關鍵字例如:if, else, for, do, while, function, class等等。
可以用下面的任意布局來定義ECMAScript語法:
non_terminal_symbol :: symbol1 symbol2 (production rule 1, Symbol1 followed by Symbol2) symbol3 symbol4 (production rule 2, Symbol3 followed by Symbol4)
在::左邊的稱作左邊部分,右邊的稱為右邊部分。對于常規的和上下文無關語法非終結符只能在左邊,右邊可以是終結符也可以是非終結符
然而對于常規語法,只能是下面的一種:
只有終結字符的
或者有終結字符和單個非終結字符,并且終結字符在開始或者結尾。
non_terminal_symbol :: terminal_symbol non_terminal_symbol :: terminal_symbol non_terminal_symbol (right-linear) non_terminal_symbol :: non_terminal_symbol terminal_symbol (left-linear)
上下文無關語法更加寬松,允許任意數量的終結字符和非終結字符在右邊。常規語法和上下文無關語法都可以有任意數量的符號在左邊:
non_terminal_symbol :: production rule 1 production rule 2 ... production rule n
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/100628.html
摘要:在這里,詞法解析器應用的規則即為詞匯語法的定義,語法解釋器應用的規則即為表達式語句聲明和函數等的定義。如何編寫簡單的實踐篇 什么是parser? 簡單的說,parser的工作即是將代碼片段轉換成計算機可讀的數據結構的過程。這個計算機可讀的數據結構更專業的說法是抽象語法樹(abstract syntax tree),簡稱AST。AST是代碼片段具體語義的抽象表達,它不包含該段代碼的所有細...
摘要:不包括作為其嵌套函數的被解析的源代碼。作用域鏈當代碼在一個環境中執行時,會創建變量對象的一個作用域鏈。棧結構最頂層的執行環境稱為當前運行的執行環境,最底層是全局執行環境。無限制函數上下文。或者拋出異常退出一個執行環境。 前言 其實規范這東西不是給人看的,它更多的是給語言實現者提供參考。但是當碰到問題找不到答案時,規范往往能提供想要的答案 。偶爾讀一下能夠帶來很大的啟發和思考,如果只讀一...
摘要:關鍵字計算為當前執行上下文的屬性的值。毫無疑問它將指向了這個前置的對象。構造函數也是同理。嚴格模式無論調用位置,只取顯式給定的上下文綁定的,通過方法傳入的第一參數,否則是。其實并不屬于特殊規則,是由于各種事件監聽定義方式本身造成的。 this 是 JavaScript 中非常重要且使用最廣的一個關鍵字,它的值指向了一個對象的引用。這個引用的結果非常容易引起開發者的誤判,所以必須對這個關...
摘要:如果形參有設置默認值,第二個就被建立,他針對的是函數體內的聲明我們可以形象的理解為這是一個除了函數作用域和塊級作用域之外的第三作用域。 開門見山,我們來看看下面這個有趣的例子 showImg(http://ogitl0zvo.bkt.clouddn.com/public/16-11-12/77445738.jpg); ?對于上面這種用var的聲明方式,無論x的默認值為什么,只要形參中出...
閱讀 694·2023-04-25 22:50
閱讀 1529·2021-10-08 10:05
閱讀 986·2021-09-30 09:47
閱讀 1919·2021-09-28 09:35
閱讀 822·2021-09-26 09:55
閱讀 3414·2021-09-10 10:51
閱讀 3431·2021-09-02 15:15
閱讀 3296·2021-08-05 09:57