GAL / Backend /semantic /analyzer.py
Clarkoer's picture
add comments ehehe
e6b283b
Raw
History Blame Contribute Delete
14.2 kB
"""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)