"""Final AST validation pass for GAL. builder.py already catches many declaration/type issues while creating the AST. This validator walks the finished AST and checks rules that are easier to see from the tree structure, such as prune/skip placement. """ # AUTO: Imports names from another module. from semantic.errors import SemanticError # AUTO: Defines class `ASTValidator`. class ASTValidator: # AUTO: Defines function `__init__`. def __init__(self): # GUIDE: Context counters tell checks if they are inside a loop, # switch, or function while walking nested AST nodes. # AUTO: Sets `self.errors`. self.errors = [] # AUTO: Sets `self.warnings`. self.warnings = [] # AUTO: Sets `self._in_loop`. self._in_loop = 0 # AUTO: Sets `self._in_switch`. self._in_switch = 0 # AUTO: Sets `self._in_function`. self._in_function = False # AUTO: Sets `self._current_func_type`. self._current_func_type = None # AUTO: Defines function `validate`. def validate(self, ast, symbol_table_data): # Start walking from the ProgramNode. Every checker can add messages # into self.errors; no errors means semantic validation succeeds. # LINE: Begin recursive semantic walk from the AST root. self._walk(ast) # LINE: Return validation result plus errors/warnings/symbol table. return { # LINE: success is true only when no semantic errors were collected. "success": len(self.errors) == 0, # AUTO: Executes this statement. "errors": self.errors, # AUTO: Executes this statement. "warnings": self.warnings, # AUTO: Executes this statement. "symbol_table": symbol_table_data, # AUTO: Executes this statement. "ast": ast, # AUTO: Closes the current grouped code/data. } # AUTO: Defines function `_walk`. def _walk(self, node): # GUIDE: Dynamic dispatch; Program calls _check_Program, Break calls # _check_Break, and unknown node types simply recurse into children. # LINE: Nothing to check for missing/empty AST node. if node is None: # AUTO: Returns this result to the caller. return # LINE: Find checker method based on node type, like _check_Break. handler = getattr(self, f'_check_{node.node_type}', None) # LINE: If checker exists, run that specific semantic rule. if handler: # AUTO: Calls `handler`. handler(node) # AUTO: Runs when previous condition did not pass. else: # LINE: Otherwise keep walking through this node's children. for child in getattr(node, 'children', []): # AUTO: Calls `self._walk`. self._walk(child) # AUTO: Defines function `_check_Program`. def _check_Program(self, node): # LINE: ProgramNode validates by checking each top-level child. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # AUTO: Defines function `_check_VariableDeclaration`. def _check_VariableDeclaration(self, node): # LINE: VariableDeclaration must at least contain type and name. if len(node.children) < 2: # AUTO: Appends a value to a list. self.errors.append( # AUTO: Executes this statement. f"SEMANTIC error line {node.line}: Malformed variable declaration.") # LINE: Continue validating initializer/children. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # AUTO: Defines function `_check_VariableDeclarationList`. def _check_VariableDeclarationList(self, node): # AUTO: Starts a loop over these values. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # AUTO: Defines function `_check_SturdyDeclaration`. def _check_SturdyDeclaration(self, node): # AUTO: Checks this condition. if len(node.children) < 3: # AUTO: Appends a value to a list. self.errors.append( # AUTO: Executes this statement. f"SEMANTIC error line {node.line}: Fertile declaration must have type, name, and value.") # AUTO: Starts a loop over these values. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # AUTO: Defines function `_check_FunctionDeclaration`. def _check_FunctionDeclaration(self, node): # LINE: FunctionDeclaration must have a function name in node.value. if not node.value: # AUTO: Appends a value to a list. self.errors.append( # AUTO: Executes this statement. f"SEMANTIC error line {node.line}: Function declaration missing name.") # LINE: Save previous function context before entering this function. prev_in_func = self._in_function # AUTO: Sets `prev_func_type`. prev_func_type = self._current_func_type # LINE: Mark validator as currently inside a function. self._in_function = True # AUTO: Checks this condition. if node.children: # LINE: First child stores return type. self._current_func_type = node.children[0].value # LINE: Validate parameters/body/children inside the function. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # LINE: Restore previous context after leaving function. self._in_function = prev_in_func # AUTO: Sets `self._current_func_type`. self._current_func_type = prev_func_type # AUTO: Defines function `_check_FunctionCall`. def _check_FunctionCall(self, node): # AUTO: Starts a loop over these values. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # AUTO: Defines function `_check_Assignment`. def _check_Assignment(self, node): # AUTO: Starts a loop over these values. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # AUTO: Defines function `_check_AssignmentList`. def _check_AssignmentList(self, node): # AUTO: Starts a loop over these values. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # AUTO: Defines function `_check_BinaryOp`. def _check_BinaryOp(self, node): # AUTO: Starts a loop over these values. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # AUTO: Defines function `_check_UnaryOp`. def _check_UnaryOp(self, node): # AUTO: Starts a loop over these values. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # AUTO: Defines function `_check_Block`. def _check_Block(self, node): # AUTO: Starts a loop over these values. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # AUTO: Defines function `_check_IfStatement`. def _check_IfStatement(self, node): # AUTO: Starts a loop over these values. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # AUTO: Defines function `_check_ElseIfStatement`. def _check_ElseIfStatement(self, node): # AUTO: Starts a loop over these values. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # AUTO: Defines function `_check_ElseStatement`. def _check_ElseStatement(self, node): # AUTO: Starts a loop over these values. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # AUTO: Defines function `_check_ForLoop`. def _check_ForLoop(self, node): # Enter loop context so prune/skip inside this block are legal. # LINE: Increase loop depth before checking cultivate body. self._in_loop += 1 # AUTO: Starts a loop over these values. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # Leave loop context after all nested children are checked. # LINE: Decrease loop depth after cultivate body is checked. self._in_loop -= 1 # AUTO: Defines function `_check_WhileLoop`. def _check_WhileLoop(self, node): # Same context rule as cultivate: grow allows prune/skip inside. # LINE: Increase loop depth before checking grow body. self._in_loop += 1 # AUTO: Starts a loop over these values. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # LINE: Decrease loop depth after grow body. self._in_loop -= 1 # AUTO: Defines function `_check_DoWhileLoop`. def _check_DoWhileLoop(self, node): # AUTO: Adds into `self._in_loop`. self._in_loop += 1 # AUTO: Starts a loop over these values. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # AUTO: Subtracts from `self._in_loop`. self._in_loop -= 1 # AUTO: Defines function `_check_Switch`. def _check_Switch(self, node): # LINE: harvest/variety context allows prune inside switch cases. self._in_switch += 1 # AUTO: Starts a loop over these values. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # LINE: Leave switch context after all cases are checked. self._in_switch -= 1 # AUTO: Defines function `_check_Break`. def _check_Break(self, node): # prune is valid only while the walker is inside a loop or harvest. # LINE: If no loop/switch context, prune is illegal. if self._in_loop == 0 and self._in_switch == 0: # AUTO: Appends a value to a list. self.errors.append( # AUTO: Executes this statement. f"SEMANTIC error line {node.line}: 'prune' used outside a loop or switch.") # AUTO: Defines function `_check_Continue`. def _check_Continue(self, node): # skip is valid only while the walker is inside a loop. # LINE: If no loop context, skip is illegal. if self._in_loop == 0: # AUTO: Appends a value to a list. self.errors.append( # AUTO: Executes this statement. f"SEMANTIC error line {node.line}: 'skip' used outside a loop.") # AUTO: Defines function `_check_Return`. def _check_Return(self, node): # AUTO: Starts a loop over these values. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # AUTO: Defines function `_check_PrintStatement`. def _check_PrintStatement(self, node): # AUTO: Starts a loop over these values. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # AUTO: Defines function `_check_Condition`. def _check_Condition(self, node): # AUTO: Starts a loop over these values. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # AUTO: Defines function `_check_Update`. def _check_Update(self, node): # AUTO: Starts a loop over these values. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # AUTO: Defines function `_check_List`. def _check_List(self, node): # AUTO: Starts a loop over these values. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # AUTO: Defines function `_check_ListAccess`. def _check_ListAccess(self, node): # AUTO: Starts a loop over these values. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # AUTO: Defines function `_check_TypeCast`. def _check_TypeCast(self, node): # AUTO: Starts a loop over these values. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # AUTO: Defines function `_check_MemberAccess`. def _check_MemberAccess(self, node): # AUTO: Starts a loop over these values. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # AUTO: Defines function `_check_BundleDefinition`. def _check_BundleDefinition(self, node): # AUTO: Checks this condition. if not node.bundle_name: # AUTO: Appends a value to a list. self.errors.append( # AUTO: Executes this statement. f"SEMANTIC error line {node.line}: Bundle definition missing name.") # AUTO: Defines function `_check_Append`. def _check_Append(self, node): # AUTO: Starts a loop over these values. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # AUTO: Defines function `_check_Insert`. def _check_Insert(self, node): # AUTO: Starts a loop over these values. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # AUTO: Defines function `_check_Remove`. def _check_Remove(self, node): # AUTO: Starts a loop over these values. for child in node.children: # AUTO: Calls `self._walk`. self._walk(child) # AUTO: Defines function `validate_ast`. def validate_ast(ast, symbol_table_data): # LINE: Create a fresh validator for this compile/run. validator = ASTValidator() # LINE: Run validator and return its result. return validator.validate(ast, symbol_table_data)