Repository: CrossGL/crosstl (python)

Issue:
ASSIGN_XOR : ^=

Pull request description:
feat(parser): Add support for ASSIGN_XOR in DirectX backend
### PR Description
<!-- Provide a brief summary of the changes you have made. Explain the purpose and motivation behind these changes. -->

Added support for `ASSIGN_XOR` (`^=`) in the DirectX backend.

### Related Issue
<!-- Link to the related issue(s) that this PR addresses. Example: #123 -->
Close #187 

### shader Sample
<!-- Provide a shader sample or snippet on which you have tested your changes. like

```crossgl
shader PerlinNoise {
    vertex {
        input vec3 position;
        output vec2 vUV;

        void main() {
            vUV = position.xy * 10.0;
            gl_Position = vec4(position, 1.0);
        }
    }

    // Perlin Noise Function
    float perlinNoise(vec2 p) {
        return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);
    }

    // Fragment Shader
    fragment {
        input vec2 vUV;
        output vec4 fragColor;

        void main() {
            float noise = perlinNoise(vUV);
            float height = noise * 10.0;
            vec3 color = vec3(height / 10.0, 1.0 - height / 10.0, 0.0);
            fragColor = vec4(color, 1.0);
        }
    }
}
```-->


### Checklist
- [X] Have you added the necessary tests?
- [X] Only modified the files mentioned in the related issue(s)?
- [ ] Are all tests passing?




Patch:
diff --git a/crosstl/src/backend/DirectX/DirectxLexer.py b/crosstl/src/backend/DirectX/DirectxLexer.py
index 07dbd6d..eeb43b9 100644
--- a/crosstl/src/backend/DirectX/DirectxLexer.py
+++ b/crosstl/src/backend/DirectX/DirectxLexer.py
@@ -42,6 +42,7 @@ TOKENS = [
     ("MINUS_EQUALS", r"-="),
     ("MULTIPLY_EQUALS", r"\*="),
     ("DIVIDE_EQUALS", r"/="),
+    ("ASSIGN_XOR", r"\^="),
     ("AND", r"&&"),
     ("OR", r"\|\|"),
     ("DOT", r"\."),
diff --git a/crosstl/src/backend/DirectX/DirectxParser.py b/crosstl/src/backend/DirectX/DirectxParser.py
index b1ecfa6..fef775b 100644
--- a/crosstl/src/backend/DirectX/DirectxParser.py
+++ b/crosstl/src/backend/DirectX/DirectxParser.py
@@ -207,12 +207,19 @@ class HLSLParser:
             "BOOL",
             "IDENTIFIER",
         ]:
+            # Handle variable declaration (e.g., int a = b;)
             first_token = self.current_token
-            self.eat(self.current_token[0])
+            self.eat(self.current_token[0])  # Eat type or identifier
+            name = None
 
+            # Check for member access (e.g., a.b)
             if self.current_token[0] == "IDENTIFIER":
                 name = self.current_token[1]
                 self.eat("IDENTIFIER")
+
+                if self.current_token[0] == "DOT":
+                    name = self.parse_member_access(name)
+
                 if self.current_token[0] == "SEMICOLON":
                     self.eat("SEMICOLON")
                     return VariableNode(first_token[1], name)
@@ -222,13 +229,32 @@ class HLSLParser:
                     "MINUS_EQUALS",
                     "MULTIPLY_EQUALS",
                     "DIVIDE_EQUALS",
+                    "ASSIGN_XOR",
                 ]:
+                    # Handle assignment operators (e.g., =, +=, -=, ^=, etc.)
                     op = self.current_token[1]
                     self.eat(self.current_token[0])
                     value = self.parse_expression()
                     self.eat("SEMICOLON")
                     return AssignmentNode(VariableNode(first_token[1], name), value, op)
+
+            elif self.current_token[0] in [
+                "EQUALS",
+                "PLUS_EQUALS",
+                "MINUS_EQUALS",
+                "MULTIPLY_EQUALS",
+                "DIVIDE_EQUALS",
+                "ASSIGN_XOR",
+            ]:
+                # Handle assignment operators (e.g., =, +=, -=, ^=, etc.)
+                op = self.current_token[1]
+                self.eat(self.current_token[0])
+                value = self.parse_expression()
+                self.eat("SEMICOLON")
+                return AssignmentNode(first_token[1], value, op)
+
             elif self.current_token[0] == "DOT":
+                # Handle int a.b = c; case directly
                 left = self.parse_member_access(first_token[1])
                 if self.current_token[0] in [
                     "EQUALS",
@@ -236,6 +262,7 @@ class HLSLParser:
                     "MINUS_EQUALS",
                     "MULTIPLY_EQUALS",
                     "DIVIDE_EQUALS",
+                    "ASSIGN_XOR",
                 ]:
                     op = self.current_token[1]
                     self.eat(self.current_token[0])
@@ -245,6 +272,8 @@ class HLSLParser:
                 else:
                     self.eat("SEMICOLON")
                     return left
+
+        # If it's not a type/identifier, it must be an expression
         expr = self.parse_expression()
         self.eat("SEMICOLON")
         return expr
@@ -330,6 +359,7 @@ class HLSLParser:
             "MINUS_EQUALS",
             "MULTIPLY_EQUALS",
             "DIVIDE_EQUALS",
+            "ASSIGN_XOR",
         ]:
             op = self.current_token[1]
             self.eat(self.current_token[0])
@@ -406,6 +436,14 @@ class HLSLParser:
 
     def parse_primary(self):
         if self.current_token[0] in ["IDENTIFIER", "FLOAT", "FVECTOR"]:
+            if self.current_token[0] == "IDENTIFIER":
+                name = self.current_token[1]
+                self.eat("IDENTIFIER")
+                if self.current_token[0] == "LPAREN":
+                    return self.parse_function_call(name)
+                elif self.current_token[0] == "DOT":
+                    return self.parse_member_access(name)
+                return VariableNode("", name)
             if self.current_token[0] in ["FLOAT", "FVECTOR"]:
                 type_name = self.current_token[1]
                 self.eat(self.current_token[0])
@@ -415,6 +453,10 @@ class HLSLParser:
         elif self.current_token[0] == "NUMBER":
             value = self.current_token[1]
             self.eat("NUMBER")
+            if self.current_token[0] == "IDENTIFIER":
+                name = self.current_token[1]
+                self.eat("IDENTIFIER")
+                return VariableNode(value, name)
             return value
         elif self.current_token[0] == "LPAREN":
             self.eat("LPAREN")


Test patch:
diff --git a/tests/test_backend/test_directx/test_codegen.py b/tests/test_backend/test_directx/test_codegen.py
index 882e9f4..4171483 100644
--- a/tests/test_backend/test_directx/test_codegen.py
+++ b/tests/test_backend/test_directx/test_codegen.py
@@ -50,7 +50,7 @@ def test_struct_codegen():
     };
 
     struct PSOutput {
-        float4 out_color : SV_Target0;
+        float4 out_color : SV_TARGET0;
     };
 
     PSOutput PSMain(PSInput input) {
@@ -93,7 +93,7 @@ def test_if_codegen():
     };
 
     struct PSOutput {
-        float4 out_color : SV_Target0;
+        float4 out_color : SV_TARGET0;
     };
 
     PSOutput PSMain(PSInput input) {
@@ -139,7 +139,7 @@ def test_for_codegen():
     };
 
     struct PSOutput {
-        float4 out_color : SV_Target0;
+        float4 out_color : SV_TARGET0;
     };
 
     PSOutput PSMain(PSInput input) {
@@ -188,7 +188,7 @@ def test_else_codegen():
     };
 
     struct PSOutput {
-        float4 out_color : SV_Target0;
+        float4 out_color : SV_TARGET0;
     };
 
     PSOutput PSMain(PSInput input) {
@@ -238,7 +238,7 @@ def test_function_call_codegen():
     };
 
     struct PSOutput {
-        float4 out_color : SV_Target0;
+        float4 out_color : SV_TARGET0;
     };
 
     PSOutput PSMain(PSInput input) {
@@ -285,7 +285,7 @@ def test_else_if_codegen():
     };
 
     struct PSOutput {
-        float4 out_color : SV_Target0;
+        float4 out_color : SV_TARGET0;
     };
 
     PSOutput PSMain(PSInput input) {
@@ -309,5 +309,45 @@ def test_else_if_codegen():
         pytest.fail("Else_if statement parsing or code generation not implemented.")
 
 
+def test_assignment_ops_parsing():
+    code = """
+    PSOutput PSMain(PSInput input) {
+        PSOutput output;
+        output.out_color = float4(0.0, 0.0, 0.0, 1.0);
+
+        if (input.in_position.r > 0.5) {
+            output.out_color += input.in_position;
+        }
+
+        if (input.in_position.r < 0.5) {
+            output.out_color -= float4(0.1, 0.1, 0.1, 0.1);
+        }
+
+        if (input.in_position.g > 0.5) {
+            output.out_color *= 2.0;
+        }
+
+        if (input.in_position.b > 0.5) {
+            out_color /= 2.0;
+        }
+
+        if (input.in_position.r == 0.5) {
+            uint redValue = asuint(output.out_color.r);
+            output.redValue ^= 0x1;
+            output.out_color.r = asfloat(redValue);
+        }
+
+        return output;
+    }
+    """
+    try:
+        tokens = tokenize_code(code)
+        ast = parse_code(tokens)
+        generated_code = generate_code(ast)
+        print(generated_code)
+    except SyntaxError:
+        pytest.fail("assignment ops parsing or code generation not implemented.")
+
+
 if __name__ == "__main__":
     pytest.main()
diff --git a/tests/test_backend/test_directx/test_lexer.py b/tests/test_backend/test_directx/test_lexer.py
index 62689be..4cb364d 100644
--- a/tests/test_backend/test_directx/test_lexer.py
+++ b/tests/test_backend/test_directx/test_lexer.py
@@ -111,5 +111,42 @@ def test_else_if_tokenization():
         pytest.fail("else_if tokenization not implemented.")
 
 
+def test_assignment_ops_tokenization():
+    code = """
+    PSOutput PSMain(PSInput input) {
+        PSOutput output;
+        output.out_color = float4(0.0, 0.0, 0.0, 1.0);
+
+        if (input.in_position.r > 0.5) {
+            output.out_color += input.in_position;
+        }
+
+        if (input.in_position.r < 0.5) {
+            output.out_color -= float4(0.1, 0.1, 0.1, 0.1);
+        }
+
+        if (input.in_position.g > 0.5) {
+            output.out_color *= 2.0;
+        }
+
+        if (input.in_position.b > 0.5) {
+            output.out_color /= 2.0;
+        }
+
+        if (input.in_position.r == 0.5) {
+            uint redValue = asuint(output.out_color.r);
+            redValue ^= 0x1;
+            output.out_color.r = asfloat(redValue);
+        }
+
+        return output;
+    }
+    """
+    try:
+        tokenize_code(code)
+    except SyntaxError:
+        pytest.fail("assign_op tokenization is not implemented.")
+
+
 if __name__ == "__main__":
     pytest.main()
diff --git a/tests/test_backend/test_directx/test_parser.py b/tests/test_backend/test_directx/test_parser.py
index 17515d5..15456de 100644
--- a/tests/test_backend/test_directx/test_parser.py
+++ b/tests/test_backend/test_directx/test_parser.py
@@ -130,5 +130,43 @@ def test_else_if_parsing():
         pytest.fail("else_if parsing not implemented.")
 
 
+def test_assignment_ops_parsing():
+    code = """
+    PSOutput PSMain(PSInput input) {
+        PSOutput output;
+        output.out_color = float4(0.0, 0.0, 0.0, 1.0);
+
+        if (input.in_position.r > 0.5) {
+            output.out_color += input.in_position;
+        }
+
+        if (input.in_position.r < 0.5) {
+            output.out_color -= float4(0.1, 0.1, 0.1, 0.1);
+        }
+
+        if (input.in_position.g > 0.5) {
+            output.out_color *= 2.0;
+        }
+
+        if (input.in_position.b > 0.5) {
+            out_color /= 2.0;
+        }
+
+        if (input.in_position.r == 0.5) {
+            uint redValue = asuint(output.out_color.r);
+            output.redValue ^= 0x1;
+            output.out_color.r = asfloat(redValue);
+        }
+
+        return output;
+    }
+    """
+    try:
+        tokens = tokenize_code(code)
+        parse_code(tokens)
+    except SyntaxError:
+        pytest.fail("assign_op parsing not implemented.")
+
+
 if __name__ == "__main__":
     pytest.main()