主頁 > 後端開發 > 04.從0實作一個JVM語言系列之語意分析器-Semantic

04.從0實作一個JVM語言系列之語意分析器-Semantic

2021-03-03 06:24:24 後端開發

從0實作JVM語言之語意分析-Semantic

原始碼github, 如果這個系列文章對您有幫助, 希望獲得您的一個star

本節相關語意分析package地址

致親愛的讀者:

    個人的文字組織和寫文章的功除錯實一般, 寫的也比較趕時間, 所以系列文章的文字可能比較粗糙,
難免有詞不達意或者寫的很迷惑抽象的地方 

    如果您看了有疑問或者覺得我寫的實在亂七八糟, 這個很抱歉, 確實是我的問題, 您如果有不懂的地方
的地方或者發現我的錯誤(文字錯誤, 邏輯錯誤或者知識點錯誤都有可能), 可以直接留言, 我看到都會回復您!

系列食用方法建議

    由于時間原因, 目前測驗并不完善, 所以推薦如下方式根據您的目的進行閱讀

    如果您是學習用, 建議您先將整個專案clone到本地, 然后把感興趣的章節洗掉, 自己重寫對照著重寫
    書寫完每一步測驗一下能否正常運行(在指定的路徑去讀取原始碼測驗能否編譯成功并在命令列執行

    java Application(類名)

嘗試能否輸出期望結果, 我沒有研究Junit對編譯器輸出class檔案進行測驗, 所以目前可能需要您手動測驗)

    按照以上步驟, 等您將所有模塊重寫一遍, 大概也對這個系列的脈絡有深刻理解了! 如果您重頭開始重寫, 
往往可能由于出現某些低級錯誤導致長時間debug才找得到錯誤, 所以對于初學者, 推薦采用自己補寫替換模塊的
方式

    對于希望貢獻代碼的朋友或者對Cva感興趣的朋友, 歡迎貢獻您的原始碼與見解, 或者對于該系列一些錯誤/
bug愿意提出指正的朋友, 您可以留言或者在github發issue, 我看到后一定及時處理!

語意分析器作業基于語法分析器輸出的抽象語法樹, 通過對該語法樹的分析做進一步的檢查,
回答我們, 也回答代碼生成器最后一個問題:源程式是否符合語意規則. 如果確實存在問題,
那么這段代碼即使翻譯成機器碼(在這里是我們后面要生成的JVM匯編指令), 也不可能執行成功,
必然收到JVM的拒絕執行(讀者可以自己嘗試瞎寫命令然后用jasmin匯編成位元組碼, 看報錯反應, 哈哈),
或者造成一些意想不到的問題, 因此語意分析有問題勢必造成后面的錯誤, 所以語意分析旨在搜集
代碼出現的邏輯錯誤(多數是型別檢查的型別問題), 并指出, 以讓接下來的步驟能正常進行

在這個階段要給出盡可能準確的報錯資訊, 供用戶參考并修改源代碼.
語意分析中最重要的作業便是型別檢查, 此外還會有一些其他的檢查, 例如變數在使用前是否宣告等.

語意分析實作

符號表

語意分析的正常進行少不了符號表的參與. 所謂符號, 程式中的變數、方法、欄位、類都是符號.
符號表存盤了程式中的符號的相關資訊, 這些資訊包括型別、作用域、訪問控制資訊等等, 而且符號表必須非常高效,
因為程式中符號的規模會非常大. 我們的符號表都是采用HashMap, JVM針對HashMap的優化可謂是比較極致,
我們的JDK8后引入了紅黑樹, 對于Map, HotSpot會傾向于將其編譯成本地代碼, 在一些情況下,
其運行效率甚至超過一般的手寫分支判斷

我們的哈希表以符號名字為鍵, 以符號的相關資訊為值, 建立映射.
并按照樹形結構, 組織各個作用域的符號及資訊, 建立全域符號表.

全域符號表的大致結構如下:

// TODO: 樹形圖

+ ClassMap
  + ClassBinding
    + BaseClass
    + FieldMap
      + Field
      + ...
    + MethodMap
      + Method
      + ...
  + ...
+ MethodVariableMap

+ ClassMap

  全域符號表的入口, 直接維護了類名和類的相關資訊的

+ ClassBinding

  存盤了類的相關資訊, 包括父類, 欄位表, 方法表. 其中欄位表和方法表是以名為鍵的映射. 

+ Field

  存盤了欄位的宣告型別

+ Method

  存盤了方法的相關資訊, 包括宣告的回傳型別, 引數的個數及各自型別. 

+ MethodVariableMap

  引數和本地變數表, 是名字和型別的映射. 當分析某個方法的時候, 對于該方法的引數和本地變數的訪問是相當頻繁的,
 因此將他們單獨存盤到某個位置, 分析完畢即銷毀, 優化空間的占用. 

分析思路

本質上說, 所謂“分析”僅僅是語法樹的遍歷而已, 只是附帶上了附加條件, 要求某子樹符合某個要求. 此外我們應當注意到,
型別的宣告、欄位的宣告和方法的宣告, 并沒有任何值得語意分析的地方, 真正值得我們去分析和檢查的是陳述句和運算式,
查看它們是否合法. 因此, 分析程序可簡要分成兩步.

  1. 資訊收集和索引

    對當前語法樹的前幾層進行遍歷, 掃描并存盤類資訊, 各自的欄位和方法相關資訊, 構建全域隨時可用的“全域符號表”,
    該程序僅在語意分析實際進行之前進行一次.

  2. 語意分析和檢查

    收集要分析的方法的引數和變數資訊, 然后順序遍歷方法的每一條陳述句和運算式. 如果找到錯誤, 那么就輸出一條資訊,
    提示用戶在某位置發現何種型別的錯誤, 并盡可能進行恢復, 繼續檢查下文, 在一趟分析中給出盡可能多的錯誤資訊.

對于每個方法, 都執行一遍步驟2, 直到所有的方法都分析過. 如果未發現任何錯誤, 那么進入下一階段, 如果發現問題,
那么在給出所有資訊后, 退出編譯程序.

分析程序

型別檢查

型別檢查是語意分析的重點所在, 如果此處的檢查沒有通過, 那么這個程式必定存在問題, 必定不能運行.
下面通過幾個例子來展示型別檢查的作業細節.

運算式

對于運算式而言, 型別檢查主要分為以下幾類

  • 運算子

    對于單目運算子邏輯非 !主要檢測其運算元是否是前端boolean
    (只有前端用戶才會看到boolean, 在編譯器后端boolean會被處理為int),
    對于雙目運算子如+ - * /型別檢查的步驟是:

    先確認兩側/單側的運算式型別, 然后確認兩側運算式型別是否匹配,
    最后確認當前的運算子能否對該型別進行操作全部沒有問題的話, 才認為該運算式通過了檢查,
    并確認該運算式的值型別(在代碼中運算式的結果值直接取雙目運算子的左邊)
    (在這里, 我們前面的toEnum()方法就派上了用場)

    錯誤語法例子如:

    • 10 + true

      顯然 + 兩側型別不匹配, 型別檢查的給出的資訊是,

      Cva日后將遵循JVM規范, 基本運算可以是范圍小于 int 的整形, 做運算時都強轉為 int
      如 byte char short 型別轉為運算元都視為 int, 編譯器不報錯

      Error: Line 1 Add Expr ression: the type of left is @int, but the type of right is @boolean

    • true < false

      兩側型別是一致的, 但很顯然, 這個比較沒有任何意義, 因此這個運算式也是個錯誤

      Error: Line 1 only numeric can be compared.

    • !200

      只有布林值才能取邏輯非, 因此這個運算式顯然也是非法的

      Error: Line 1 the Expr r cannot calculate to a boolean.

  • 方法呼叫

    Cva目前僅支持實體方法呼叫, 暫不支持靜態方法、方法多載等.

    對于方法呼叫運算式, 型別檢查主要關注形參及實參.
    首先確認形參和實引數量是相等的, 然后確認引數的型別是一一對應的.
    通過檢查后, 運算式的值型別被設定成為該方法的回傳值型別.

    假定本類中有有方法 int compute(int a, int b), 對它的兩種錯誤呼叫如下

    • this.compute(10)

      顯然, 這并不能通過第一步: 對于引數個數的檢查

      Error: Line 1 the count of arguments is not match.

    • this.compute(10, false)

      顯然, 第二個引數的型別不是匹配的

      Error: Line 32 the parameter 2 needs a int, but got a boolean

    • println(Expr expr)

      應當注意到, 在本程式中我們認為write(控制臺寫操作, println, echo, printf)是一個陳述句,
      (內置方法關鍵字)而不是函式呼叫運算式. 這樣做的目的是為了簡化該編譯器開發,
      但其本質依舊是函式呼叫, 因此對于它的型別檢查等同函式呼叫. 寫操作應檢查引數是否為string 或者基本型別,
      因此Expr expr最終應當能求得一個string 或者 整形(目前, 以后完善boolean和浮點數情形

  • 回傳運算式

    這里是確認方法宣告處的回傳型別和實際的回傳型別是匹配的. 首先檢查 return 關鍵字后緊跟的運算式是否有意義,
    然后再確認這個有意義的運算式是否符合回傳型別. 考慮這樣的一個源程式:

    boolean doSomething()
    {
        // Some VarDecls
        // Some Statements
        return 666;
    }
    

    很明顯, 實際回傳型別和宣告的回傳型別不一致, 我們應當給出相應的錯誤提示
    此外, 在Cva中, void回傳型別的方法, 我們允許不顯式return, 也可以在方法結束時 使用return; 陳述句

    Error: Line 3 the return Expr ression's type is not match the method "DoSomething" declared.

  • 識別符號 / 字面量 / this / new cvaIdentifierExpr r()

    這里是討論剩余的幾類特殊的運算式, 變數/欄位參考, 字面量, this關鍵字, 實體化物件運算式.

    • 識別符號

      查找識別符號大致分為兩步, 首先在引數串列/本地變數表查找該識別符號, 若查找失敗, 再去類/基類欄位宣告串列中嘗試查找.
      若最終查找失敗, 則報一個錯.

      Error: Line 1 you should declare "x" before use it.

    • 字面量 / this

      這個種類主要包括常量(數字整形字面量, true ,false), this關鍵字. 應當特別注意this關鍵字,
      它指代當前類的實體, 它的型別自然是該關鍵字所處的類的型別.

    • new cvaIdentifierExpr r()

      應當注意到本程式不支持建構式, 因此每個類只有一個形式上的無參構造器. 除主類外,
      對于其他普通類的宣告順序不做要求. 如果嘗試實體化一個不存在的類, 也會報告錯誤.

      `Error: Line 1 cannot find the declaration of class "XXX".`
      

陳述句

有了前面運算式級的型別檢查作為基礎, 做陳述句的型別檢查就很方便快捷了.

  • write

    上文(writeExpr )已給出解釋

  • if / while / for

    對于這種型別的陳述句, 只需要檢查是否符合對應的規則即可. 例如條件判斷處必須是個布爾型別的運算式

  • {StatementList}

    這種型別的運算式, 按順序輪流進行檢查即可

  • cvaIdentifierExpr r = Expr r;

    賦值陳述句, 只要等號兩側的型別是互相匹配的, 那就允許賦值.

特別注意

應當注意到, 我們之前提到的一直是“型別匹配”, 而不是“型別相等”. 隱含的, 我們允許合法的型別隱式轉換.

其他檢查

在本程式里, 我們還做了另外兩個對于識別符號的檢查:變數/欄位識別符號(CvaIdentifierExpr expr),
類名識別符號(也是identifier). 由于這兩類識別符號存在于不同的符號表里面, 因此本程式可以宣告類似
SomeThing SomeThing; 這樣型別和名稱一樣的變數/欄位,這種宣告是合法的, 在使用時具體的意義取決于這個符號所在位置的語意.

錯誤恢復

前面已經提到, 在這個階段, 我們要在一遍掃描中給出盡可能多的資訊, 因此我們需要實作錯誤恢復功能. 出現的具體錯誤和恢復思想如下

  • 使用了未宣告的變數

    一旦發現某處使用了未使用的變數, 那么會立即給出資訊提示此處發現一個未宣告變數, 但是分析還是要繼續, 于是原地定義該變數是
    一個 unkonwn 型別, 使用該型別, 進行接下來的分析.

  • 運算子型運算式

    例如 true + 10, !200這種型別的錯誤, 我們優先考慮運算子的語意, 例如在我們的程式中, 加減乘必定得出一個整數,
    比較運算必定得出布爾型別的值. 我們假定該運算子被正確使用并得出結果, 然后進行下面的分析.

  • 方法呼叫

    方法呼叫出現了問題, 例如引數不全、引數型別不匹配, 我們給出應當提示用戶的資訊之后, 假定方法正常呼叫, 按照該有的回傳值進行下面的分析.

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/265299.html

標籤:其他

上一篇:C語言學習:在編程中的一些低級錯誤

下一篇:ThinkPHP5權限管理

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more