Selaa lähdekoodia

assignment operators

Johan Rong 1 päivä sitten
vanhempi
sitoutus
6bd2c05d2b

+ 1 - 3
examples/testing/src/main.zk

@@ -16,9 +16,7 @@ fn add(a: i64, b: i64) -> i64 {
 
 fn main() -> void {
     var test: i64 = add(5, 5)
-    test = test + 3
-
-
+    test += 5 + 2 * 4 + (2+3 * 4+1)
 
     #print_i64(test)
     #print_i64(test + 1)

+ 1 - 1
src/zelkelbt/build.py

@@ -52,7 +52,7 @@ def build_package(build_mode: str, config: dict[str, any], debug_info: bool):
     ast = Expander(ids).run(ast)
     if debug_info:
         rich.print(ast)
-    
+
     Validator(ast)
 
     codegen = Codegen(ast)

+ 49 - 22
src/zelkelc/ast/parser.py

@@ -4,7 +4,7 @@ from src.zelkelc.ast import types
 from src.zelkelc.lexer import token
 
 
-class ParseError(Exception):
+class SyntaxError(Exception):
     pass
 
 
@@ -20,12 +20,12 @@ class Parser:
 
     def expect(self, tok: token.Token):
         if self.tokens[self.cursor] != tok:
-            raise ParseError(f"Expected {tok}, but found {self.tokens[self.cursor]}")
+            raise SyntaxError(f"Expected {tok}, but found {self.tokens[self.cursor]}")
         self.cursor += 1
 
     def expect_type(self, tok_type: token.Token) -> token.Token:
         if type(self.tokens[self.cursor]) is not tok_type:
-            raise ParseError(
+            raise SyntaxError(
                 f"Expected {tok_type}, but found {type(self.tokens[self.cursor])}"
             )
         self.cursor += 1
@@ -52,7 +52,7 @@ class Parser:
             case types.Void if is_return_type:
                 return "void"
             case _:
-                raise ParseError(f"Invalid type found in type_to_str: {typ}")
+                raise SyntaxError(f"Invalid type found in type_to_str: {typ}")
 
     def parse_type(self, is_return_type: bool) -> types.Value:
         identifier = self.expect_type(token.Identifier)
@@ -66,7 +66,7 @@ class Parser:
             case "void" if is_return_type:
                 return types.Void
             case _:
-                raise ParseError(
+                raise SyntaxError(
                     f"Invalid type found in parse_type: {identifier.value}"
                 )
 
@@ -83,11 +83,11 @@ class Parser:
                     case "class":
                         return self.parse_class_declaration(path)
                     case _:
-                        raise ParseError(f"Invalid keyword in global {t.value}")
+                        raise SyntaxError(f"Invalid keyword in global {t.value}")
             case token.Pound():
                 return self.parse_pound()
             case _:
-                raise ParseError(f"Invalid token found while parsing global {t}")
+                raise SyntaxError(f"Invalid token found while parsing global {t}")
 
     def parse_items(self) -> list[types.Node]:
         items = []
@@ -104,12 +104,12 @@ class Parser:
         elif self.current(token.LBrace()):
             open_t, _close_t = token.LBrace(), token.RBrace()
         else:
-            raise ParseError("Expected '(' or '{' to start delimited token tree")
+            raise SyntaxError("Expected '(' or '{' to start delimited token tree")
         self.expect(open_t)
         depth, start = 1, self.cursor
         while depth:
             if self.cursor >= len(self.tokens):
-                raise ParseError("unclosed delimited group")
+                raise SyntaxError("unclosed delimited group")
             t = self.tokens[self.cursor]
             if isinstance(t, (token.LParen, token.LBrace)):
                 depth += 1
@@ -138,11 +138,10 @@ class Parser:
             self.expect(token.Colon())
             kind = self.expect_type(token.Identifier).value
             if kind not in ("expr", "ident", "type", "tt"):
-                raise ParseError(f"Unknown macro fragment kind: {kind}")
+                raise SyntaxError(f"Unknown macro fragment kind: {kind}")
             params.append((pname, kind))
         self.expect(token.RParen())
 
-        # Body is a { ... } token tree, stored unparsed
         body = self.take_delimited()
         return types.MacroDefinition(name=name, params=params, body=body)
 
@@ -175,11 +174,11 @@ class Parser:
                                 self.parse_member_declaration(True, len(members))
                             )
                         case _:
-                            raise ParseError(
+                            raise SyntaxError(
                                 f"Invalid keyword in struct body {t.value}"
                             )
                 case _:
-                    raise ParseError(
+                    raise SyntaxError(
                         f"Invalid token found while parsing class declaration: {t}"
                     )
 
@@ -232,9 +231,11 @@ class Parser:
                                 self.parse_member_declaration(True, len(members))
                             )
                         case _:
-                            raise ParseError(f"Invalid keyword in class body {t.value}")
+                            raise SyntaxError(
+                                f"Invalid keyword in class body {t.value}"
+                            )
                 case _:
-                    raise ParseError(
+                    raise SyntaxError(
                         f"Invalid token found while parsing class declaration: {t}"
                     )
 
@@ -260,12 +261,12 @@ class Parser:
                         if self.tokens[self.cursor + 1]:
                             return self.parse_value_assignment()
 
-                        raise ParseError(f"Invalid keyword in function body {t.value}")
+                        raise SyntaxError(f"Invalid keyword in function body {t.value}")
             case token.Pound():
                 return self.parse_pound()
             case _:
                 # TODO: expression statement
-                raise ParseError(f"implement expression statement: {t}")
+                raise SyntaxError(f"implement expression statement: {t}")
 
     def parse_statements(self, path: str, stop_at_rbrace: bool) -> list[types.Node]:
         body = []
@@ -328,7 +329,7 @@ class Parser:
         self.expect(token.Colon())
         typ = self.parse_type(False)
 
-        self.expect(token.Equals())
+        self.expect(token.Assigner("="))
 
         expr = self.parse_expression()
 
@@ -339,12 +340,38 @@ class Parser:
     def parse_value_assignment(self) -> types.ValueAssignment:
         name = self.expect_type(token.Identifier).value
 
-        self.expect(token.Equals())
-
-        expr = self.parse_expression()
+        assigner = self.tokens[self.cursor].value
+        match assigner:
+            case "=":
+                self.expect(token.Assigner("="))
+            case "+=":
+                self.expect(token.Assigner("+="))
+            case "-=":
+                self.expect(token.Assigner("-="))
+            case "*=":
+                self.expect(token.Assigner("*="))
+            case "/=":
+                self.expect(token.Assigner("/="))
+            case _:
+                raise SyntaxError(f"Unknown assignment operator '{assigner}' found while parsing")
 
+        if assigner == "=":
+            expr = self.parse_expression()
+        else:
+            right = self.parse_expression()
+            expr = types.BinaryExpression(
+                types.PrimaryExpression(
+                    types.VariableRef(name, True, None, None),
+                    None
+                ),
+                right,
+                assigner[0],
+                right.typ,
+            )
+            
         return types.ValueAssignment(
             name,
+            assigner,
             None,
             expr,
             None,
@@ -377,7 +404,7 @@ class Parser:
             op = self.expect_type(token.Operator).value
 
             if op not in ("-", "+"):
-                raise ParseError(f"Invalid unary operator: {op}")
+                raise SyntaxError(f"Invalid unary operator: {op}")
             operand = self.parse_unary_expression()
             return types.UnaryExpression(operand, op, operand.typ)
 

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

@@ -46,13 +46,16 @@ class FunctionDeclaration:
     body: list[Node]
     irname: str
 
+
 @dataclass
 class ValueAssignment:
     name: str
+    assigner: str
     typ: Value
     expr: Expression
     irname: str
 
+
 @dataclass
 class ReturnStatement:
     expr: Expression | None

+ 45 - 32
src/zelkelc/codegen/codegen.py

@@ -1,9 +1,11 @@
 import struct
-import sys
 
 from src.zelkelc.ast import types
 from src.zelkelc.expander.expander import render_llvm
-from src.zelkelc.ast.parser import Parser
+
+
+class GeneratorError(Exception):
+    pass
 
 
 class IRTable:
@@ -41,10 +43,13 @@ class Codegen:
                 case types.FunctionDeclaration():
                     self.ir.global_functions += self.gen_function_declaration(node)
                 case types.InlineIR():
-                    self.ir.top = self.gen_inline_ir(node, is_top=True) + "\n" + self.ir.top
+                    self.ir.top = (
+                        self.gen_inline_ir(node, is_top=True) + "\n" + self.ir.top
+                    )
                 case _:
-                    print(f"Invalid node found during codegen {type(node)}")
-                    sys.exit(1)
+                    raise GeneratorError(
+                        f"Invalid node found during codegen {type(node)}"
+                    )
 
         self.gen_main_function()
 
@@ -66,7 +71,7 @@ class Codegen:
         self.ir.bottom += "call void @zkmain()\n"
         self.ir.bottom += "ret i32 0\n"
         self.ir.bottom += "}\n"
-        
+
     def gen_inline_ir(self, node: types.InlineIR, is_top: bool) -> str:
         vals: list[str] = []
         tys: list[str] = []
@@ -120,10 +125,9 @@ class Codegen:
                 case types.ReturnStatement():
                     ir += self.gen_return_statement(child_node)
                 case _:
-                    print(
+                    raise GeneratorError(
                         f"gen_function_declaration() {type(child_node)} is unimplemented"
                     )
-                    sys.exit(1)
 
         return ir + "\n}\n"
 
@@ -147,26 +151,28 @@ class Codegen:
                 else:
                     ir += f"\n%{node.irname} = fadd double {operand}, 0.0"
             case _:
-                print(f"gen_value_declaration() for {node.typ} is unimplemented")
-                sys.exit(1)
+                raise GeneratorError(
+                    f"gen_value_declaration() for {node.typ} is unimplemented"
+                )
 
         return ir
-    
+
     def gen_value_assignment(self, node: types.ValueAssignment) -> str:
         ir = ""
-        
+
         operand, expr = self.gen_expression(node.expr)
         ir += expr
-        
+
         match node.typ:
             case types.Integer64:
                 ir += f"\nstore i64 {operand}, i64* %{node.irname}, align 8"
             case types.Float64:
                 ir += f"\nstore double {operand}, double* %{node.irnmae}, align 8"
             case _:
-                print(f"gen_value_assignment() for {node.typ} is unimplemented")
-                sys.exit(1)
-        
+                raise GeneratorError(
+                    f"gen_value_assignment() for {node.typ} is unimplemented"
+                )
+
         return ir
 
     def gen_return_statement(self, node: types.ReturnStatement) -> str:
@@ -190,8 +196,7 @@ class Codegen:
             case "*":
                 return "mul"
             case _:
-                print(f"Cannot do operation '{sign}' on i64")
-                sys.exit(1)
+                raise GeneratorError(f"Cannot do operation '{sign}' on i64")
 
     def make_f64_op(self, sign: str) -> str:
         match sign:
@@ -206,8 +211,7 @@ class Codegen:
             case "%":
                 return "frem"
             case _:
-                print(f"Cannot do operation '{sign}' on f64")
-                sys.exit(1)
+                raise GeneratorError(f"Cannot do operation '{sign}' on f64")
 
     def get_function_args_llvm(self, node: types.FunctionCall) -> tuple[str, str]:
         ir = ""
@@ -229,8 +233,9 @@ class Codegen:
             return "double"
         if typ is types.Void or isinstance(typ, types.Void):
             return "void"
-        print(f"Cannot convert type {typ} to llvm ir (was the AST validated?)")
-        sys.exit(1)
+        raise GeneratorError(
+            f"Cannot convert type {typ} to llvm ir (was the AST validated?)"
+        )
 
     @staticmethod
     def llvm_double(value: float) -> str:
@@ -254,8 +259,9 @@ class Codegen:
                 elif node.typ is types.Float64:
                     inst = self.make_f64_op(node.op)
                 else:
-                    print(f"Binary '{node.op}' on unsupported type {node.typ}")
-                    sys.exit(1)
+                    raise GeneratorError(
+                        f"Binary '{node.op}' on unsupported type {node.typ}"
+                    )
 
                 tmp = self.new_temp()
                 ir += f"\n{tmp} = {inst} {self.llvm_type(node.typ)} {left}, {right}"
@@ -284,12 +290,19 @@ class Codegen:
                             tmp = self.new_temp()
                             match node.value.typ:
                                 case types.Integer64:
-                                    return tmp, f"\n{tmp} = load i64, i64* %{node.value.irname}, align 8"
+                                    return (
+                                        tmp,
+                                        f"\n{tmp} = load i64, i64* %{node.value.irname}, align 8",
+                                    )
                                 case types.Float64:
-                                    return tmp, f"\n{tmp} = load double, double* %{node.value.irname}, align 8"
+                                    return (
+                                        tmp,
+                                        f"\n{tmp} = load double, double* %{node.value.irname}, align 8",
+                                    )
                                 case _:
-                                    print(f"Type {self.value.typ} is unimplemented for mutable VariableRef")
-                                    sys.exit(1)
+                                    raise GeneratorError(
+                                        f"Type {self.value.typ} is unimplemented for mutable VariableRef"
+                                    )
                         else:
                             return f"%{node.value.irname}", ""
                     case types.FunctionCall():
@@ -302,10 +315,10 @@ class Codegen:
                         )
                         return tmp, ir
                     case _:
-                        print(
+                        raise GeneratorError(
                             f"gen_expression() for PrimaryExpression for {type(node.value)}"
                         )
-                        sys.exit(1)
             case _:
-                print(f"Invalid expression types found during codegen: {node}")
-                sys.exit(1)
+                raise GeneratorError(
+                    f"Invalid expression types found during codegen: {node}"
+                )

+ 21 - 19
src/zelkelc/expander/expander.py

@@ -3,11 +3,11 @@ import re
 from itertools import count
 
 from src.zelkelc.ast import types
-from src.zelkelc.ast.parser import ParseError, Parser
+from src.zelkelc.ast.parser import Parser, SyntaxError
 from src.zelkelc.lexer import token
 
 
-class MacroError(Exception):
+class ExpanderError(Exception):
     pass
 
 
@@ -27,7 +27,7 @@ def render_llvm(template: str, vals: list[str], tys: list[str], eid: int) -> str
             return str(eid)
         i = int(idx) if idx else next(auto)
         if i >= len(vals):
-            raise MacroError("#llvm placeholder out of range")
+            raise ExpanderError("#llvm placeholder out of range")
         if spec == "ty":
             return tys[i]
         return vals[i]
@@ -46,7 +46,7 @@ class Expander:
         for n in nodes:
             if isinstance(n, types.MacroDefinition):
                 if n.name in self.macros:
-                    raise MacroError(f"macro #{n.name} already defined")
+                    raise ExpanderError(f"macro #{n.name} already defined")
                 self.macros[n.name] = n
             else:
                 remaining.append(n)
@@ -54,7 +54,7 @@ class Expander:
 
     def expand_nodes(self, nodes: list[types.Node], depth: int) -> list[types.Node]:
         if depth > 64:
-            raise MacroError("macro recursion limit")
+            raise ExpanderError("macro recursion limit")
         out: list[types.Node] = []
         for n in nodes:
             if isinstance(n, types.MacroCall):
@@ -78,7 +78,7 @@ class Expander:
 
         macro = self.macros.get(call.name)
         if macro is None:
-            raise MacroError(f"unknown macro #{call.name}")
+            raise ExpanderError(f"unknown macro #{call.name}")
 
         bindings = self.match(macro, call.args)
         transcribed = self.transcribe(macro.body, bindings)
@@ -86,15 +86,15 @@ class Expander:
         sub = Parser(transcribed, 0, self.ids)
         try:
             return sub.parse_statements("_macro_", stop_at_rbrace=False)
-        except ParseError as e:
-            raise MacroError(f"in expansion of #{call.name}: {e}") from e
+        except SyntaxError as e:
+            raise ExpanderError(f"in expansion of #{call.name}: {e}") from e
 
     def expand_llvm(self, call: types.MacroCall) -> types.InlineIR:
         tokens = call.args
         if not tokens:
-            raise MacroError("#llvm requires at least a template string")
+            raise ExpanderError("#llvm requires at least a template string")
         if not isinstance(tokens[0], token.String):
-            raise MacroError("#llvm first argument must be a string template")
+            raise ExpanderError("#llvm first argument must be a string template")
 
         template = tokens[0].value
         arg_exprs: list[types.Expression] = []
@@ -106,10 +106,10 @@ class Expander:
             sub = Parser(tokens, i, self.ids)
             try:
                 expr = sub.parse_expression()
-            except ParseError as e:
-                raise MacroError(f"#llvm argument parse error: {e}") from e
+            except SyntaxError as e:
+                raise ExpanderError(f"#llvm argument parse error: {e}") from e
             if expr is None:
-                raise MacroError("#llvm expected expression argument")
+                raise ExpanderError("#llvm expected expression argument")
             arg_exprs.append(expr)
             i = sub.cursor
 
@@ -125,7 +125,9 @@ class Expander:
         for name, kind in macro.params:
             if i > 0:
                 if i >= len(tokens) or not isinstance(tokens[i], token.Comma):
-                    raise MacroError(f"expected ',' between arguments to #{macro.name}")
+                    raise ExpanderError(
+                        f"expected ',' between arguments to #{macro.name}"
+                    )
                 i += 1
 
             sub = Parser(tokens, i, self.ids)
@@ -134,7 +136,7 @@ class Expander:
                     case "expr":
                         expr = sub.parse_expression()
                         if expr is None:
-                            raise ParseError("expected expression")
+                            raise SyntaxError("expected expression")
                         bindings[name] = [token.Interpolated(expr)]
 
                     case "ident":
@@ -166,15 +168,15 @@ class Expander:
                         bindings[name] = tokens[start : sub.cursor]
 
                     case _:
-                        raise MacroError(f"unknown fragment kind {kind}")
-            except ParseError as e:
-                raise MacroError(
+                        raise ExpanderError(f"unknown fragment kind {kind}")
+            except SyntaxError as e:
+                raise ExpanderError(
                     f"failed to match '{name}:{kind}' of #{macro.name}: {e}"
                 ) from e
             i = sub.cursor
 
         if i != len(tokens):
-            raise MacroError(f"unexpected tokens in call to #{macro.name}")
+            raise ExpanderError(f"unexpected tokens in call to #{macro.name}")
         return bindings
 
     def transcribe(

+ 4 - 4
src/zelkelc/lexer/token.py

@@ -78,12 +78,12 @@ class Colon:
 
 
 @dataclass
-class Equals:
-    pass
+class Operator:
+    value: str
 
 
 @dataclass
-class Operator:
+class Assigner:
     value: str
 
 
@@ -100,8 +100,8 @@ Token = (
     | Comma
     | Arrow
     | Colon
-    | Equals
     | Operator
+    | Assigner
     | Dollar
     | Interpolated
 )

+ 23 - 8
src/zelkelc/lexer/tokenizer.py

@@ -1,6 +1,10 @@
 from src.zelkelc.lexer import token
 
 
+class TokenError(Exception):
+    pass
+
+
 def lex(source: str) -> list[token.Token]:
     tokens: list[token.Token] = []
 
@@ -52,9 +56,8 @@ def lex(source: str) -> list[token.Token]:
                     break
                 i += 1
                 string += string_c
-            if i > len(source) or source[i] != '"':
-                print("No '\"' to close string")
-                break
+            if i >= len(source) or source[i] != '"':
+                raise TokenError("No '\"' to close string")
             i += 1
             tokens.append(token.String(string))
         elif c == "#" and i + 1 < len(source) and source[i + 1] == '"':
@@ -69,11 +72,22 @@ def lex(source: str) -> list[token.Token]:
                 string += source[i]
                 i += 1
             else:
-                print("No '\"#' to close string")
-                break
+                raise TokenError("No '\"#' to close string")
         elif c == "-" and source[i + 1] == ">":
             tokens.append(token.Arrow())
             i += 2
+        elif c == "+" and source[i + 1] == "=":
+            tokens.append(token.Assigner("+="))
+            i += 2
+        elif c == "-" and source[i + 1] == "=":
+            tokens.append(token.Assigner("-="))
+            i += 2
+        elif c == "*" and source[i + 1] == "=":
+            tokens.append(token.Assigner("*="))
+            i += 2
+        elif c == "/" and source[i + 1] == "=":
+            tokens.append(token.Assigner("/="))
+            i += 2
         elif c == "<" and source[i + 1] == "=":
             tokens.append(token.Operator("<="))
             i += 2
@@ -121,7 +135,7 @@ def lex(source: str) -> list[token.Token]:
                     tokens.append(token.Colon())
                     i += 1
                 case "=":
-                    tokens.append(token.Equals())
+                    tokens.append(token.Assigner("="))
                     i += 1
                 case "+":
                     tokens.append(token.Operator("+"))
@@ -142,7 +156,8 @@ def lex(source: str) -> list[token.Token]:
                     tokens.append(token.Dollar())
                     i += 1
                 case _:
-                    print(f"Unknown character found during lexing: '{c}' at {i}")
-                    break
+                    raise TokenError(
+                        f"Unknown character found during lexing: '{c}' at {i}"
+                    )
 
     return tokens

+ 2 - 2
src/zelkelc/validator/scope.py

@@ -337,7 +337,7 @@ class ScopeChecker:
                     if not function_scope.values[child.name].mutable:
                         self._error(f"Variable '{child.name}' is immutable")
                         continue
-                    
+
                     expression_type = self._infer_expression_type(
                         child.expr, function_scope
                     )
@@ -483,7 +483,7 @@ class ScopeChecker:
 
                 match left_type:
                     case types.Integer64:
-                        if op not in ["+", "-", "*", "/", "%"]:
+                        if op not in ["+", "-", "*"]:
                             self._error(
                                 f"Operator '{op}' does not support {self._type_name(left_type)}"
                             )