Repository: CrossGL/crosstl (python)

Issue:
ASSIGN_AND : &=

Pull request description:
enh(directx): add directx backend support for ASSIGN_AND
### PR Description
<!-- Provide a brief summary of the changes you have made. Explain the purpose and motivation behind these changes. -->

Implementation details:

- Add ASSIGN_AND token to lexer
- Implement ASSIGN_AND parsing in directx parser

Test details:

- Add parser and codegen tests for ASSIGN_AND
- Add lexer tests for ASSIGN_AND and ASSIGN_OR

### Related Issue
<!-- Link to the related issue(s) that this PR addresses. Example: #123 -->

Fixes #185 

### 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);
        }
    }
}
```-->

```crossgl
shader main {
    // Fragment Shader
    fragment {
       PSOutput PSMain(PSInput input )  {
        PSOutput output;
        output.out_color = vec4(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 -= vec4(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 ^= 0 x1;
            output.out_color.r = asfloat( redValue);
            output.redValue |= 0 x2;
            output.redValue &= 0 x3;
        }
        return  output;
    }

    }

}
```

The above CrossGL code was generated from the following Directx HLSL code

```hlsl
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);

            output.redValue |= 0x2;

            output.redValue &= 0x3;

        }

        return output;
    }
```


### Checklist
- [X] Have you added the necessary tests?
- [X] Only modified the files mentioned in the related issue(s)?
- [X] Are all tests passing?




Patch:
diff --git a/crosstl/src/backend/DirectX/DirectxLexer.py b/crosstl/src/backend/DirectX/DirectxLexer.py
index c5a1574..d6632a3 100644
--- a/crosstl/src/backend/DirectX/DirectxLexer.py
+++ b/crosstl/src/backend/DirectX/DirectxLexer.py
@@ -45,6 +45,7 @@ TOKENS = [
     ("DIVIDE_EQUALS", r"/="),
     ("ASSIGN_XOR", r"\^="),
     ("ASSIGN_OR", r"\|="),
+    ("ASSIGN_AND", r"\&="),
     ("AND", r"&&"),
     ("OR", r"\|\|"),
     ("DOT", r"\."),
diff --git a/crosstl/src/backend/DirectX/DirectxParser.py b/crosstl/src/backend/DirectX/DirectxParser.py
index 3dd6014..2764649 100644
--- a/crosstl/src/backend/DirectX/DirectxParser.py
+++ b/crosstl/src/backend/DirectX/DirectxParser.py
@@ -234,6 +234,7 @@ class HLSLParser:
                     "DIVIDE_EQUALS",
                     "ASSIGN_XOR",
                     "ASSIGN_OR",
+                    "ASSIGN_AND",
                 ]:
                     # Handle assignment operators (e.g., =, +=, -=, ^=, etc.)
                     op = self.current_token[1]
@@ -250,6 +251,7 @@ class HLSLParser:
                 "DIVIDE_EQUALS",
                 "ASSIGN_XOR",
                 "ASSIGN_OR",
+                "ASSIGN_AND",
             ]:
                 # Handle assignment operators (e.g., =, +=, -=, ^=, etc.)
                 op = self.current_token[1]
@@ -269,6 +271,7 @@ class HLSLParser:
                     "DIVIDE_EQUALS",
                     "ASSIGN_XOR",
                     "ASSIGN_OR",
+                    "ASSIGN_AND",
                 ]:
                     op = self.current_token[1]
                     self.eat(self.current_token[0])
@@ -380,6 +383,7 @@ class HLSLParser:
             "DIVIDE_EQUALS",
             "ASSIGN_XOR",
             "ASSIGN_OR",
+            "ASSIGN_AND",
         ]:
             op = self.current_token[1]
             self.eat(self.current_token[0])


Test patch:
diff --git a/tests/test_backend/test_directx/test_codegen.py b/tests/test_backend/test_directx/test_codegen.py
index ed7112b..55ed1be 100644
--- a/tests/test_backend/test_directx/test_codegen.py
+++ b/tests/test_backend/test_directx/test_codegen.py
@@ -386,7 +386,10 @@ def test_assignment_ops_parsing():
             output.redValue ^= 0x1;
             output.out_color.r = asfloat(redValue);
 
-            outpu.redValue |= 0x2;
+            output.redValue |= 0x2;
+
+            output.redValue &= 0x3;
+
         }
 
         return output;
diff --git a/tests/test_backend/test_directx/test_lexer.py b/tests/test_backend/test_directx/test_lexer.py
index 4cb364d..76ba0bc 100644
--- a/tests/test_backend/test_directx/test_lexer.py
+++ b/tests/test_backend/test_directx/test_lexer.py
@@ -137,6 +137,10 @@ def test_assignment_ops_tokenization():
             uint redValue = asuint(output.out_color.r);
             redValue ^= 0x1;
             output.out_color.r = asfloat(redValue);
+
+            redValue |= 0x2;
+
+            redValue &= 0x3;
         }
 
         return output;
diff --git a/tests/test_backend/test_directx/test_parser.py b/tests/test_backend/test_directx/test_parser.py
index 7d3cc2d..5b1b702 100644
--- a/tests/test_backend/test_directx/test_parser.py
+++ b/tests/test_backend/test_directx/test_parser.py
@@ -177,6 +177,8 @@ def test_assignment_ops_parsing():
             output.out_color.r = asfloat(redValue);
 
             output.redValue |= 0x2;
+
+            output.redValue &= 0x3;
         }
 
         return output;