Kaynağa Gözat

lots of parser and codegen progress

Johan Rong 3 hafta önce
ebeveyn
işleme
bd93734091

+ 3 - 2
README.md

@@ -5,9 +5,10 @@
 * [ ] Reference-counting garbage collector
 
 ## TODO
-* [ ] Scope checking and stuff in parser
+* [x] Scope checking and stuff in parser
 * [ ] Parser should generate all names (unique), struct indexes, etc for codegen, this should also be in the scope
-    * [ ] Multi pass parser: first scope/symbol collection (what i've been doing now), then check if it makes sense :)
+    * [x] Multi pass parser: first scope/symbol collection (what i've been doing now), then check if it makes sense :)
+* [ ] Functions may kind of have same name but different arguments, but the scope stuff is using a dict[str, Value] so it is not tracked, the resolve_function() should work for this though, so scope stuff needs work.
 * Types:
   * With no * or & before type it will be "dynamic", the compiler figures out if it's the pointer or the value that is being used
   * explicit value with * or explicit pointer with &. You can also use & and * on a "dynamic" type to get both, "dynamic" types are both at the same time, but in reality they are probably the pointer and then you know both 

+ 1 - 16
src/zelkelc/__main__.py

@@ -6,23 +6,8 @@ from src.zelkelc.codegen.codegen import Codegen
 import rich
 
 source = """
-struct MyStruct {
-    val test: i64
-}
-
-//test
-class MyClass {
-    val immutable_value: i64
-    var mutable_value: i64
-    
-    fn function(test: i64, test2: i64) -> i64 {
-        return 12 * "sd"
-    }
-}
-
 fn main() -> i64 {
-    val test: i64 = 16
-    var test2: i64 = test
+    val test: i64 = 10 + (5 * 5)
     return 0
 }
 """

+ 50 - 35
src/zelkelc/ast/parser.py

@@ -1,6 +1,5 @@
 import src.zelkelc.lexer.token as token
-import src.zelkelc.ast.nodes as nodes
-import src.zelkelc.ast.values as values
+import src.zelkelc.ast.types as types
 
 class Parser:
     tokens: list[token.Token]
@@ -33,21 +32,21 @@ class Parser:
             return False
         return True
 
-    def parse_type(self, is_return_type: bool) -> values.Value:
+    def parse_type(self, is_return_type: bool) -> types.Value:
         identifier = self.expect_type(token.Identifier)
         match identifier.value:
             case "str":
-                return values.String
+                return types.String
             case "i64":
-                return values.Integer64
+                return types.Integer64
             case "void" if is_return_type == True:
-                return values.Void
+                return types.Void
             case _:
                 print("Invalid type found in parse_type")
                 exit(1)
 
     def parse(self):
-        ast: list[nodes.FunctionDeclaration | nodes.ClassDeclaration] = []
+        ast: list[types.FunctionDeclaration | types.ClassDeclaration] = []
         
         while self.cursor < len(self.tokens):
             t = self.tokens[self.cursor]
@@ -71,12 +70,12 @@ class Parser:
         
         return ast
 
-    def parse_struct_declaration(self, path: str) -> nodes.StructDeclaration:
+    def parse_struct_declaration(self, path: str) -> types.StructDeclaration:
         self.expect(token.Identifier("struct"))
         name = self.expect_type(token.Identifier).value
         self.expect(token.LBrace())
         
-        members: list[nodes.MemberDeclaration] = []
+        members: list[types.MemberDeclaration] = []
                 
         while self.cursor < len(self.tokens) and not self.tokens[self.cursor] == token.RBrace():
             t = self.tokens[self.cursor]
@@ -97,13 +96,13 @@ class Parser:
         
         self.expect(token.RBrace())
         
-        return nodes.StructDeclaration(
+        return types.StructDeclaration(
             name,
             members,
             real_name = f"{path}.s_{name}"
         )
     
-    def parse_member_declaration(self, mutable: bool, member_idx: int) -> nodes.MemberDeclaration:
+    def parse_member_declaration(self, mutable: bool, member_idx: int) -> types.MemberDeclaration:
         if mutable:
             self.expect(token.Identifier("var"))
         else:
@@ -114,20 +113,20 @@ class Parser:
         self.expect(token.Colon())
         typ = self.parse_type(False)
         
-        return nodes.MemberDeclaration(
+        return types.MemberDeclaration(
             name,
             mutable,
             typ,
             real_idx = member_idx
         )
     
-    def parse_class_declaration(self, path: str) -> nodes.ClassDeclaration:
+    def parse_class_declaration(self, path: str) -> types.ClassDeclaration:
         self.expect(token.Identifier("class"))
         name = self.expect_type(token.Identifier).value
         self.expect(token.LBrace())
         
-        members: list[nodes.ValueDeclaration] = []
-        methods: list[nodes.FunctionDeclaration] = []
+        members: list[types.ValueDeclaration] = []
+        methods: list[types.FunctionDeclaration] = []
         
         while self.cursor < len(self.tokens) and not self.tokens[self.cursor] == token.RBrace():
             t = self.tokens[self.cursor]
@@ -150,19 +149,19 @@ class Parser:
         
         self.expect(token.RBrace())
         
-        return nodes.ClassDeclaration(
+        return types.ClassDeclaration(
             name,
             members,
             methods,
             real_name = f"{path}.c_{name}"
         )
     
-    def parse_function_declaration(self, path: str) -> nodes.FunctionDeclaration:
+    def parse_function_declaration(self, path: str) -> types.FunctionDeclaration:
         self.expect(token.Identifier("fn"))
         name = self.expect_type(token.Identifier).value
         self.expect(token.LParen())
         
-        args: dict[str, values.Value] = {}
+        args: dict[str, types.Value] = {}
         while self.cursor < len(self.tokens) and not self.current(token.RParen()):
             arg_name = self.expect_type(token.Identifier).value
             
@@ -182,7 +181,7 @@ class Parser:
         
         self.expect(token.LBrace())
         
-        body: list[nodes.Node] = []
+        body: list[types.Node] = []
         hasReturn: bool = False # TODO: should be replaced with real return tree checking, this is not a good system
         
         while self.cursor < len(self.tokens) and not self.tokens[self.cursor] == token.RBrace():
@@ -214,25 +213,29 @@ class Parser:
         
         self.expect(token.RBrace())
         
-        return nodes.FunctionDeclaration(
+        real_name = f"{path}.f_{name}_{typ}"
+        if name == "main":
+            real_name = "main"
+        
+        return types.FunctionDeclaration(
             name = name,
             typ = typ,
             args = args,
             body = body,
-            real_name = f"{path}.f_{name}_{typ}"
+            real_name = real_name
         )
     
-    def parse_return(self) -> nodes.ReturnStatement:
+    def parse_return(self) -> types.ReturnStatement:
         self.expect(token.Identifier("return"))
         
         # TODO: if there is an expression then check for add it?
         expr = self.parse_expression()
         
-        return nodes.ReturnStatement(
+        return types.ReturnStatement(
             expr
         )
     
-    def parse_value_declaration(self, mutable: bool) -> nodes.ValueDeclaration:
+    def parse_value_declaration(self, mutable: bool) -> types.ValueDeclaration:
         if mutable:
             self.expect(token.Identifier("var"))
         else:
@@ -247,18 +250,18 @@ class Parser:
         
         expr = self.parse_expression()
         
-        return nodes.ValueDeclaration(
+        return types.ValueDeclaration(
             name,
             mutable,
             typ,
             expr,
-            real_name = f"{self.cursor}_{name}"
+            real_name = f"v_{self.cursor}_{name}"
         )
     
-    def parse_expression(self) -> nodes.Expression:
+    def parse_expression(self) -> types.Expression:
         return self.parse_binary_expression(0)
 
-    def parse_binary_expression(self, min_precedence: int = 0) -> nodes.Expression:
+    def parse_binary_expression(self, min_precedence: int = 0) -> types.Expression:
         left = self.parse_unary_expression()
 
         while self.current_type(token.Operator):
@@ -273,11 +276,11 @@ class Parser:
 
             right = self.parse_binary_expression(precedence + 1)
 
-            left = nodes.BinaryExpression(left, right, op)
+            left = types.BinaryExpression(left, right, op)
 
         return left
 
-    def parse_unary_expression(self) -> nodes.Expression:
+    def parse_unary_expression(self) -> types.Expression:
         if self.current_type(token.Operator):
             op = self.expect_type(token.Operator).value
 
@@ -285,25 +288,37 @@ class Parser:
                 print(f"Invalid unary operator: {op}")
                 exit(1)
             operand = self.parse_unary_expression()
-            return nodes.UnaryExpression(operand, op)
+            return types.UnaryExpression(operand, op)
 
         return self.parse_primary_expression()
 
-    def parse_primary_expression(self) -> nodes.Expression:
+    def parse_primary_expression(self) -> types.Expression:
         t = self.tokens[self.cursor]
 
         match t:
             case token.Identifier():
                 self.cursor += 1
-                return nodes.PrimaryExpression(value=values.Variable(t.value))
+                
+                if self.current(token.LParen()):
+                    self.cursor += 1
+                    args: list[types.Expression] = []
+                    while self.cursor < len(self.tokens) and self.tokens[self.cursor] != token.RParen():
+                        if len(args) != 0:
+                            self.expect(token.Comma())
+                        args.append(self.parse_expression())
+                    
+                    self.expect(token.RParen())
+                    return types.PrimaryExpression(types.FunctionCall(t.value, args))
+                else:
+                    return types.PrimaryExpression(types.VariableRef(t.value))
 
             case token.Integer():
                 self.cursor += 1
-                return nodes.PrimaryExpression(value=values.Integer64(t.value))
+                return types.PrimaryExpression(types.Integer64(t.value))
 
             case token.String():
                 self.cursor += 1
-                return nodes.PrimaryExpression(value=values.String(t.value))
+                return types.PrimaryExpression(types.String(t.value))
 
             case token.LParen():
                 self.expect(token.LParen())

+ 112 - 0
src/zelkelc/ast/types.py

@@ -0,0 +1,112 @@
+from __future__ import annotations
+
+from dataclasses import dataclass
+
+@dataclass
+class ClassDeclaration:
+    name: str
+    members: list[ValueDeclaration]
+    methods: list[FunctionDeclaration]
+    real_name: str
+
+@dataclass
+class StructDeclaration:
+    name: str
+    members: list[MemberDeclaration]
+    real_name: str
+
+@dataclass
+class MemberDeclaration:
+    name: str
+    mutable: bool
+    typ: Value
+    real_idx: int
+
+@dataclass
+class ValueDeclaration:
+    name: str
+    mutable: bool
+    typ: Value
+    expr: Expression
+    real_name: str
+    
+@dataclass
+class FunctionDeclaration:
+    name: str
+    typ: Value
+    args: dict[str, Value]
+    body: list[Node]
+    real_name: str
+    
+@dataclass
+class ReturnStatement:
+    expr: Expression | None
+
+Node = ClassDeclaration | ValueDeclaration | FunctionDeclaration | StructDeclaration
+
+@dataclass
+class BinaryExpression:
+    left: Expression
+    right: Expression
+    op: str
+
+@dataclass
+class UnaryExpression:
+    expr: Expression
+    sign: str
+
+@dataclass
+class PrimaryExpression:
+    value: Value
+
+Expression = BinaryExpression | UnaryExpression | PrimaryExpression
+
+@dataclass
+class Integer64:
+    value: int
+    
+    def to_llvm() -> str:
+        return "i64"
+    
+    def __eq__(self, other):
+        if not isinstance(other, Integer64):
+            return False
+        return self.value == other.value
+
+@dataclass
+class String:
+    value: str
+    
+    def to_llvm() -> str:
+        print("Cannot convert String to llvm ir")
+        exit(1)
+
+@dataclass
+class Void:
+    pass
+
+    def to_llvm() -> str:
+        return "void"
+
+@dataclass
+class VariableRef:
+    name: str
+    
+    def to_llvm() -> str:
+        print("Cannot convert Variable to llvm ir")
+        exit(1)
+
+@dataclass
+class FunctionCall:
+    name: str
+    args: list[Expression]
+
+@dataclass
+class Ptr[T]:
+    pass
+
+@dataclass
+class Val[T]:
+    pass
+
+Value = Integer64 | String | Void | VariableRef | FunctionCall

+ 77 - 19
src/zelkelc/codegen/codegen.py

@@ -1,5 +1,4 @@
-import src.zelkelc.ast.nodes as nodes
-import src.zelkelc.ast.values as values
+import src.zelkelc.ast.types as types
 
 class IRTable:
     structs: str
@@ -12,21 +11,23 @@ class IRTable:
         self.global_functions = ""
 
 class Codegen:
-    ast: list[nodes.FunctionDeclaration | nodes.ClassDeclaration]
+    ast: list[types.FunctionDeclaration | types.ClassDeclaration]
     ir: IRTable
+    temp_counter: int
     
-    def __init__(self, ast: list[nodes.FunctionDeclaration | nodes.ClassDeclaration]):
+    def __init__(self, ast: list[types.FunctionDeclaration | types.ClassDeclaration]):
         self.ast = ast
         self.ir = IRTable()
+        self.temp_counter = 0
         
     def generate(self):
         for node in self.ast:
             match node:
-                case nodes.StructDeclaration():
+                case types.StructDeclaration():
                     self.gen_struct_declaration(node)
-                case nodes.ClassDeclaration():
+                case types.ClassDeclaration():
                     self.gen_class_declaration(node)
-                case nodes.FunctionDeclaration():
+                case types.FunctionDeclaration():
                     self.ir.global_functions += self.gen_function_declaration(node)
                 case _:
                     print(f"Invalid node found during codegen {type(node)}")
@@ -34,14 +35,14 @@ class Codegen:
         
         return self.ir.structs + "\n" + self.ir.struct_functions + "\n" + self.ir.global_functions
     
-    def gen_struct_declaration(self, node: nodes.StructDeclaration):
+    def gen_struct_declaration(self, node: types.StructDeclaration):
         members_str = ""
         for i, m in enumerate(node.members):
             members_str += f"{", " if i != 0 else ""}{m.typ.to_llvm()}"
 
         self.ir.structs += f"\n%{node.real_name} = type " + "{ " + members_str + " }"
 
-    def gen_class_declaration(self, node: nodes.ClassDeclaration):
+    def gen_class_declaration(self, node: types.ClassDeclaration):
         members_str = ""
         for i, m in enumerate(node.members):
             members_str += f"{", " if i != 0 else ""}{m.typ.to_llvm()}"
@@ -51,7 +52,7 @@ class Codegen:
         for func in node.methods:
             self.ir.struct_functions += self.gen_function_declaration(func)
     
-    def gen_function_declaration(self, node: nodes.FunctionDeclaration) -> str:
+    def gen_function_declaration(self, node: types.FunctionDeclaration) -> str:
         args_str = ""
         for i, (name, typ) in enumerate(node.args.items()):
             args_str += f"{", " if i != 0 else ""}{typ.to_llvm()} %{name}"
@@ -61,20 +62,77 @@ class Codegen:
         
         for child_node in node.body:
             match child_node:
-                case nodes.ValueDeclaration():
+                case types.ValueDeclaration():
                     ir += self.gen_value_declaration(child_node)
                 case _:
                     continue
         
         return ir + "\n}"
     
-    def gen_value_declaration(self, node: nodes.ValueDeclaration) -> str:
-        ir = "\n"
+    def gen_value_declaration(self, node: types.ValueDeclaration) -> str:
+        ir = ""
         
-        if node.mutable == True: # TODO: both are hardcoded to just put int: 16
-            ir += f"%{node.real_name} = alloca {node.typ.to_llvm()}, align 8" # TODO: this would only work for i64s, just testing
-            ir += f"\nstore {node.typ.to_llvm()} 16, {node.typ.to_llvm()}* %{node.real_name}, align 8"
-        else:
-            ir += f"%{node.real_name} = add {node.typ.to_llvm()} 16, 0"
+        count, expr = self.gen_expression(node.expr)
+        ir += expr
+        
+        match node.typ:
+            case types.Integer64:
+                if node.mutable == True:
+                    ir += f"\n%{node.real_name} = alloca i64, align 8" # TODO: this would only work for i64s, just testing
+                    ir += f"\nstore i64 %{count}, i64* %{node.real_name}, align 8"
+                else:
+                    ir += f"\n%{node.real_name} = add i64 %{count}, 0" 
+            case _:
+                print(f"gen_value_declaration() for {type(node.typ)} is unimplemented")
+                exit(1)
             
-        return ir
+        return ir
+    
+    def gen_expression(self, node: types.Expression) -> tuple[int, str]:
+        ir = ""
+        
+        match type(node):
+            case types.BinaryExpression:
+                left_count, left = self.gen_expression(node.left)
+                right_count, right = self.gen_expression(node.right)
+                
+                ir += left + right
+                        
+                #match type(node.left.): # TODO: need to know the value type of the expresison here...
+                #    case types.Integer64:
+                op = ""          
+                match node.op:
+                    case '+':
+                        op = "add"
+                    case '-':
+                        op = "sub"
+                    case '*':
+                        op = "mul"
+                    case '/':
+                        print("gen_expression() for BinaryExpression for operator '/' is unimplemented")
+                        exit(1)
+                    case _:
+                        print(f"gen_expression() for BinaryExpression for operator '{node.op}' is unimplemented")
+                        exit(1)
+                        
+                ir += f"\n%{self.temp_counter} = {op} i64 %{left_count}, %{right_count}"
+                self.temp_counter += 1
+                #    case _:
+                #        print(f"gen_expression() for PrimaryExpression for {type(node.value)}")
+                #        exit(1)
+            case types.UnaryExpression:
+                print("gen_expression() for UnaryExpression unimplemented")
+                exit(1)
+            case types.PrimaryExpression:
+                match type(node.value):
+                    case types.Integer64:
+                        ir += f"\n%{self.temp_counter} = add i64 {node.value.value}, 0"
+                        self.temp_counter += 1
+                    case _:
+                        print(f"gen_expression() for PrimaryExpression for {type(node.value)}")
+                        exit(1)
+            case _:
+                print(f"Invalid expression types found during codegen: {node}")
+                exit(1)
+
+        return self.temp_counter - 1, ir

+ 6 - 3
src/zelkelc/lexer/tokenizer.py

@@ -15,13 +15,16 @@ def lex(source: str) -> list[token.Token]:
         elif c.isnumeric():
             i += 1
             
-            integer = int(c)
+            integer = c
             for _, integer_c in enumerate(source[i:]):
+                if integer_c == '_':
+                    i += 1
+                    continue
                 if not integer_c.isnumeric():
                     break
                 i += 1
-                integer += int(integer_c)
-            tokens.append(token.Integer(integer))
+                integer += integer_c
+            tokens.append(token.Integer(int(integer)))
         elif c.isalpha() or c == '_':
             i += 1
             

+ 69 - 41
src/zelkelc/validator/scope.py

@@ -1,11 +1,9 @@
 from __future__ import annotations
 
-import src.zelkelc.ast.values as values
-import src.zelkelc.ast.nodes as nodes
+import src.zelkelc.ast.types as types
 from dataclasses import dataclass
 from typing import Optional
 
-
 @dataclass
 class SemanticError:
     message: str
@@ -29,22 +27,22 @@ class FunctionSignature:
     name: str
     real_name: str
     static: bool
-    typ: values.Value
-    args: dict[str, values.Value]
+    typ: types.Value
+    args: dict[str, types.Value]
     
 @dataclass
-class ValueSignature:
+class typesignature:
     name: str
     real_name: str
     mutable: bool
-    typ: type[values.Integer64] | type[values.String] | type[values.Void]
+    typ: type[types.Integer64] | type[types.String] | type[types.Void]
     
 @dataclass
 class MemberSignature:
     name: str
     real_name: str
     mutable: bool
-    typ: type[values.Integer64] | type[values.String] | type[values.Void]
+    typ: type[types.Integer64] | type[types.String] | type[types.Void]
     member_idx: int
 
 
@@ -54,7 +52,7 @@ class Scope:
     classes: dict[str, ClassSignature]
     structs: dict[str, StructSignature]
     functions: dict[str, FunctionSignature]
-    variables: dict[str, ValueSignature]
+    variables: dict[str, typesignature]
 
     @staticmethod
     def root() -> Scope:
@@ -75,7 +73,7 @@ class Scope:
             variables = {},
         )
 
-    def resolve_variable(self, name: str) -> Optional[ValueSignature]:
+    def resolve_variable(self, name: str) -> Optional[typesignature]:
         current: Optional[Scope] = self
         while current is not None:
             if name in current.variables:
@@ -83,6 +81,30 @@ class Scope:
             current = current.parent
         return None
 
+    def resolve_function(self, scope_checker: ScopeChecker, name: str, args: list[types.Expression]) -> list[Optional[typesignature]]:
+        current: Optional[Scope] = self
+        while current is not None:
+            if name in current.functions:
+                func = current.functions[name]
+                arg_types = self.expr_args_to_arg_types(scope_checker, args)
+                if arg_types is None:
+                    return None
+                
+                if list(func.args.values()) == arg_types:
+                    return func
+            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
 
 class ScopeChecker:
     global_scope: Scope
@@ -92,27 +114,27 @@ class ScopeChecker:
         self.global_scope = Scope.root()
         self.errors = []
 
-    def check(self, tree: list[nodes.Node]) -> tuple[Scope, list[SemanticError]]:
+    def check(self, tree: list[types.Node]) -> tuple[Scope, list[SemanticError]]:
         self._collect_globals(tree)
 
         for top_level in tree:
             match top_level:
-                case nodes.FunctionDeclaration():
+                case types.FunctionDeclaration():
                     self._check_function(top_level, self.global_scope)
-                case nodes.ClassDeclaration():
+                case types.ClassDeclaration():
                     for method in top_level.methods:
                         self._check_function(method, self.global_scope)
-                case nodes.StructDeclaration():
+                case types.StructDeclaration():
                     continue
                 case _:
                     self._error(f"Unsupported top-level node: {type(top_level)}")
 
         return self.global_scope, self.errors
 
-    def _collect_globals(self, tree: list[nodes.Node]) -> None:
+    def _collect_globals(self, tree: list[types.Node]) -> None:
         for top_level in tree:
             match top_level:
-                case nodes.StructDeclaration(name=name, real_name=real_name, members=members):
+                case types.StructDeclaration(name=name, real_name=real_name, members=members):
                     if name in self.global_scope.structs:
                         self._error(f"Struct '{name}' is already declared")
                         continue
@@ -136,7 +158,7 @@ class ScopeChecker:
                         members=member_table,
                     )
 
-                case nodes.ClassDeclaration(name=name, real_name=real_name, members=members, methods=methods):
+                case types.ClassDeclaration(name=name, real_name=real_name, members=members, methods=methods):
                     if name in self.global_scope.classes:
                         self._error(f"Class '{name}' is already declared")
                         continue
@@ -174,7 +196,7 @@ class ScopeChecker:
                         methods = method_table,
                     )
 
-                case nodes.FunctionDeclaration(name=name, real_name=real_name, typ=typ, args=args):
+                case types.FunctionDeclaration(name=name, real_name=real_name, typ=typ, args=args):
                     if name in self.global_scope.functions:
                         self._error(f"Function '{name}' is already declared")
                         continue
@@ -190,14 +212,14 @@ class ScopeChecker:
                 case _:
                     self._error(f"Unsupported top-level node while collecting symbols: {type(top_level)}")
 
-    def _check_function(self, fn: nodes.FunctionDeclaration, enclosing_scope: Scope) -> None:
+    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.variables:
                 self._error(f"Duplicate argument '{arg_name}' in function '{fn.name}'")
                 continue
-            function_scope.variables[arg_name] = ValueSignature(
+            function_scope.variables[arg_name] = typesignature(
                 name = arg_name,
                 real_name = arg_name,
                 mutable = False,
@@ -208,7 +230,7 @@ class ScopeChecker:
 
         for child in fn.body:
             match child:
-                case nodes.ValueDeclaration():
+                case types.ValueDeclaration():
                     if child.name in function_scope.variables:
                         self._error(f"Variable '{child.name}' is already defined in function '{fn.name}'")
                         continue
@@ -220,17 +242,17 @@ class ScopeChecker:
                             f"expected {self._type_name(child.typ)}, got {self._type_name(expression_type)}"
                         )
 
-                    function_scope.variables[child.name] = ValueSignature(
+                    function_scope.variables[child.name] = typesignature(
                         name = child.name,
                         real_name = child.real_name,
                         mutable = child.mutable,
                         typ = child.typ,
                     )
 
-                case nodes.ReturnStatement():
+                case types.ReturnStatement():
                     has_return = True
                     if child.expr is None:
-                        if fn.typ is not values.Void:
+                        if fn.typ is not types.Void:
                             self._error(f"Function '{fn.name}' must return {self._type_name(fn.typ)}")
                         continue
 
@@ -238,7 +260,7 @@ class ScopeChecker:
                     if return_type is None:
                         continue
 
-                    if fn.typ is values.Void:
+                    if fn.typ is types.Void:
                         self._error(f"Function '{fn.name}' returns a value, but return type is void")
                         continue
 
@@ -248,7 +270,7 @@ class ScopeChecker:
                             f"expected {self._type_name(fn.typ)}, got {self._type_name(return_type)}"
                         )
 
-                case nodes.FunctionDeclaration():
+                case types.FunctionDeclaration():
                     if child.name in function_scope.functions:
                         self._error(f"Nested function '{child.name}' is already defined in '{fn.name}'")
                     else:
@@ -264,32 +286,38 @@ class ScopeChecker:
                 case _:
                     self._error(f"Unsupported node in function '{fn.name}': {type(child)}")
 
-        if not has_return and fn.typ is not values.Void:
+        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: nodes.Expression, active_scope: Scope) -> Optional[type[values.Integer64] | type[values.String] | type[values.Void]]:
+    def _infer_expression_type(self, expr: types.Expression, active_scope: Scope) -> Optional[type[types.Integer64] | type[types.String] | type[types.Void]]:
         match expr:
-            case nodes.PrimaryExpression(value=values.Integer64()):
-                return values.Integer64
-            case nodes.PrimaryExpression(value=values.String()):
-                return values.String
-            case nodes.PrimaryExpression(value=values.Variable(name=name)):
+            case types.PrimaryExpression(types.Integer64()):
+                return types.Integer64
+            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
                 return signature.typ
-            case nodes.UnaryExpression(expr=inner_expr, sign=sign):
+            case types.PrimaryExpression(types.FunctionCall(name, args)):
+                signature = active_scope.resolve_function(self, name, args)
+                if signature is None:
+                    self._error(f"Unknown function '{name}' with given argument signature")
+                    return None
+                return signature.typ
+            case types.UnaryExpression(expr=inner_expr, sign=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 values.Integer64:
+                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 nodes.BinaryExpression(left=left, right=right, op=op):
+            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:
@@ -300,12 +328,12 @@ class ScopeChecker:
                         f"{self._type_name(left_type)} vs {self._type_name(right_type)}"
                     )
                     return None
-                if left_type is not values.Integer64:
+                if left_type is not types.Integer64:
                     self._error(
                         f"Operator '{op}' currently only supports i64, got {self._type_name(left_type)}"
                     )
                     return None
-                return values.Integer64
+                return types.Integer64
             case _:
                 self._error(f"Unsupported expression type: {type(expr)}")
                 return None
@@ -314,11 +342,11 @@ class ScopeChecker:
         return self._type_token(left) == self._type_token(right)
 
     def _type_token(self, typ: object) -> str:
-        if typ is values.Integer64 or isinstance(typ, values.Integer64):
+        if typ is types.Integer64 or isinstance(typ, types.Integer64):
             return "i64"
-        if typ is values.String or isinstance(typ, values.String):
+        if typ is types.String or isinstance(typ, types.String):
             return "str"
-        if typ is values.Void or isinstance(typ, values.Void):
+        if typ is types.Void or isinstance(typ, types.Void):
             return "void"
         return str(typ)
 

+ 3 - 3
src/zelkelc/validator/validator.py

@@ -1,6 +1,6 @@
 from __future__ import annotations
 
-import src.zelkelc.ast.nodes as nodes
+import src.zelkelc.ast.types as types
 import src.zelkelc.validator.scope as scope
 
 
@@ -8,7 +8,7 @@ class SemanticCheckFailed(Exception):
 	pass
 
 
-def validate_ast(tree: list[nodes.Node]) -> scope.Scope:
+def validate_ast(tree: list[types.Node]) -> scope.Scope:
 	checker = scope.ScopeChecker()
 	global_scope, errors = checker.check(tree)
 
@@ -22,5 +22,5 @@ def validate_ast(tree: list[nodes.Node]) -> scope.Scope:
 class Validator:
 	scope: scope.Scope
 
-	def __init__(self, tree: list[nodes.Node]):
+	def __init__(self, tree: list[types.Node]):
 		self.scope = validate_ast(tree)


+ 16 - 0
test.ll

@@ -0,0 +1,16 @@
+@.str = private unnamed_addr constant [4 x i8] c"%d\0A\00", align 1
+
+declare i32 @printf(ptr, ...)
+
+define i32 @main() {
+entry:
+%0 = add i32 10, 0
+%1 = add i32 5, 0
+%2 = add i32 5, 0
+%3 = mul i32 %1, %2
+%4 = add i32 %0, %3
+%v_19_test = add i32 %4, 0
+%call = call i32 (ptr, ...) @printf(ptr @.str, i32 %v_19_test)
+
+ret i32 0
+}