如何定義並檢出 `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 的解析過程大致如下:
enable: Lexer 提供T_IDENTIFIERtoken。Bison 匹配identifier_list規則 (基礎情況)。:: Lexer 提供T_COLONtoken。Bison 匹配規則中的T_COLON。BOOL: Lexer 提供代表BOOL的 token (可能是T_BOOL或通用的TYPE_IDENTIFIER)。Bison 匹配type_spec規則 (例如匹配到simple_type_name)。- (無
AT): Bison 預期location_spec。由於下一個 token 是:=而不是AT,它會匹配location_spec的空 (epsilon) 規則。 :=: Lexer 提供T_ASSIGNtoken。Bison 匹配simple_spec_init規則中的T_ASSIGN部分。10: Lexer 提供INTEGER_LITERALtoken (值為 10)。Bison 預期constant_expression(或expression)。它會匹配constant_expression的規則 (例如,匹配到signed_integer,再匹配到INTEGER_LITERAL)。;: Lexer 提供T_SEMICOLONtoken。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_declaration或simple_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,$3,$5等獲取匹配到的語法元素的語意值(通常是 AST 節點指標或類型資訊)。 - 分析初始化器類型: 如果存在初始化表達式 (
initializerExpr != NULL),需要一個輔助函數analyze_expression_type來確定這個表達式的類型(例如,10的類型是INT)。 - 檢查相容性:
is_assignment_compatible函數會根據 IEC 61131-3 的規則(或編譯器的具體實現)來判斷初始化器的類型 (INT) 是否可以合法地賦值給宣告的類型 (BOOL)。- 在 IEC 61131-3 中,通常允許將整數賦值給布林:0 變為
FALSE,非 0 變為TRUE。但編譯器可能會針對這種隱式轉換發出警告,或者如果設定了嚴格模式則可能報錯。is_assignment_compatible函數會包含這個邏輯。
- 在 IEC 61131-3 中,通常允許將整數賦值給布林:0 變為
- 報告錯誤: 如果類型不相容(或根據規則需要報告警告/錯誤),則調用錯誤報告函數
yyerror_semantic(或類似名稱),提供錯誤訊息和位置。 - 建立 AST / 符號表: 如果檢查通過,則繼續建立代表此變數宣告的 AST 節點,並可能將變數資訊加入符號表。
總結:
對於 enable : BOOL := 10;:
- Bison 語法分析: 會成功匹配
var_declaration規則,認為其語法合法。 - 語意分析 (動作代碼或後續階段): 會檢查
INT(來自10) 是否能賦值給BOOL。根據 IEC 61131-3 規則,這通常是允許的(非零轉為 TRUE),但實現上可能會在此處發出警告或(在嚴格模式下)錯誤。檢測和報告這個問題的邏輯位於 Bison 規則的動作代碼中,或者在後續遍歷 AST 的語意分析器中。