如何定義並檢出 `var_decl` (變數宣告) 的合法性(2) - benchen2001/matiec GitHub Wiki

好的,我們來分析 iec_bison.yy 如何處理像 enable : BOOL := 10; 這樣的變數宣告,並判斷其「合法性」。

1. 語法分析 (Syntax Analysis) - Bison 的主要工作

Bison 的首要任務是檢查這行程式碼是否符合 var_declaration語法結構。它會根據你在 iec_bison.yy 中定義的規則來解析 token 序列。

假設相關規則類似於上一回答中的:

var_declaration:
      identifier_list T_COLON type_spec location_spec simple_spec_init T_SEMICOLON
      { /* 動作代碼 */ }
    ;

simple_spec_init:
      /* empty */
      { $$ = NULL; }
    | T_ASSIGN constant_expression // 或 expression
      { $$ = process_initialization($2); }
    ;

// ... 其他相關規則 ...

對於輸入 enable : BOOL := 10;,Bison 的解析過程大致如下:

  1. enable: Lexer 提供 T_IDENTIFIER token。Bison 匹配 identifier_list 規則 (基礎情況)。
  2. :: Lexer 提供 T_COLON token。Bison 匹配規則中的 T_COLON
  3. BOOL: Lexer 提供代表 BOOL 的 token (可能是 T_BOOL 或通用的 TYPE_IDENTIFIER)。Bison 匹配 type_spec 規則 (例如匹配到 simple_type_name)。
  4. (無 AT): Bison 預期 location_spec。由於下一個 token 是 := 而不是 AT,它會匹配 location_spec 的空 (epsilon) 規則。
  5. :=: Lexer 提供 T_ASSIGN token。Bison 匹配 simple_spec_init 規則中的 T_ASSIGN 部分。
  6. 10: Lexer 提供 INTEGER_LITERAL token (值為 10)。Bison 預期 constant_expression (或 expression)。它會匹配 constant_expression 的規則 (例如,匹配到 signed_integer,再匹配到 INTEGER_LITERAL)。
  7. ;: Lexer 提供 T_SEMICOLON token。Bison 匹配規則結尾的 T_SEMICOLON

結論 (語法層面): 由於 token 序列 T_IDENTIFIER T_COLON T_BOOL T_ASSIGN INTEGER_LITERAL T_SEMICOLON 完全符合 var_declaration 及其子規則定義的模式,Bison 會認為這個宣告在語法上是合法的

2. 語意分析 (Semantic Analysis) - 動作代碼或後續階段

僅僅語法正確還不夠。enable : BOOL := 10; 存在一個語意問題:試圖用一個整數 10 來初始化一個布林 (BOOL) 變數。

Bison 本身通常不直接執行這種複雜的類型檢查。這類檢查發生在:

  • 動作代碼 (Action Code) 中:var_declarationsimple_spec_init 規則成功匹配後,其對應的 {...} 中的 C/C++ 代碼會被執行。
  • 後續的語意分析階段: 更常見的做法是,Bison 的動作代碼主要負責建立抽象語法樹 (AST)。然後,會有一個獨立的語意分析階段遍歷 AST 來執行所有語意檢查。

相關程式碼片段 (概念性 - 在動作代碼中檢查):

// 假設 %union 中有 AST 節點指標類型
%type <ast_node_ptr> simple_spec_init constant_expression type_spec identifier_list
%type <type_info_ptr> type_spec // 可能返回類型資訊指標

// ...

var_declaration:
      identifier_list T_COLON type_spec location_spec simple_spec_init T_SEMICOLON
      {
          // $1: identifier_list 的 AST 節點 (或列表)
          // $3: type_spec 的類型資訊 (type_info_ptr)
          // $4: location_spec 的 AST 節點 (或 NULL)
          // $5: simple_spec_init 的初始化表達式 AST 節點 (或 NULL)

          TypeInfo* declaredType = $3; // 獲取宣告的類型 (例如 BOOL)
          ASTNode* initializerExpr = $5; // 獲取初始化表達式 (例如代表 10 的節點)
          ASTNode* identList = $1;

          if (initializerExpr != NULL) {
              // 1. 分析初始化表達式的類型 (這可能需要遞迴調用分析函數)
              TypeInfo* initializerType = analyze_expression_type(initializerExpr); // 例如返回 INT

              // 2. 檢查類型相容性
              if (!is_assignment_compatible(declaredType, initializerType)) {
                  // 報告錯誤!類型不匹配
                  SourceLocation loc = get_expression_location(initializerExpr); // 獲取初始化表達式的位置
                  yyerror_semantic(loc, "Initializer type '%s' is not compatible with variable type '%s'",
                                   type_to_string(initializerType),
                                   type_to_string(declaredType));
                  // 可能需要設置錯誤標記或返回錯誤節點
              } else {
                  // 類型相容,繼續處理 (例如建立 AST 節點)
                  $$ = build_var_decl_node(identList, declaredType, $4, initializerExpr);
              }
          } else {
              // 沒有初始化器,直接處理
              $$ = build_var_decl_node(identList, declaredType, $4, NULL);
          }

          // 3. (可選) 將變數添加到符號表
          // add_variables_to_symbol_table(identList, declaredType, ...);
      }
    ;

simple_spec_init:
      /* empty */               { $$ = NULL; }
    | T_ASSIGN constant_expression
                              { $$ = $2; } // $2 是 constant_expression 的 AST 節點
    ;

說明:

  1. 獲取資訊: 動作代碼透過 $1, $3, $5 等獲取匹配到的語法元素的語意值(通常是 AST 節點指標或類型資訊)。
  2. 分析初始化器類型: 如果存在初始化表達式 (initializerExpr != NULL),需要一個輔助函數 analyze_expression_type 來確定這個表達式的類型(例如,10 的類型是 INT)。
  3. 檢查相容性: is_assignment_compatible 函數會根據 IEC 61131-3 的規則(或編譯器的具體實現)來判斷初始化器的類型 (INT) 是否可以合法地賦值給宣告的類型 (BOOL)。
    • 在 IEC 61131-3 中,通常允許將整數賦值給布林:0 變為 FALSE,非 0 變為 TRUE。但編譯器可能會針對這種隱式轉換發出警告,或者如果設定了嚴格模式則可能報錯。is_assignment_compatible 函數會包含這個邏輯。
  4. 報告錯誤: 如果類型不相容(或根據規則需要報告警告/錯誤),則調用錯誤報告函數 yyerror_semantic(或類似名稱),提供錯誤訊息和位置。
  5. 建立 AST / 符號表: 如果檢查通過,則繼續建立代表此變數宣告的 AST 節點,並可能將變數資訊加入符號表。

總結:

對於 enable : BOOL := 10;

  • Bison 語法分析: 會成功匹配 var_declaration 規則,認為其語法合法
  • 語意分析 (動作代碼或後續階段): 會檢查 INT (來自 10) 是否能賦值給 BOOL。根據 IEC 61131-3 規則,這通常是允許的(非零轉為 TRUE),但實現上可能會在此處發出警告或(在嚴格模式下)錯誤。檢測和報告這個問題的邏輯位於 Bison 規則的動作代碼中,或者在後續遍歷 AST 的語意分析器中。