| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474 |
- from __future__ import annotations
- import src.zelkelc.ast.types as types
- from dataclasses import dataclass
- from typing import Optional
- @dataclass
- class SemanticError:
- message: str
- @dataclass
- class StructSignature:
- name: str
- irname: str
- members: dict[str, MemberSignature]
- @dataclass
- class ClassSignature:
- name: str
- irname: str
- members: dict[str, MemberSignature]
- methods: dict[str, list[FunctionSignature]]
- @dataclass
- class FunctionSignature:
- name: str
- irname: str
- static: bool
- typ: types.Value
- args: dict[str, types.Value]
- @dataclass
- class ValueSignature:
- name: str
- irname: str
- mutable: bool
- typ: type[types.Integer64] | type[types.String] | type[types.Void]
- @dataclass
- class MemberSignature:
- name: str
- irname: str
- mutable: bool
- typ: type[types.Integer64] | type[types.String] | type[types.Void]
- member_idx: int
- @dataclass
- class Scope:
- parent: Optional[Scope]
- classes: dict[str, ClassSignature]
- structs: dict[str, StructSignature]
- functions: dict[str, list[FunctionSignature]]
- values: dict[str, ValueSignature]
- @staticmethod
- def root() -> Scope:
- return Scope(
- parent=None,
- classes={},
- structs={},
- functions={},
- values={},
- )
- def child(self) -> Scope:
- return Scope(
- parent=self,
- classes=self.classes,
- structs=self.structs,
- functions=self.functions,
- values={},
- )
- def resolve_variable(self, name: str) -> Optional[ValueSignature]:
- current: Optional[Scope] = self
- while current is not None:
- if name in current.values:
- return current.values[name]
- current = current.parent
- return None
- def resolve_function(
- self, scope_checker: ScopeChecker, name: str, args: list[types.Expression]
- ) -> Optional[FunctionSignature]:
- current: Optional[Scope] = self
- while current is not None:
- overloads = current.functions.get(name)
- if overloads:
- arg_types = current.expr_args_to_arg_types(scope_checker, args)
- if arg_types is None:
- return None
- return current.resolve_function_by_types(name, arg_types)
- current = current.parent
- return None
- def resolve_function_by_types(
- self, name: str, arg_types: list[types.Value]
- ) -> Optional[FunctionSignature]:
- current: Optional[Scope] = self
- while current is not None:
- overloads = current.functions.get(name)
- if overloads:
- for candidate in overloads:
- if list(candidate.args.values()) == arg_types:
- return candidate
- current = current.parent
- return None
- def expr_args_to_arg_types(
- self, scope_checker: ScopeChecker, args: list[types.Expression]
- ) -> Optional[list[types.Value]]:
- arg_types: list[types.Value] = []
- for arg in args:
- typ = scope_checker._infer_expression_type(arg, self)
- if typ is None:
- return None
- arg_types.append(typ)
- return arg_types
- def _ensure_function_overloads(self, name: str) -> list[FunctionSignature]:
- if name not in self.functions:
- self.functions[name] = []
- return self.functions[name]
- class ScopeChecker:
- global_scope: Scope
- errors: list[SemanticError]
- def __init__(self):
- self.global_scope = Scope.root()
- self.errors = []
- def check(self, tree: list[types.Node]) -> tuple[Scope, list[SemanticError]]:
- self._collect_globals(tree)
- for top_level in tree:
- match top_level:
- case types.FunctionDeclaration():
- self._check_function(top_level, self.global_scope)
- case types.ClassDeclaration():
- for method in top_level.methods:
- self._check_function(method, self.global_scope)
- case types.StructDeclaration():
- continue
- case types.InlineIR():
- for arg in top_level.args:
- self._infer_expression_type(arg, self.global_scope)
- case types.MacroCall():
- self._error(f"unexpanded macro call #{top_level.name}")
- case _:
- self._error(f"Unsupported top-level node: {type(top_level)}")
- return self.global_scope, self.errors
- def _collect_globals(self, tree: list[types.Node]) -> None:
- for top_level in tree:
- match top_level:
- case types.StructDeclaration(name=name, irname=irname, members=members):
- if name in self.global_scope.structs:
- self._error(f"Struct '{name}' is already declared")
- continue
- member_table: dict[str, MemberSignature] = {}
- for member in members:
- if member.name in member_table:
- self._error(
- f"Duplicate member '{member.name}' in struct '{name}'"
- )
- continue
- member_table[member.name] = MemberSignature(
- name=member.name,
- irname=f"{irname}.m_{member.name}",
- mutable=member.mutable,
- typ=member.typ,
- member_idx=member.real_idx,
- )
- self.global_scope.structs[name] = StructSignature(
- name=name,
- irname=irname,
- members=member_table,
- )
- case types.ClassDeclaration(
- name=name, irname=irname, members=members, methods=methods
- ):
- if name in self.global_scope.classes:
- self._error(f"Class '{name}' is already declared")
- continue
- member_table: dict[str, MemberSignature] = {}
- for idx, member in enumerate(members):
- if member.name in member_table:
- self._error(
- f"Duplicate member '{member.name}' in class '{name}'"
- )
- continue
- member_table[member.name] = MemberSignature(
- name=member.name,
- irname=f"{irname}.m_{member.name}",
- mutable=member.mutable,
- typ=member.typ,
- member_idx=idx,
- )
- method_table: dict[str, list[FunctionSignature]] = {}
- for method in methods:
- signature = FunctionSignature(
- name=method.name,
- irname=method.irname,
- static=False,
- typ=method.typ,
- args=method.args,
- )
- overloads = method_table.setdefault(method.name, [])
- has_duplicate_signature = any(
- list(existing.args.values())
- == list(signature.args.values())
- for existing in overloads
- )
- if has_duplicate_signature:
- self._error(
- f"Duplicate method '{method.name}' in class '{name}' "
- f"with same argument signature"
- )
- continue
- overloads.append(signature)
- self.global_scope.classes[name] = ClassSignature(
- name=name,
- irname=irname,
- members=member_table,
- methods=method_table,
- )
- case types.FunctionDeclaration(name, typ, args, _, irname):
- existing = self.global_scope.resolve_function_by_types(
- name, list(args.values())
- )
- if existing is not None:
- self._error(
- f"Function '{name}' is already defined with following signature '{existing}'"
- )
- continue
- self.global_scope._ensure_function_overloads(name).append(
- FunctionSignature(
- name=name,
- irname=irname,
- static=True,
- typ=typ,
- args=args,
- )
- )
- case types.MacroCall() | types.InlineIR():
- continue
- case _:
- self._error(
- f"Unsupported top-level node while collecting symbols: {type(top_level)}"
- )
- def _check_function(
- self, fn: types.FunctionDeclaration, enclosing_scope: Scope
- ) -> None:
- function_scope = enclosing_scope.child()
- for arg_name, arg_type in fn.args.items():
- if arg_name in function_scope.values:
- self._error(f"Duplicate argument '{arg_name}' in function '{fn.name}'")
- continue
- function_scope.values[arg_name] = ValueSignature(
- name=arg_name,
- irname=arg_name,
- mutable=False,
- typ=arg_type,
- )
- has_return = False
- for child in fn.body:
- match child:
- case types.ValueDeclaration():
- if child.name in function_scope.values:
- self._error(
- f"Variable '{child.name}' is already defined in function '{fn.name}'"
- )
- continue
- expression_type = self._infer_expression_type(
- child.expr, function_scope
- )
- if expression_type is not None and not self._same_type(
- expression_type, child.typ
- ):
- self._error(
- f"Type mismatch for '{child.name}' in function '{fn.name}': "
- f"expected {self._type_name(child.typ)}, got {self._type_name(expression_type)}"
- )
- function_scope.values[child.name] = ValueSignature(
- name=child.name,
- irname=child.irname,
- mutable=child.mutable,
- typ=child.typ,
- )
- case types.ReturnStatement():
- has_return = True
- if child.expr is None:
- if fn.typ is not types.Void:
- self._error(
- f"Function '{fn.name}' must return {self._type_name(fn.typ)}"
- )
- continue
- return_type = self._infer_expression_type(
- child.expr, function_scope
- )
- if return_type is None:
- continue
- if fn.typ is types.Void:
- self._error(
- f"Function '{fn.name}' returns a value, but return type is void"
- )
- continue
- if not self._same_type(return_type, fn.typ):
- self._error(
- f"Return type mismatch in function '{fn.name}': "
- f"expected {self._type_name(fn.typ)}, got {self._type_name(return_type)}"
- )
- case types.FunctionDeclaration():
- existing_nested = function_scope.resolve_function_by_types(
- child.name, list(child.args.values())
- )
- if existing_nested is not None:
- self._error(
- f"Nested function '{child.name}' is already defined in '{fn.name}'"
- )
- else:
- function_scope._ensure_function_overloads(child.name).append(
- FunctionSignature(
- name=child.name,
- irname=child.irname,
- static=True,
- typ=child.typ,
- args=child.args,
- )
- )
- self._check_function(child, function_scope)
- case types.InlineIR():
- for arg in child.args:
- self._infer_expression_type(arg, function_scope)
- case _:
- self._error(
- f"Unsupported node in function '{fn.name}': {type(child)}"
- )
- if not has_return and fn.typ is not types.Void:
- self._error(f"Function '{fn.name}' has no return statement")
- def _infer_expression_type(
- self, expr: types.Expression, active_scope: Scope
- ) -> Optional[type[types.Integer64] | type[types.String] | type[types.Void]]:
- match expr:
- case types.PrimaryExpression(types.Integer64()):
- return types.Integer64
- case types.PrimaryExpression(types.Float64()):
- return types.Float64
- case types.PrimaryExpression(types.String()):
- return types.String
- case types.PrimaryExpression(types.VariableRef(name)):
- signature = active_scope.resolve_variable(name)
- if signature is None:
- self._error(f"Unknown variable '{name}'")
- return None
- expr.value.irname = signature.irname
- expr.value.typ = signature.typ
- return signature.typ
- case types.PrimaryExpression(types.FunctionCall(name, args)):
- arg_types = active_scope.expr_args_to_arg_types(self, args)
- if arg_types is None:
- self._error("Could not get argument types from function call")
- return None
- signature = active_scope.resolve_function_by_types(name, arg_types)
- if signature is None:
- self._error(
- f"Unknown function '{name}' with given argument signature"
- )
- return None
- expr.value.irname = signature.irname
- return signature.typ
- case types.UnaryExpression(inner_expr, sign):
- inner_type = self._infer_expression_type(inner_expr, active_scope)
- if inner_type is None:
- return None
- if sign in ("+", "-") and inner_type is not types.Integer64:
- self._error(
- f"Unary operator '{sign}' expects i64, got {self._type_name(inner_type)}"
- )
- return None
- return inner_type
- case types.BinaryExpression(left=left, right=right, op=op):
- left_type = self._infer_expression_type(left, active_scope)
- right_type = self._infer_expression_type(right, active_scope)
- if left_type is None or right_type is None:
- return None
- if not self._same_type(left_type, right_type):
- self._error(
- f"Binary operator '{op}' type mismatch: "
- f"{self._type_name(left_type)} vs {self._type_name(right_type)}"
- )
- return None
- match left_type:
- case types.Integer64:
- if op not in ["+", "-", "*"]:
- self._error(
- f"Operator '{op}' does not support {self._type_name(left_type)}"
- )
- return types.Integer64
- case types.Float64:
- if op not in ["+", "-", "*", "/"]:
- self._error(
- f"Operator '{op}' does not support {self._type_name(left_type)}"
- )
- return types.Float64
- case _:
- return None
- case _:
- self._error(f"Unsupported expression type: {type(expr)}")
- return None
- def _same_type(self, left: object, right: object) -> bool:
- return self._type_token(left) == self._type_token(right)
- def _type_token(self, typ: object) -> str:
- if typ is types.Integer64 or isinstance(typ, types.Integer64):
- return "i64"
- if typ is types.Float64 or isinstance(typ, types.Float64):
- return "f64"
- if typ is types.String or isinstance(typ, types.String):
- return "str"
- if typ is types.Void or isinstance(typ, types.Void):
- return "void"
- return str(typ)
- def _type_name(self, typ: object) -> str:
- return self._type_token(typ)
- def _error(self, message: str) -> None:
- self.errors.append(SemanticError(message))
|