`simple_spec_init_c` 節點的型別檢查機制深入探討 - benchen2001/matiec GitHub Wiki

simple_spec_init_c 節點的型別檢查機制深入探討

這段程式碼是 matiec 編譯器中 Stage 3 階段的型別檢查邏輯,專門處理變數初始化語句的型別相容性檢查。讓我們深入分析它的工作原理、調用流程和實際應用。

一、程式碼功能說明

print_datatypes_error_c::visit(simple_spec_init_c *symbol) 函數負責檢查 simple_spec_init_c 類型的 AST 節點,這類節點代表 IEC 61131-3 中的變數初始化語句,如:

MyVar : INT := 10;  // 這是一個 simple_spec_init_c 節點

該函數執行三個關鍵檢查:

  1. 型別有效性檢查:確認變數宣告的型別是否有效
  2. 初始值型別相容性檢查:確認初始值的型別與變數型別相容
  3. 最終型別檢查:確保整個初始化語句的型別有效(這是一個安全檢查)

二、調用流程

1. 調用時機

這個函數在 Stage 3 階段被調用,具體流程如下:

main() -> stage3() -> type_safety() -> print_datatypes_error_c(tree_root) -> tree_root->accept()

當 AST 遍歷到 simple_spec_init_c 節點時,會自動呼叫這個函數。

2. 實際調用範例

考慮以下 IEC 61131-3 代碼:

PROGRAM Example
  VAR
    count : INT := 10;    // 合法: INT 與 10 型別相容
    status : BOOL := 5;   // 錯誤: BOOL 與數字 5 型別不相容
    temp : REAL;          // 合法: 沒有初始值
    sensor : WRONG_TYPE;  // 錯誤: 無效型別
  END_VAR
END_PROGRAM

編譯過程中,AST 遍歷器會為上述每個變數宣告呼叫 visit(simple_spec_init_c *symbol)

範例 1: count : INT := 10;

  • symbol->simple_specification->datatypeINT (有效型別)
  • symbol->constant->datatypeINT (整數常數 10)
  • 檢查通過,不報錯

範例 2: status : BOOL := 5;

  • symbol->simple_specification->datatypeBOOL (有效型別)
  • symbol->constant->datatypeINT (整數常數 5)
  • 型別不相容,觸發錯誤: "Initial value has incompatible data type."

範例 3: temp : REAL;

  • symbol->simple_specification->datatypeREAL (有效型別)
  • symbol->constantNULL (沒有初始值)
  • 檢查通過,不報錯

範例 4: sensor : WRONG_TYPE;

  • symbol->simple_specification->datatype 為無效型別
  • 觸發錯誤: "Invalid data type."

三、程式碼深入分析

讓我們逐行分析這個函數:

void *print_datatypes_error_c::visit(simple_spec_init_c *symbol) {
    // 檢查 1: 確認宣告的型別是否有效
    if (!get_datatype_info_c::is_type_valid(symbol->simple_specification->datatype)) {
        // 如果型別無效,報告錯誤
        STAGE3_ERROR(0, symbol->simple_specification, symbol->simple_specification, "Invalid data type.");
    } 
    // 檢查 2: 如果有初始值,確認初始值型別與變數型別相容
    else if (NULL != symbol->constant) {
        if (!get_datatype_info_c::is_type_valid(symbol->constant->datatype))
            STAGE3_ERROR(0, symbol->constant, symbol->constant, "Initial value has incompatible data type.");
    } 
    // 檢查 3: 最終確認整個初始化語句的型別是否有效
    else if (!get_datatype_info_c::is_type_valid(symbol->datatype)) {
        ERROR; // 這應該不會發生,因為如果發生則前面的檢查應該會捕獲
    }
    return NULL;
}

關鍵函數與巨集

  1. get_datatype_info_c::is_type_valid()

    • 這個函數檢查型別是否有效
    • 有效型別必須在先前的 Stage 3 階段 (fill_candidate_datatypesnarrow_candidate_datatypes) 中被正確識別
    • 無效型別可能是未宣告的型別、型別不匹配或型別歧義
  2. STAGE3_ERROR 巨集

    • 格式化錯誤訊息並輸出至標準錯誤
    • 記錄錯誤位置 (文件名、行號、列號)
    • 增加全域錯誤計數 error_count
    • 參數 0 表示錯誤嚴重性 (0 = 最嚴重)
  3. ERROR 巨集

    • 這是一個調試巨集,用於標記不應該發生的情況
    • 在發布版本中可能會觸發中止或記錄嚴重錯誤

四、與其他模組的關係

這個函數與其他 Stage 3 模組密切相關:

  1. fill_candidate_datatypes_c

    • 在型別檢查前填充可能的型別候選列表
    • simple_spec_init_c 節點確定可能的型別
  2. narrow_candidate_datatypes_c

    • 從候選型別列表中選擇最合適的型別
    • 設置 datatype 欄位,供 print_datatypes_error_c 檢查
  3. stage3.cc

    • 協調整個 Stage 3 階段的處理流程
    • 確保型別檢查在適當時機執行

五、實際應用範例

範例: 分析標準 IEC 61131-3 程式

考慮以下程式碼:

FUNCTION_BLOCK AVERAGE
  VAR_INPUT
    RUN : BOOL;
    XIN : REAL;
    N : INT := 100;    // 初始化值型別相容
  END_VAR
  VAR_OUTPUT
    XOUT : REAL;
  END_VAR
  VAR
    SUM : REAL := 0.0;  // 初始化值型別相容
    FIFO : DELAY;       // 沒有初始值
    error_var : INT := TRUE;  // 型別不相容錯誤
  END_VAR
  
  // 函數體...
END_FUNCTION_BLOCK

編譯時,會針對每個變數宣告調用 visit(simple_spec_init_c *symbol)

  • 對於 N : INT := 100;,檢查通過,因為 INT 型別與整數常數 100 相容
  • 對於 SUM : REAL := 0.0;,檢查通過,因為 REAL 型別與實數常數 0.0 相容
  • 對於 FIFO : DELAY;,檢查通過,因為 DELAY 是有效型別且沒有初始值
  • 對於 error_var : INT := TRUE;,檢查失敗,因為 INT 型別與布林常數 TRUE 不相容

會輸出錯誤訊息:

[file.st:15] error: Initial value has incompatible data type.

六、小結

print_datatypes_error_c::visit(simple_spec_init_c *symbol) 函數是 matiec 編譯器中執行型別檢查的關鍵部分,特別針對變數初始化語句。它確保:

  1. 宣告的變數型別是有效的
  2. 初始值(如果有)與變數型別相容
  3. 整個初始化語句的型別檢查無誤

這是 Stage 3 階段(語意分析)的重要組成部分,即使語法正確的程式碼,如果型別不相容,也會在這個階段被檢測並報錯。這種嚴格的型別檢查確保了 IEC 61131-3 程式的型別安全性,防止運行時出現未定義行為。