Johan Rong 3 日 前
親
コミット
5b1404d1a1

+ 2 - 2
.gitignore

@@ -1,4 +1,4 @@
 .direnv/
+.ruff_cache
 __pycache__/
-test.ll
-test
+build/

+ 21 - 0
examples/testing/src/main.zk

@@ -0,0 +1,21 @@
+#llvm(
+    #"@int.fmt = private unnamed_addr constant [5 x i8] c"%ld\0A\00""#
+)
+
+#llvm(
+    #"declare i32 @printf(ptr, ...)"#
+)
+
+fn main() -> void {
+    val test: i64 = 2 + 2*3
+    
+    #llvm(
+        "%call = call i32 (ptr, ...) @printf(ptr @int.fmt, i64 %v_27_test)"
+    )
+    
+    #llvm(
+        "ret void"
+    )
+    
+    return
+}

+ 2 - 0
examples/testing/zelkel.toml

@@ -0,0 +1,2 @@
+[package]
+name = "testing"

+ 1 - 0
flake.nix

@@ -21,6 +21,7 @@
       devShells.${system}.default = pkgs.mkShell {
         packages = [
           pythonEnv
+          pkgs.ruff
         ];
 
         shellHook = ''

+ 0 - 22
main.ll

@@ -1,22 +0,0 @@
-declare i32 @printf(ptr, ...)
-@int.fmt = private unnamed_addr constant [5 x i8] c"%ldA"
-
-
-
-
-define void @zkmain() {
-entry:
-%0 = add i64 2, 0
-%1 = add i64 2, 0
-%2 = add i64 3, 0
-%3 = mul i64 %1, %2
-%4 = add i64 %0, %3
-%v_27_test = add i64 %4, 0
-%call = call i32 (ptr, ...) @printf(ptr @int.fmt, i64 %v_27_test)
-}
-
-define i32 @main() {
-entry:
-call void @zkmain()
-ret i32 0
-}

+ 28 - 0
src/zelkelbt/__main__.py

@@ -0,0 +1,28 @@
+from src.zelkelbt.config.config import has_config
+
+
+def print_help():
+    print("""
+Zelkel Buildtool
+
+Usage: zk [OPTIONS] [COMMAND]
+
+Options:
+    -V, --version
+""")
+
+
+if __name__ == "__main__":
+    print(has_config())
+
+# tokens = lex(source)
+# print(tokens)
+# parser = Parser(tokens, 0)
+# ast = parser.parse()
+# rich.print(ast)
+
+# Validator(ast)
+
+# codegen = Codegen(ast)
+# ir = codegen.generate()
+# print(ir)

+ 5 - 0
src/zelkelbt/config/config.py

@@ -0,0 +1,5 @@
+from pathlib import Path
+
+
+def has_config():
+    return Path("zelkel.toml").is_file()

+ 0 - 39
src/zelkelc/__main__.py

@@ -1,39 +0,0 @@
-from src.zelkelc.lexer.tokenizer import lex
-from src.zelkelc.ast.parser import Parser
-from src.zelkelc.validator.validator import Validator
-from src.zelkelc.codegen.codegen import Codegen
-
-import rich
-
-source = """
-#llvm(
-    #"@int.fmt = private unnamed_addr constant [5 x i8] c"%ld\0A\00""#
-)
-
-#llvm(
-    #"declare i32 @printf(ptr, ...)"#
-)
-
-fn main() -> void {
-    val test: i64 = 2 + 2*3
-    
-    #llvm(
-        "%call = call i32 (ptr, ...) @printf(ptr @int.fmt, i64 %v_27_test)"
-    )
-    
-    return
-}
-"""
-
-if __name__ == "__main__":
-    tokens = lex(source)
-    print(tokens)
-    parser = Parser(tokens, 0)
-    ast = parser.parse()
-    rich.print(ast)
-    
-    Validator(ast)
-    
-    codegen = Codegen(ast)
-    ir = codegen.generate()
-    print(ir)

+ 14 - 3
src/zelkelc/ast/nodes.py

@@ -3,6 +3,7 @@ from __future__ import annotations
 from dataclasses import dataclass
 import src.zelkelc.ast.values as values
 
+
 @dataclass
 class ClassDeclaration:
     name: str
@@ -10,12 +11,14 @@ class ClassDeclaration:
     methods: list[FunctionDeclaration]
     irname: str
 
+
 @dataclass
 class StructDeclaration:
     name: str
     members: list[MemberDeclaration]
     irname: str
 
+
 @dataclass
 class MemberDeclaration:
     name: str
@@ -23,6 +26,7 @@ class MemberDeclaration:
     typ: values.Value
     real_idx: int
 
+
 @dataclass
 class ValueDeclaration:
     name: str
@@ -30,7 +34,8 @@ class ValueDeclaration:
     typ: values.Value
     expr: Expression
     irname: str
-    
+
+
 @dataclass
 class FunctionDeclaration:
     name: str
@@ -38,26 +43,32 @@ class FunctionDeclaration:
     args: dict[str, values.Value]
     body: list[Node]
     irname: 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: values.Value
 
-Expression = BinaryExpression | UnaryExpression | PrimaryExpression
+
+Expression = BinaryExpression | UnaryExpression | PrimaryExpression

+ 155 - 142
src/zelkelc/ast/parser.py

@@ -1,38 +1,36 @@
-import src.zelkelc.lexer.token as token
-import src.zelkelc.ast.types as types
-
+import sys
 from typing import Optional
 
+from src.zelkelc.ast import types
+from src.zelkelc.lexer import token
+
+
 class Parser:
     tokens: list[token.Token]
     cursor: int
-        
+
     def __init__(self, tokens: list[token.Token], cursor: int):
         self.tokens = tokens
         self.cursor = cursor
-    
-    def expect(self, tok: token.Token): 
-        if not self.tokens[self.cursor] == tok:
+
+    def expect(self, tok: token.Token):
+        if self.tokens[self.cursor] != tok:
             print(f"Expected {tok}, but found {self.tokens[self.cursor]}")
-            exit(1)
+            sys.exit(1)
         self.cursor += 1
-            
+
     def expect_type(self, tok_type: token.Token) -> token.Token:
-        if not type(self.tokens[self.cursor]) == tok_type:
+        if type(self.tokens[self.cursor]) is not tok_type:
             print(f"Expected {tok_type}, but found {type(self.tokens[self.cursor])}")
-            exit(1)
+            sys.exit(1)
         self.cursor += 1
         return self.tokens[self.cursor - 1]
-    
-    def current(self, tok: token.Token): 
-        if not self.tokens[self.cursor] == tok:
-            return False
-        return True
+
+    def current(self, tok: token.Token):
+        return self.tokens[self.cursor] == tok
 
     def current_type(self, tok_type: token.Token) -> bool:
-        if not type(self.tokens[self.cursor]) == tok_type:
-            return False
-        return True
+        return type(self.tokens[self.cursor]) is tok_type
 
     def parse_type(self, is_return_type: bool) -> types.Value:
         identifier = self.expect_type(token.Identifier)
@@ -43,179 +41,188 @@ class Parser:
                 return types.Integer64
             case "f64":
                 return types.Float64
-            case "void" if is_return_type == True:
+            case "void" if is_return_type:
                 return types.Void
             case _:
                 print("Invalid type found in parse_type")
-                exit(1)
+                sys.exit(1)
 
     def parse(self):
         ast: list[types.FunctionDeclaration | types.ClassDeclaration] = []
-        
+
         while self.cursor < len(self.tokens):
             t = self.tokens[self.cursor]
-            
+
             match t:
                 case token.Identifier():
                     match t.value:
                         case "fn":
-                            ast.append(self.parse_function_declaration("_main.zk_")) # TODO: replace with actual filepath to the file
+                            ast.append(
+                                self.parse_function_declaration("_main.zk_")
+                            )  # TODO: replace with actual filepath to the file
                         case "struct":
                             ast.append(self.parse_struct_declaration("_main.zk_"))
                         case "class":
                             ast.append(self.parse_class_declaration("_main.zk_"))
                         case _:
                             print(f"Invalid keyword in global {t.value}")
-                            exit(1)
-                case token.Pound(): # TODO: hard coded to that all macro calls are treated as #llvm() macro calls
+                            sys.exit(1)
+                case token.Pound():  # TODO: hard coded to that all macro calls are treated as #llvm() macro calls
                     self.expect(token.Pound())
                     ident = self.expect_type(token.Identifier)
-                    
+
                     match ident.value:
                         case "fn":
-                            print(f"Macro declaration is not implemented")
-                            exit(1)
+                            print("Macro declaration is not implemented")
+                            sys.exit(1)
                         case _:
                             ast.append(self.parse_macro_call(ident))
                 case _:
                     print(f"Invalid token found while parsing global {t}")
-                    exit(1)
-                            
-        
+                    sys.exit(1)
+
         return ast
-    
+
     def parse_macro_call(self, ident: token.Identifier) -> types.MacroCall:
         # Hard core builtin #llvm macro block
-        
+
         self.expect(token.LParen())
         body = self.expect_type(token.String)
         self.expect(token.RParen())
-        
+
         return types.MacroCall(body.value)
-                
 
     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[types.MemberDeclaration] = []
-                
-        while self.cursor < len(self.tokens) and not self.tokens[self.cursor] == token.RBrace():
+
+        while (
+            self.cursor < len(self.tokens)
+            and self.tokens[self.cursor] != token.RBrace()
+        ):
             t = self.tokens[self.cursor]
-            
+
             match t:
                 case token.Identifier():
                     match t.value:
                         case "val":
-                            members.append(self.parse_member_declaration(False, len(members)))
+                            members.append(
+                                self.parse_member_declaration(False, len(members))
+                            )
                         case "var":
-                            members.append(self.parse_member_declaration(True, len(members)))
+                            members.append(
+                                self.parse_member_declaration(True, len(members))
+                            )
                         case _:
                             print(f"Invalid keyword in struct body {t.value}")
-                            exit(1)
+                            sys.exit(1)
                 case _:
                     print(f"Invalid token found while parsing class declaration: {t}")
-                    exit(1)
-        
+                    sys.exit(1)
+
         self.expect(token.RBrace())
-        
-        return types.StructDeclaration(
-            name,
-            members,
-            irname = f"{path}.s_{name}"
-        )
-    
-    def parse_member_declaration(self, mutable: bool, member_idx: int) -> types.MemberDeclaration:
+
+        return types.StructDeclaration(name, members, irname=f"{path}.s_{name}")
+
+    def parse_member_declaration(
+        self, mutable: bool, member_idx: int
+    ) -> types.MemberDeclaration:
         if mutable:
             self.expect(token.Identifier("var"))
         else:
             self.expect(token.Identifier("val"))
-        
+
         name = self.expect_type(token.Identifier).value
-        
+
         self.expect(token.Colon())
         typ = self.parse_type(False)
-        
-        return types.MemberDeclaration(
-            name,
-            mutable,
-            typ,
-            real_idx = member_idx
-        )
-    
+
+        return types.MemberDeclaration(name, mutable, typ, real_idx=member_idx)
+
     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[types.ValueDeclaration] = []
         methods: list[types.FunctionDeclaration] = []
-        
-        while self.cursor < len(self.tokens) and not self.tokens[self.cursor] == token.RBrace():
+
+        while (
+            self.cursor < len(self.tokens)
+            and self.tokens[self.cursor] != token.RBrace()
+        ):
             t = self.tokens[self.cursor]
 
             match t:
                 case token.Identifier():
                     match t.value:
                         case "fn":
-                            methods.append(self.parse_function_declaration(f"{path}.c_{name}"))
+                            methods.append(
+                                self.parse_function_declaration(f"{path}.c_{name}")
+                            )
                         case "val":
-                            members.append(self.parse_member_declaration(False, len(members)))
+                            members.append(
+                                self.parse_member_declaration(False, len(members))
+                            )
                         case "var":
-                            members.append(self.parse_member_declaration(True, len(members)))
+                            members.append(
+                                self.parse_member_declaration(True, len(members))
+                            )
                         case _:
                             print(f"Invalid keyword in class body {t.value}")
-                            exit(1)
+                            sys.exit(1)
                 case _:
                     print(f"Invalid token found while parsing class declaration: {t}")
-                    exit(1)
-        
+                    sys.exit(1)
+
         self.expect(token.RBrace())
-        
-        return types.ClassDeclaration(
-            name,
-            members,
-            methods,
-            irname = f"{path}.c_{name}"
-        )
-    
+
+        return types.ClassDeclaration(name, members, methods, irname=f"{path}.c_{name}")
+
     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, types.Value] = {}
         while self.cursor < len(self.tokens) and not self.current(token.RParen()):
             arg_name = self.expect_type(token.Identifier).value
-            
+
             self.expect(token.Colon())
             typ = self.parse_type(False)
             args[arg_name] = typ
-            
+
             if not self.current(token.Comma()):
                 break
             else:
                 self.expect(token.Comma())
-        
+
         self.expect(token.RParen())
-        
+
         self.expect(token.Arrow())
         typ = self.parse_type(True)
-        
+
         self.expect(token.LBrace())
-        
+
         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():
+        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 self.tokens[self.cursor] != token.RBrace()
+        ):
             t = self.tokens[self.cursor]
-            
+
             match t:
                 case token.Identifier():
                     match t.value:
                         case "fn":
-                            body.append(self.parse_function_declaration(f"{path}.f_{name}"))
+                            body.append(
+                                self.parse_function_declaration(f"{path}.f_{name}")
+                            )
                         case "val":
                             body.append(self.parse_value_declaration(False))
                         case "var":
@@ -225,74 +232,64 @@ class Parser:
                             hasReturn = True
                         case _:
                             print(f"Invalid keyword in function body {t.value}")
-                            exit(1)
-                case token.Pound(): # TODO: hard coded to that all macro calls are treated as #llvm() macro calls
+                            sys.exit(1)
+                case token.Pound():  # TODO: hard coded to that all macro calls are treated as #llvm() macro calls
                     self.expect(token.Pound())
                     ident = self.expect_type(token.Identifier)
-                    
+
                     match ident.value:
                         case "fn":
-                            print(f"Macro declaration is not implemented")
-                            exit(1)
+                            print("Macro declaration is not implemented")
+                            sys.exit(1)
                         case _:
                             body.append(self.parse_macro_call(ident))
                 case _:
-                    #TODO: expression statement
+                    # TODO: expression statement
                     print(f"implement expression statement: {t}")
-                    exit(1)
-        
-        if hasReturn == False:
+                    sys.exit(1)
+
+        if hasReturn:
             print("Function must have return")
-            exit(1)
-        
+            sys.exit(1)
+
         self.expect(token.RBrace())
-        
+
         irname = f"{path}.f_{name}_{typ}"
         if name == "main":
             irname = "zkmain"
-        
+
         return types.FunctionDeclaration(
-            name = name,
-            typ = typ,
-            args = args,
-            body = body,
-            irname = irname
+            name=name, typ=typ, args=args, body=body, irname=irname
         )
-    
+
     def parse_return(self) -> types.ReturnStatement:
         self.expect(token.Identifier("return"))
-        
-        expr: Optional[types.Expression] = None    
+
+        expr: types.Expression | None = None
         if not self.current(token.RBrace()):
             expr = self.parse_expression()
 
-        return types.ReturnStatement(
-            expr
-        )
-    
+        return types.ReturnStatement(expr)
+
     def parse_value_declaration(self, mutable: bool) -> types.ValueDeclaration:
         if mutable:
             self.expect(token.Identifier("var"))
         else:
             self.expect(token.Identifier("val"))
-        
+
         name = self.expect_type(token.Identifier).value
-        
+
         self.expect(token.Colon())
         typ = self.parse_type(False)
-        
+
         self.expect(token.Equals())
-        
+
         expr = self.parse_expression()
-        
+
         return types.ValueDeclaration(
-            name,
-            mutable,
-            typ,
-            expr,
-            irname = f"v_{self.cursor}_{name}"
+            name, mutable, typ, expr, irname=f"v_{self.cursor}_{name}"
         )
-    
+
     def parse_expression(self) -> types.Expression:
         return self.parse_binary_expression(0)
 
@@ -321,7 +318,7 @@ class Parser:
 
             if op not in ("-", "+"):
                 print(f"Invalid unary operator: {op}")
-                exit(1)
+                sys.exit(1)
             operand = self.parse_unary_expression()
             return types.UnaryExpression(operand, op, operand.typ)
 
@@ -333,24 +330,33 @@ class Parser:
         match t:
             case token.Identifier():
                 self.cursor += 1
-                
+
                 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():
+                    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, None), types.FunctionCall)
+                    return types.PrimaryExpression(
+                        types.FunctionCall(t.value, args, None), types.FunctionCall
+                    )
                 else:
-                    return types.PrimaryExpression(types.VariableRef(t.value, None, None), types.VariableRef)
+                    return types.PrimaryExpression(
+                        types.VariableRef(t.value, None, None), types.VariableRef
+                    )
 
             case token.Integer():
                 self.cursor += 1
-                return types.PrimaryExpression(types.Integer64(t.value), types.Integer64)
-            
+                return types.PrimaryExpression(
+                    types.Integer64(t.value), types.Integer64
+                )
+
             case token.Float():
                 self.cursor += 1
                 return types.PrimaryExpression(types.Float64(t.value), types.Float64)
@@ -372,10 +378,17 @@ class Parser:
         table = {
             "||": 1,
             "&&": 2,
-            "==": 3, "!=": 3,
-            "<": 4, "<=": 4, ">": 4, ">=": 4,
-            "+": 5, "-": 5,
-            "*": 6, "/": 6, "%": 6,
+            "==": 3,
+            "!=": 3,
+            "<": 4,
+            "<=": 4,
+            ">": 4,
+            ">=": 4,
+            "+": 5,
+            "-": 5,
+            "*": 6,
+            "/": 6,
+            "%": 6,
         }
-        
-        return table.get(op, 0)
+
+        return table.get(op, 0)

+ 42 - 16
src/zelkelc/ast/types.py

@@ -1,8 +1,10 @@
 from __future__ import annotations
 
 from dataclasses import dataclass
+import sys
 from typing import Optional
 
+
 @dataclass
 class ClassDeclaration:
     name: str
@@ -10,12 +12,14 @@ class ClassDeclaration:
     methods: list[FunctionDeclaration]
     irname: str
 
+
 @dataclass
 class StructDeclaration:
     name: str
     members: list[MemberDeclaration]
     irname: str
 
+
 @dataclass
 class MemberDeclaration:
     name: str
@@ -23,6 +27,7 @@ class MemberDeclaration:
     typ: Value
     real_idx: int
 
+
 @dataclass
 class ValueDeclaration:
     name: str
@@ -30,7 +35,8 @@ class ValueDeclaration:
     typ: Value
     expr: Expression
     irname: str
-    
+
+
 @dataclass
 class FunctionDeclaration:
     name: str
@@ -38,16 +44,26 @@ class FunctionDeclaration:
     args: dict[str, Value]
     body: list[Node]
     irname: str
-    
+
+
 @dataclass
 class ReturnStatement:
     expr: Optional[Expression]
-    
+
+
 @dataclass
 class MacroCall:
     body: str
 
-Node = ClassDeclaration | ValueDeclaration | FunctionDeclaration | StructDeclaration | MacroCall
+
+Node = (
+    ClassDeclaration
+    | ValueDeclaration
+    | FunctionDeclaration
+    | StructDeclaration
+    | MacroCall
+)
+
 
 @dataclass
 class BinaryExpression:
@@ -56,67 +72,74 @@ class BinaryExpression:
     op: str
     typ: Value
 
+
 @dataclass
 class UnaryExpression:
     expr: Expression
     sign: str
     typ: Value
 
+
 @dataclass
 class PrimaryExpression:
     value: Value
     typ: 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 Float64:
     value: int
-    
+
     def to_llvm() -> str:
         return "f64"
-    
+
     def __eq__(self, other):
         if not isinstance(other, Float64):
             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)
+        sys.exit(1)
+
 
 @dataclass
 class Void:
-    pass
-
     def to_llvm() -> str:
         return "void"
 
+
 @dataclass
 class VariableRef:
     name: str
     irname: str
     typ: Value
-    
+
     def to_llvm() -> str:
         print("Cannot convert Variable to llvm ir")
-        exit(1)
+        sys.exit(1)
+
 
 @dataclass
 class FunctionCall:
@@ -124,12 +147,15 @@ class FunctionCall:
     irname: str
     args: list[Expression]
 
+
 @dataclass
 class Ptr[T]:
     pass
 
+
 @dataclass
 class Val[T]:
     pass
 
-Value = Integer64 | Float64 | String | Void | VariableRef | FunctionCall
+
+Value = Integer64 | Float64 | String | Void | VariableRef | FunctionCall

+ 78 - 54
src/zelkelc/codegen/codegen.py

@@ -1,4 +1,7 @@
-import src.zelkelc.ast.types as types
+import sys
+
+from src.zelkelc.ast import types
+
 
 class IRTable:
     top: str
@@ -6,7 +9,7 @@ class IRTable:
     struct_functions: str
     global_functions: str
     bottom: str
-    
+
     def __init__(self):
         self.top = ""
         self.structs = ""
@@ -14,16 +17,17 @@ class IRTable:
         self.global_functions = ""
         self.bottom = ""
 
+
 class Codegen:
     ast: list[types.FunctionDeclaration | types.ClassDeclaration]
     ir: IRTable
     temp_counter: int
-    
+
     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:
@@ -37,44 +41,54 @@ class Codegen:
                     self.ir.top = rf"{node.body}" + "\n" + self.ir.top
                 case _:
                     print(f"Invalid node found during codegen {type(node)}")
-                    exit(1)
-                    
+                    sys.exit(1)
+
         self.gen_main_function()
-        
-        return self.ir.top + "\n" + self.ir.structs + "\n" + self.ir.struct_functions + "\n" + self.ir.global_functions + "\n" + self.ir.bottom
-    
+
+        return (
+            self.ir.top
+            + "\n"
+            + self.ir.structs
+            + "\n"
+            + self.ir.struct_functions
+            + "\n"
+            + self.ir.global_functions
+            + "\n"
+            + self.ir.bottom
+        )
+
     def gen_main_function(self):
         self.ir.bottom += "define i32 @main() {\n"
         self.ir.bottom += "entry:\n"
         self.ir.bottom += "call void @zkmain()\n"
         self.ir.bottom += "ret i32 0\n"
         self.ir.bottom += "}\n"
-    
+
     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()}"
+            members_str += f"{', ' if i != 0 else ''}{m.typ.to_llvm()}"
 
         self.ir.structs += f"\n%{node.irname} = type " + "{ " + members_str + " }"
 
     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()}"
-        
+            members_str += f"{', ' if i != 0 else ''}{m.typ.to_llvm()}"
+
         self.ir.structs += f"\n%{node.irname} = type " + "{ " + members_str + " }"
-        
+
         for func in node.methods:
             self.ir.struct_functions += self.gen_function_declaration(func)
-    
+
     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}"
-            
-        ir = f"\ndefine {str(node.typ.to_llvm())} @{node.irname}({args_str}) " + "{"
+            args_str += f"{', ' if i != 0 else ''}{typ.to_llvm()} %{name}"
+
+        ir = f"\ndefine {node.typ.to_llvm()!s} @{node.irname}({args_str}) " + "{"
         ir += "\nentry:"
-        
+
         for child_node in node.body:
             match child_node:
                 case types.ValueDeclaration():
@@ -82,75 +96,79 @@ class Codegen:
                 case types.MacroCall():
                     ir += "\n" + rf"{child_node.body}"
                 case types.ReturnStatement():
-                    print(f"gen_function_declaration() {type(child_node)} is unimplemented")
+                    print(
+                        f"gen_function_declaration() {type(child_node)} is unimplemented"
+                    )
                     continue
                 case _:
-                    print(f"gen_function_declaration() {type(child_node)} is unimplemented")
-                    exit(1)
-        
+                    print(
+                        f"gen_function_declaration() {type(child_node)} is unimplemented"
+                    )
+                    sys.exit(1)
+
         return ir + "\n}\n"
-    
+
     def gen_value_declaration(self, node: types.ValueDeclaration) -> str:
         ir = ""
-        
+
         count, expr = self.gen_expression(node.expr)
         ir += expr
-        
+
         match node.typ:
             case types.Integer64:
-                if node.mutable == True:
+                if node.mutable:
                     ir += f"\n%{node.irname} = alloca i64, align 8"
                     ir += f"\nstore i64 %{count}, i64* %{node.irname}, align 8"
                 else:
                     ir += f"\n%{node.irname} = add i64 %{count}, 0"
             case types.Float64:
-                if node.mutable == True:
+                if node.mutable:
                     ir += f"\n%{node.irname} = alloca f64, align 8"
                     ir += f"\nstore f64 %{count}, f64* %{node.irname}, align 8"
                 else:
                     ir += f"\n%{node.irname} = fadd f64 %{count}, 0"
             case _:
                 print(f"gen_value_declaration() for {node.typ} is unimplemented")
-                exit(1)
-            
+                sys.exit(1)
+
         return ir
-    
+
     def make_i64_op(self, sign: str) -> str:
         match sign:
-            case '+':
+            case "+":
                 return "add"
-            case '-':
+            case "-":
                 return "sub"
-            case '*':
+            case "*":
                 return "mul"
             case _:
                 print(f"Cannot do operation '{sign}' on i64")
-                exit(1)
-                
+                sys.exit(1)
+
     def make_f64_op(self, sign: str) -> str:
         match sign:
-            case '+':
+            case "+":
                 return "fadd"
-            case '-':
+            case "-":
                 return "fsub"
-            case '*':
+            case "*":
                 return "fmul"
-            case '/':
+            case "/":
                 return "fdiv"
             case _:
                 print(f"Cannot do operation '{sign}' on f64")
-                exit(1)
-    
+                sys.exit(1)
+
     def gen_expression(self, node: types.Expression) -> tuple[str, str]:
         ir = ""
-        
+
         match type(node):
             case types.BinaryExpression:
                 left_irname, left = self.gen_expression(node.left)
                 right_irname, right = self.gen_expression(node.right)
-                
+
                 ir += left + right
-                        
+
                 match node.left.typ:
                     case types.Integer64:
                         ir += f"\n%{self.temp_counter} = {self.make_i64_op(node.op)} i64 %{left_irname}, %{right_irname}"
@@ -167,14 +185,18 @@ class Codegen:
                                 ir += f"\n%{self.temp_counter} = {self.make_f64_op(node.op)} f64 %{left_irname}, %{right_irname}"
                                 self.temp_counter += 1
                             case _:
-                                print(f"gen_expression() for BinaryExpression left operand variable type '{node.left.value.typ}' is unimplemented")
+                                print(
+                                    f"gen_expression() for BinaryExpression left operand variable type '{node.left.value.typ}' is unimplemented"
+                                )
                     case _:
-                       print(f"gen_expression() for BinaryExpression for {node.left.typ}")
-                       exit(1)
+                        print(
+                            f"gen_expression() for BinaryExpression for {node.left.typ}"
+                        )
+                        sys.exit(1)
             case types.UnaryExpression:
                 print("gen_expression() for UnaryExpression unimplemented")
-                exit(1)
-                
+                sys.exit(1)
+
             case types.PrimaryExpression:
                 match type(node.value):
                     case types.Integer64:
@@ -186,10 +208,12 @@ class Codegen:
                     case types.VariableRef:
                         return node.value.irname, ir
                     case _:
-                        print(f"gen_expression() for PrimaryExpression for {type(node.value)}")
-                        exit(1)
+                        print(
+                            f"gen_expression() for PrimaryExpression for {type(node.value)}"
+                        )
+                        sys.exit(1)
             case _:
                 print(f"Invalid expression types found during codegen: {node}")
-                exit(1)
+                sys.exit(1)
 
-        return str(self.temp_counter - 1), ir
+        return str(self.temp_counter - 1), ir

+ 33 - 2
src/zelkelc/lexer/token.py

@@ -1,63 +1,94 @@
 from dataclasses import dataclass
 
+
 @dataclass
 class Identifier:
     value: str
 
+
 @dataclass
 class String:
     value: str
 
+
 @dataclass
 class Integer:
     value: int
-    
+
+
 @dataclass
 class Float:
     value: float
 
+
 @dataclass
 class Pound:
     pass
 
+
 @dataclass
 class LParen:
     pass
 
+
 @dataclass
 class RParen:
     pass
 
+
 @dataclass
 class LBrace:
     pass
 
+
 @dataclass
 class RBrace:
     pass
 
+
 @dataclass
 class Period:
     pass
 
+
 @dataclass
 class Comma:
     pass
 
+
 @dataclass
 class Arrow:
     pass
 
+
 @dataclass
 class Colon:
     pass
 
+
 @dataclass
 class Equals:
     pass
 
+
 @dataclass
 class Operator:
     value: str
 
-Token = Identifier | String | Integer | Float | LParen | RParen | LBrace | RBrace | Period | Comma | Arrow | Colon | Equals | Operator
+
+Token = (
+    Identifier
+    | String
+    | Integer
+    | Float
+    | LParen
+    | RParen
+    | LBrace
+    | RBrace
+    | Period
+    | Comma
+    | Arrow
+    | Colon
+    | Equals
+    | Operator
+)

+ 43 - 42
src/zelkelc/lexer/tokenizer.py

@@ -1,50 +1,51 @@
-import src.zelkelc.lexer.token as token
+from src.zelkelc.lexer import token
+
 
 def lex(source: str) -> list[token.Token]:
     tokens: list[token.Token] = []
-    
+
     i = 0
     while i < len(source):
         c = source[i]
-        if c == '/' and source[i + 1] == '/':
+        if c == "/" and source[i + 1] == "/":
             i += 2
             for _, comment_c in enumerate(source[i:]):
-                i += 1                
-                if comment_c == '\n':
+                i += 1
+                if comment_c == "\n":
                     break
         elif c.isnumeric():
             i += 1
-            
+
             isfloat = False
             num = c
             for _, num_c in enumerate(source[i:]):
-                if num_c == '_':
+                if num_c == "_":
                     i += 1
                     continue
-                if num_c == '.':
+                if num_c == ".":
                     isfloat = True
                 elif not num_c.isnumeric():
                     break
                 i += 1
                 num += num_c
-                
+
             if isfloat:
                 tokens.append(token.Float(float(num)))
             else:
                 tokens.append(token.Integer(int(num)))
-        elif c.isalpha() or c == '_':
+        elif c.isalpha() or c == "_":
             i += 1
-            
+
             identifier = c
             for _, identifier_c in enumerate(source[i:]):
-                if not (identifier_c.isalnum() or identifier_c == '_'):
+                if not (identifier_c.isalnum() or identifier_c == "_"):
                     break
                 i += 1
                 identifier += identifier_c
             tokens.append(token.Identifier(identifier))
         elif c == '"':
             i += 1
-            
+
             string = ""
             for _, string_c in enumerate(source[i:]):
                 if string_c == '"':
@@ -61,7 +62,7 @@ def lex(source: str) -> list[token.Token]:
 
             string = ""
             while i < len(source):
-                if source[i] == '"' and i + 1 < len(source) and source[i + 1] == '#':
+                if source[i] == '"' and i + 1 < len(source) and source[i + 1] == "#":
                     i += 2
                     tokens.append(token.String(string))
                     break
@@ -70,74 +71,74 @@ def lex(source: str) -> list[token.Token]:
             else:
                 print("No '\"#' to close string")
                 break
-        elif c == '-' and source[i + 1] == '>':
+        elif c == "-" and source[i + 1] == ">":
             tokens.append(token.Arrow())
             i += 2
-        elif c == '<' and source[i + 1] == '=':
+        elif c == "<" and source[i + 1] == "=":
             tokens.append(token.Operator("<="))
             i += 2
-        elif c == '>' and source[i + 1] == '=':
+        elif c == ">" and source[i + 1] == "=":
             tokens.append(token.Operator(">="))
             i += 2
-        elif c == '|' and source[i + 1] == '|':
+        elif c == "|" and source[i + 1] == "|":
             tokens.append(token.Operator("||"))
             i += 2
-        elif c ==  '&' and source[i + 1] == '&':
+        elif c == "&" and source[i + 1] == "&":
             tokens.append(token.Operator("&&"))
             i += 2
-        elif c == '=' and source[i + 1] == '=':
+        elif c == "=" and source[i + 1] == "=":
             tokens.append(token.Operator("=="))
             i += 2
-        elif c == '!' and source[i + 1] == '=':
+        elif c == "!" and source[i + 1] == "=":
             tokens.append(token.Operator("!="))
             i += 2
         else:
             match c:
-                case ' ' | '\n':
+                case " " | "\n":
                     i += 1
-                case '#':
+                case "#":
                     tokens.append(token.Pound())
                     i += 1
-                case '(':
+                case "(":
                     tokens.append(token.LParen())
                     i += 1
-                case ')':
+                case ")":
                     tokens.append(token.RParen())
                     i += 1
-                case '{':
+                case "{":
                     tokens.append(token.LBrace())
                     i += 1
-                case '}':
+                case "}":
                     tokens.append(token.RBrace())
                     i += 1
-                case '.':
+                case ".":
                     tokens.append(token.Period())
                     i += 1
-                case ',':
+                case ",":
                     tokens.append(token.Comma())
                     i += 1
-                case ':':
+                case ":":
                     tokens.append(token.Colon())
                     i += 1
-                case '=':
+                case "=":
                     tokens.append(token.Equals())
                     i += 1
-                case '+':
-                    tokens.append(token.Operator('+'))
+                case "+":
+                    tokens.append(token.Operator("+"))
                     i += 1
-                case '-':
-                    tokens.append(token.Operator('-'))
+                case "-":
+                    tokens.append(token.Operator("-"))
                     i += 1
-                case '*':
-                    tokens.append(token.Operator('*'))
+                case "*":
+                    tokens.append(token.Operator("*"))
                     i += 1
-                case '/':
-                    tokens.append(token.Operator('/'))
+                case "/":
+                    tokens.append(token.Operator("/"))
                     i += 1
-                case '%':
-                    tokens.append(token.Operator('%'))
+                case "%":
+                    tokens.append(token.Operator("%"))
                 case _:
                     print(f"Unknown character found during lexing: '{c}' at {i}")
                     break
-    
+
     return tokens

+ 130 - 76
src/zelkelc/validator/scope.py

@@ -4,6 +4,7 @@ import src.zelkelc.ast.types as types
 from dataclasses import dataclass
 from typing import Optional
 
+
 @dataclass
 class SemanticError:
     message: str
@@ -15,6 +16,7 @@ class StructSignature:
     irname: str
     members: dict[str, MemberSignature]
 
+
 @dataclass
 class ClassSignature:
     name: str
@@ -22,21 +24,24 @@ class ClassSignature:
     members: dict[str, MemberSignature]
     methods: dict[str, list[FunctionSignature]]
 
-@dataclass 
+
+@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
@@ -45,6 +50,7 @@ class MemberSignature:
     typ: type[types.Integer64] | type[types.String] | type[types.Void]
     member_idx: int
 
+
 @dataclass
 class Scope:
     parent: Optional[Scope]
@@ -80,7 +86,9 @@ class Scope:
             current = current.parent
         return None
 
-    def resolve_function(self, scope_checker: ScopeChecker, name: str, args: list[types.Expression]) -> Optional[FunctionSignature]:
+    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)
@@ -93,7 +101,9 @@ class Scope:
             current = current.parent
         return None
 
-    def resolve_function_by_types(self, name: str, arg_types: list[types.Value]) -> Optional[FunctionSignature]:
+    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)
@@ -104,7 +114,9 @@ class Scope:
             current = current.parent
         return None
 
-    def expr_args_to_arg_types(self, scope_checker: ScopeChecker, args: list[types.Expression]) -> Optional[list[types.Value]]:
+    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)
@@ -120,6 +132,7 @@ class Scope:
             self.functions[name] = []
         return self.functions[name]
 
+
 class ScopeChecker:
     global_scope: Scope
     errors: list[SemanticError]
@@ -158,14 +171,16 @@ class ScopeChecker:
                     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}'")
+                            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,
+                            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(
@@ -174,7 +189,9 @@ class ScopeChecker:
                         members=member_table,
                     )
 
-                case types.ClassDeclaration(name=name, irname=irname, members=members, methods=methods):
+                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
@@ -182,29 +199,32 @@ class ScopeChecker:
                     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}'")
+                            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,
+                            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,
+                            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())
+                            list(existing.args.values())
+                            == list(signature.args.values())
                             for existing in overloads
                         )
                         if has_duplicate_signature:
@@ -215,35 +235,45 @@ class ScopeChecker:
                             continue
 
                         overloads.append(signature)
-                        
+
                     self.global_scope.classes[name] = ClassSignature(
-                        name = name,
-                        irname = irname,
-                        members = member_table,
-                        methods = method_table,
+                        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 not existing is None:
-                        self._error(f"Function '{name}' is already defined with following signature '{existing}'")
+                    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,
-                    ))
-                    
+                    self.global_scope._ensure_function_overloads(name).append(
+                        FunctionSignature(
+                            name=name,
+                            irname=irname,
+                            static=True,
+                            typ=typ,
+                            args=args,
+                        )
+                    )
+
                 case types.MacroCall():
                     continue
 
                 case _:
-                    self._error(f"Unsupported top-level node while collecting symbols: {type(top_level)}")
+                    self._error(
+                        f"Unsupported top-level node while collecting symbols: {type(top_level)}"
+                    )
 
-    def _check_function(self, fn: types.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():
@@ -251,10 +281,10 @@ class ScopeChecker:
                 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,
+                name=arg_name,
+                irname=arg_name,
+                mutable=False,
+                typ=arg_type,
             )
 
         has_return = False
@@ -263,36 +293,48 @@ class ScopeChecker:
             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}'")
+                        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):
+                    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,
+                        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)}")
+                            self._error(
+                                f"Function '{fn.name}' must return {self._type_name(fn.typ)}"
+                            )
                         continue
 
-                    return_type = self._infer_expression_type(child.expr, function_scope)
+                    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")
+                        self._error(
+                            f"Function '{fn.name}' returns a value, but return type is void"
+                        )
                         continue
 
                     if not self._same_type(return_type, fn.typ):
@@ -302,34 +344,44 @@ class ScopeChecker:
                         )
 
                 case types.FunctionDeclaration():
-                    existing_nested = function_scope.resolve_function_by_types(child.name, list(child.args.values()))
+                    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}'")
+                        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,
-                        ))
+                        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.MacroCall():
                     continue
 
                 case _:
-                    self._error(f"Unsupported node in function '{fn.name}': {type(child)}")
+                    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]]:
+    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
+                return types.Float64
             case types.PrimaryExpression(types.String()):
                 return types.String
             case types.PrimaryExpression(types.VariableRef(name)):
@@ -337,21 +389,23 @@ class ScopeChecker:
                 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(f"Could not get argument types from function call")
+                    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")
+                    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):
@@ -375,7 +429,7 @@ class ScopeChecker:
                         f"{self._type_name(left_type)} vs {self._type_name(right_type)}"
                     )
                     return None
-                
+
                 match left_type:
                     case types.Integer64:
                         if op not in ["+", "-", "*"]:
@@ -413,4 +467,4 @@ class ScopeChecker:
         return self._type_token(typ)
 
     def _error(self, message: str) -> None:
-        self.errors.append(SemanticError(message))
+        self.errors.append(SemanticError(message))

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

@@ -5,22 +5,22 @@ import src.zelkelc.validator.scope as scope
 
 
 class SemanticCheckFailed(Exception):
-	pass
+    pass
 
 
 def validate_ast(tree: list[types.Node]) -> scope.Scope:
-	checker = scope.ScopeChecker()
-	global_scope, errors = checker.check(tree)
+    checker = scope.ScopeChecker()
+    global_scope, errors = checker.check(tree)
 
-	if len(errors) > 0:
-		pretty_errors = "\n".join(f"- {err.message}" for err in errors)
-		raise SemanticCheckFailed(f"AST semantic validation failed:\n{pretty_errors}")
+    if len(errors) > 0:
+        pretty_errors = "\n".join(f"- {err.message}" for err in errors)
+        raise SemanticCheckFailed(f"AST semantic validation failed:\n{pretty_errors}")
 
-	return global_scope
+    return global_scope
 
 
 class Validator:
-	scope: scope.Scope
+    scope: scope.Scope
 
-	def __init__(self, tree: list[types.Node]):
-		self.scope = validate_ast(tree)
+    def __init__(self, tree: list[types.Node]):
+        self.scope = validate_ast(tree)