Răsfoiți Sursa

floats, codegen, macros

Johan Rong 4 zile în urmă
părinte
comite
c4dd84c105

+ 22 - 0
main.ll

@@ -0,0 +1,22 @@
+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
+}

+ 13 - 11
src/zelkelc/__main__.py

@@ -6,19 +6,21 @@ from src.zelkelc.codegen.codegen import Codegen
 import rich
 
 source = """
-class String {
-    val value: i64
-    
-    fn test() -> void {
-        return
-    }
-    
-}
+#llvm(
+    #"@int.fmt = private unnamed_addr constant [5 x i8] c"%ld\0A\00""#
+)
+
+#llvm(
+    #"declare i32 @printf(ptr, ...)"#
+)
 
 fn main() -> void {
-    val a: i64 = 10
-    val b: i64 = 32
-    val test: i64 = a + b * 3
+    val test: i64 = 2 + 2*3
+    
+    #llvm(
+        "%call = call i32 (ptr, ...) @printf(ptr @int.fmt, i64 %v_27_test)"
+    )
+    
     return
 }
 """

+ 37 - 1
src/zelkelc/ast/parser.py

@@ -41,6 +41,8 @@ class Parser:
                 return types.String
             case "i64":
                 return types.Integer64
+            case "f64":
+                return types.Float64
             case "void" if is_return_type == True:
                 return types.Void
             case _:
@@ -65,12 +67,32 @@ class Parser:
                         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
+                    self.expect(token.Pound())
+                    ident = self.expect_type(token.Identifier)
+                    
+                    match ident.value:
+                        case "fn":
+                            print(f"Macro declaration is not implemented")
+                            exit(1)
+                        case _:
+                            ast.append(self.parse_macro_call(ident))
                 case _:
                     print(f"Invalid token found while parsing global {t}")
                     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"))
@@ -204,6 +226,16 @@ class Parser:
                         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
+                    self.expect(token.Pound())
+                    ident = self.expect_type(token.Identifier)
+                    
+                    match ident.value:
+                        case "fn":
+                            print(f"Macro declaration is not implemented")
+                            exit(1)
+                        case _:
+                            body.append(self.parse_macro_call(ident))
                 case _:
                     #TODO: expression statement
                     print(f"implement expression statement: {t}")
@@ -217,7 +249,7 @@ class Parser:
         
         irname = f"{path}.f_{name}_{typ}"
         if name == "main":
-            irname = "main"
+            irname = "zkmain"
         
         return types.FunctionDeclaration(
             name = name,
@@ -318,6 +350,10 @@ class Parser:
             case token.Integer():
                 self.cursor += 1
                 return types.PrimaryExpression(types.Integer64(t.value), types.Integer64)
+            
+            case token.Float():
+                self.cursor += 1
+                return types.PrimaryExpression(types.Float64(t.value), types.Float64)
 
             case token.String():
                 self.cursor += 1

+ 18 - 2
src/zelkelc/ast/types.py

@@ -42,8 +42,12 @@ class FunctionDeclaration:
 @dataclass
 class ReturnStatement:
     expr: Optional[Expression]
+    
+@dataclass
+class MacroCall:
+    body: str
 
-Node = ClassDeclaration | ValueDeclaration | FunctionDeclaration | StructDeclaration
+Node = ClassDeclaration | ValueDeclaration | FunctionDeclaration | StructDeclaration | MacroCall
 
 @dataclass
 class BinaryExpression:
@@ -76,6 +80,18 @@ class Integer64:
         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:
@@ -116,4 +132,4 @@ class Ptr[T]:
 class Val[T]:
     pass
 
-Value = Integer64 | String | Void | VariableRef | FunctionCall
+Value = Integer64 | Float64 | String | Void | VariableRef | FunctionCall

+ 70 - 22
src/zelkelc/codegen/codegen.py

@@ -1,14 +1,18 @@
 import src.zelkelc.ast.types as types
 
 class IRTable:
+    top: str
     structs: str
     struct_functions: str
     global_functions: str
+    bottom: str
     
     def __init__(self):
+        self.top = ""
         self.structs = ""
         self.struct_functions = ""
         self.global_functions = ""
+        self.bottom = ""
 
 class Codegen:
     ast: list[types.FunctionDeclaration | types.ClassDeclaration]
@@ -29,11 +33,22 @@ class Codegen:
                     self.gen_class_declaration(node)
                 case types.FunctionDeclaration():
                     self.ir.global_functions += self.gen_function_declaration(node)
+                case types.MacroCall():
+                    self.ir.top = rf"{node.body}" + "\n" + self.ir.top
                 case _:
                     print(f"Invalid node found during codegen {type(node)}")
                     exit(1)
+                    
+        self.gen_main_function()
         
-        return self.ir.structs + "\n" + self.ir.struct_functions + "\n" + self.ir.global_functions
+        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 = ""
@@ -64,10 +79,16 @@ class Codegen:
             match child_node:
                 case types.ValueDeclaration():
                     ir += self.gen_value_declaration(child_node)
-                case _:
+                case types.MacroCall():
+                    ir += "\n" + rf"{child_node.body}"
+                case types.ReturnStatement():
+                    print(f"gen_function_declaration() {type(child_node)} is unimplemented")
                     continue
+                case _:
+                    print(f"gen_function_declaration() {type(child_node)} is unimplemented")
+                    exit(1)
         
-        return ir + "\n}"
+        return ir + "\n}\n"
     
     def gen_value_declaration(self, node: types.ValueDeclaration) -> str:
         ir = ""
@@ -78,16 +99,48 @@ class Codegen:
         match node.typ:
             case types.Integer64:
                 if node.mutable == True:
-                    ir += f"\n%{node.irname} = alloca i64, align 8" # TODO: this would only work for i64s, just testing
+                    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:
+                    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 {type(node.typ)} is unimplemented")
+                print(f"gen_value_declaration() for {node.typ} is unimplemented")
                 exit(1)
             
         return ir
     
+    def make_i64_op(self, sign: str) -> str:
+        match sign:
+            case '+':
+                return "add"
+            case '-':
+                return "sub"
+            case '*':
+                return "mul"
+            case _:
+                print(f"Cannot do operation '{sign}' on i64")
+                exit(1)
+                
+    def make_f64_op(self, sign: str) -> str:
+        match sign:
+            case '+':
+                return "fadd"
+            case '-':
+                return "fsub"
+            case '*':
+                return "fmul"
+            case '/':
+                return "fdiv"
+            case _:
+                print(f"Cannot do operation '{sign}' on f64")
+                exit(1)
+    
     def gen_expression(self, node: types.Expression) -> tuple[str, str]:
         ir = ""
         
@@ -97,30 +150,21 @@ class Codegen:
                 right_irname, right = self.gen_expression(node.right)
                 
                 ir += left + right
-                
-                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)
                         
                 match node.left.typ:
                     case types.Integer64:
-                        ir += f"\n%{self.temp_counter} = {op} i64 %{left_irname}, %{right_irname}"
+                        ir += f"\n%{self.temp_counter} = {self.make_i64_op(node.op)} i64 %{left_irname}, %{right_irname}"
+                        self.temp_counter += 1
+                    case types.Float64:
+                        ir += f"\n%{self.temp_counter} = {self.make_f64_op(node.op)} f64 %{left_irname}, %{right_irname}"
                         self.temp_counter += 1
                     case types.VariableRef:
                         match node.left.value.typ:
                             case types.Integer64:
-                                ir += f"\n%{self.temp_counter} = {op} i64 %{left_irname}, %{right_irname}"
+                                ir += f"\n%{self.temp_counter} = {self.make_i64_op(node.op)} i64 %{left_irname}, %{right_irname}"
+                                self.temp_counter += 1
+                            case types.Float64:
+                                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")
@@ -130,11 +174,15 @@ class Codegen:
             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 types.Float64:
+                        ir += f"\n%{self.temp_counter} = fadd f64 {node.value.value}, 0"
+                        self.temp_counter += 1
                     case types.VariableRef:
                         return node.value.irname, ir
                     case _:

+ 9 - 1
src/zelkelc/lexer/token.py

@@ -11,6 +11,14 @@ class String:
 @dataclass
 class Integer:
     value: int
+    
+@dataclass
+class Float:
+    value: float
+
+@dataclass
+class Pound:
+    pass
 
 @dataclass
 class LParen:
@@ -52,4 +60,4 @@ class Equals:
 class Operator:
     value: str
 
-Token = Identifier | String | Integer | 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

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

@@ -15,16 +15,23 @@ def lex(source: str) -> list[token.Token]:
         elif c.isnumeric():
             i += 1
             
-            integer = c
-            for _, integer_c in enumerate(source[i:]):
-                if integer_c == '_':
+            isfloat = False
+            num = c
+            for _, num_c in enumerate(source[i:]):
+                if num_c == '_':
                     i += 1
                     continue
-                if not integer_c.isnumeric():
+                if num_c == '.':
+                    isfloat = True
+                elif not num_c.isnumeric():
                     break
                 i += 1
-                integer += integer_c
-            tokens.append(token.Integer(int(integer)))
+                num += num_c
+                
+            if isfloat:
+                tokens.append(token.Float(float(num)))
+            else:
+                tokens.append(token.Integer(int(num)))
         elif c.isalpha() or c == '_':
             i += 1
             
@@ -49,6 +56,20 @@ def lex(source: str) -> list[token.Token]:
                 break
             i += 1
             tokens.append(token.String(string))
+        elif c == "#" and i + 1 < len(source) and source[i + 1] == '"':
+            i += 2
+
+            string = ""
+            while i < len(source):
+                if source[i] == '"' and i + 1 < len(source) and source[i + 1] == '#':
+                    i += 2
+                    tokens.append(token.String(string))
+                    break
+                string += source[i]
+                i += 1
+            else:
+                print("No '\"#' to close string")
+                break
         elif c == '-' and source[i + 1] == '>':
             tokens.append(token.Arrow())
             i += 2
@@ -74,6 +95,9 @@ def lex(source: str) -> list[token.Token]:
             match c:
                 case ' ' | '\n':
                     i += 1
+                case '#':
+                    tokens.append(token.Pound())
+                    i += 1
                 case '(':
                     tokens.append(token.LParen())
                     i += 1

+ 29 - 7
src/zelkelc/validator/scope.py

@@ -140,6 +140,8 @@ class ScopeChecker:
                         self._check_function(method, self.global_scope)
                 case types.StructDeclaration():
                     continue
+                case types.MacroCall():
+                    continue
                 case _:
                     self._error(f"Unsupported top-level node: {type(top_level)}")
 
@@ -234,6 +236,9 @@ class ScopeChecker:
                         typ = typ,
                         args = args,
                     ))
+                    
+                case types.MacroCall():
+                    continue
 
                 case _:
                     self._error(f"Unsupported top-level node while collecting symbols: {type(top_level)}")
@@ -309,6 +314,9 @@ class ScopeChecker:
                             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)}")
@@ -320,6 +328,8 @@ class ScopeChecker:
         match expr:
             case types.PrimaryExpression(types.Integer64()):
                 return types.Integer64
+            case types.PrimaryExpression(types.Float64()):
+                    return types.Float64
             case types.PrimaryExpression(types.String()):
                 return types.String
             case types.PrimaryExpression(types.VariableRef(name)):
@@ -344,7 +354,7 @@ class ScopeChecker:
                 
                 expr.value.irname = signature.irname
                 return signature.typ
-            case types.UnaryExpression(expr=inner_expr, sign=sign):
+            case types.UnaryExpression(inner_expr, sign):
                 inner_type = self._infer_expression_type(inner_expr, active_scope)
                 if inner_type is None:
                     return None
@@ -365,12 +375,22 @@ class ScopeChecker:
                         f"{self._type_name(left_type)} vs {self._type_name(right_type)}"
                     )
                     return None
-                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 types.Integer64
+                
+                match left_type:
+                    case types.Integer64:
+                        if op not in ["+", "-", "*"]:
+                            self._error(
+                                f"Operator '{op}' does not support {self._type_name(left_type)}"
+                            )
+                        return types.Integer64
+                    case types.Float64:
+                        if op not in ["+", "-", "*", "/"]:
+                            self._error(
+                                f"Operator '{op}' does not support {self._type_name(left_type)}"
+                            )
+                        return types.Float64
+                    case _:
+                        return None
             case _:
                 self._error(f"Unsupported expression type: {type(expr)}")
                 return None
@@ -381,6 +401,8 @@ class ScopeChecker:
     def _type_token(self, typ: object) -> str:
         if typ is types.Integer64 or isinstance(typ, types.Integer64):
             return "i64"
+        if typ is types.Float64 or isinstance(typ, types.Float64):
+            return "f64"
         if typ is types.String or isinstance(typ, types.String):
             return "str"
         if typ is types.Void or isinstance(typ, types.Void):