Repository: CrossGL/crosstl (python)

Issue:
Add translator support for `Bitwise AND` token

Pull request description:
enh(translator): Add support for bitwise AND
### PR Description
<!-- Provide a brief summary of the changes you have made. Explain the purpose and motivation behind these changes. -->

This PR aims to add support for Bitwise AND in the translator frontend.

### Related Issue

Fixes #106 
<!-- Link to the related issue(s) that this PR addresses. Example: #123 -->

### 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 {
    struct VSInput {
        vec2 texCoord @ TEXCOORD0;
    };

    struct VSOutput {
        vec4 color @ COLOR;
    };

    sampler2D iChannel0;

    vertex {
        VSOutput main(VSInput input) {
            VSOutput output;
            // Use bitwise AND on texture coordinates (for testing purposes)
            output.color = vec4(float(int(input.texCoord.x * 100.0) & 15), 
                                float(int(input.texCoord.y * 100.0) & 15), 
                                0.0, 1.0);
            return output;
        }
    }

    fragment {
        vec4 main(VSOutput input) @ gl_FragColor {
            // Simple fragment shader to display the result of the AND operation
            return vec4(input.color.rgb, 1.0);
        }
    }
}
```


### Checklist

- [X] Have you added the necessary tests?
- [ ] Only modified the files mentioned in the related issue(s)?
- [X] Are all tests passing?




Patch:
diff --git a/crosstl/src/backend/DirectX/DirectxAst.py b/crosstl/src/backend/DirectX/DirectxAst.py
index b314f05..2484e0a 100644
--- a/crosstl/src/backend/DirectX/DirectxAst.py
+++ b/crosstl/src/backend/DirectX/DirectxAst.py
@@ -86,6 +86,15 @@ class ForNode(ASTNode):
         return f"ForNode(init={self.init}, condition={self.condition}, update={self.update}, body={self.body})"
 
 
+class WhileNode(ASTNode):
+    def __init__(self, condition, body):
+        self.condition = condition
+        self.body = body
+
+    def __repr__(self):
+        return f"WhileNode(condition={self.condition}, body={self.body})"
+
+
 class ReturnNode(ASTNode):
     def __init__(self, value):
         self.value = value
diff --git a/crosstl/src/backend/DirectX/DirectxCrossGLCodeGen.py b/crosstl/src/backend/DirectX/DirectxCrossGLCodeGen.py
index 92f02b5..57f34de 100644
--- a/crosstl/src/backend/DirectX/DirectxCrossGLCodeGen.py
+++ b/crosstl/src/backend/DirectX/DirectxCrossGLCodeGen.py
@@ -140,6 +140,8 @@ class HLSLToCrossGLConverter:
                     code += f"return {self.generate_expression(stmt.value, is_main)};\n"
             elif isinstance(stmt, ForNode):
                 code += self.generate_for_loop(stmt, indent, is_main)
+            elif isinstance(stmt, WhileNode):
+                code += self.generate_while_loop(stmt, indent, is_main)
             elif isinstance(stmt, IfNode):
                 code += self.generate_if_statement(stmt, indent, is_main)
         return code
@@ -154,6 +156,14 @@ class HLSLToCrossGLConverter:
         code += "    " * indent + "}\n"
         return code
 
+    def generate_while_loop(self, node, indent, is_main):
+        condition = self.generate_expression(node.condition, is_main)
+
+        code = f"while ({condition}) {{\n"
+        code += self.generate_function_body(node.body, indent + 1, is_main)
+        code += "    " * indent + "}\n"
+        return code
+
     def generate_if_statement(self, node, indent, is_main):
         condition = self.generate_expression(node.condition, is_main)
 
diff --git a/crosstl/src/backend/DirectX/DirectxLexer.py b/crosstl/src/backend/DirectX/DirectxLexer.py
index c42565b..c5a1574 100644
--- a/crosstl/src/backend/DirectX/DirectxLexer.py
+++ b/crosstl/src/backend/DirectX/DirectxLexer.py
@@ -19,6 +19,7 @@ TOKENS = [
     ("ELSE_IF", r"\belse\sif\b"),
     ("ELSE", r"\belse\b"),
     ("FOR", r"\bfor\b"),
+    ("WHILE", r"\b\while\b"),
     ("REGISTER", r"\bregister\b"),
     ("IDENTIFIER", r"[a-zA-Z_][a-zA-Z0-9_]*"),
     ("NUMBER", r"\d+(\.\d+)?"),
@@ -72,6 +73,7 @@ KEYWORDS = {
     "if": "IF",
     "else": "ELSE",
     "for": "FOR",
+    "while": "WHILE",
     "register": "REGISTER",
 }
 
diff --git a/crosstl/src/backend/DirectX/DirectxParser.py b/crosstl/src/backend/DirectX/DirectxParser.py
index 61fd544..3dd6014 100644
--- a/crosstl/src/backend/DirectX/DirectxParser.py
+++ b/crosstl/src/backend/DirectX/DirectxParser.py
@@ -2,6 +2,7 @@ from .DirectxAst import (
     AssignmentNode,
     BinaryOpNode,
     ForNode,
+    WhileNode,
     FunctionCallNode,
     FunctionNode,
     IfNode,
@@ -195,6 +196,8 @@ class HLSLParser:
             return self.parse_for_statement()
         elif self.current_token[0] == "RETURN":
             return self.parse_return_statement()
+        elif self.current_token[0] == "WHILE":
+            return self.parse_while_statement()
         else:
             return self.parse_expression_statement()
 
@@ -340,6 +343,19 @@ class HLSLParser:
 
         return ForNode(init, condition, update, body)
 
+    def parse_while_statement(self):
+        self.eat("WHILE")
+        self.eat("LPAREN")
+
+        # Parse condition
+        condition = self.parse_expression()
+        self.eat("RPAREN")
+
+        # Parse body
+        body = self.parse_block()
+
+        return WhileNode(condition, body)
+
     def parse_return_statement(self):
         self.eat("RETURN")
         value = self.parse_expression()
diff --git a/crosstl/src/translator/codegen/directx_codegen.py b/crosstl/src/translator/codegen/directx_codegen.py
index c013e40..e9de6f9 100644
--- a/crosstl/src/translator/codegen/directx_codegen.py
+++ b/crosstl/src/translator/codegen/directx_codegen.py
@@ -258,6 +258,7 @@ class HLSLCodeGen:
             "MULTIPLY": "*",
             "DIVIDE": "/",
             "BITWISE_XOR": "^",
+            "BITWISE_AND": "&",
             "LESS_THAN": "<",
             "GREATER_THAN": ">",
             "ASSIGN_ADD": "+=",
diff --git a/crosstl/src/translator/codegen/metal_codegen.py b/crosstl/src/translator/codegen/metal_codegen.py
index 50cc592..11b5949 100644
--- a/crosstl/src/translator/codegen/metal_codegen.py
+++ b/crosstl/src/translator/codegen/metal_codegen.py
@@ -343,6 +343,7 @@ class MetalCodeGen:
             "MULTIPLY": "*",
             "DIVIDE": "/",
             "BITWISE_XOR": "^",
+            "BITWISE_AND": "&",
             "LESS_THAN": "<",
             "GREATER_THAN": ">",
             "ASSIGN_ADD": "+=",
diff --git a/crosstl/src/translator/parser.py b/crosstl/src/translator/parser.py
index 4e4fb1c..e4e148e 100644
--- a/crosstl/src/translator/parser.py
+++ b/crosstl/src/translator/parser.py
@@ -439,6 +439,7 @@ class Parser:
             "BITWISE_SHIFT_RIGHT",
             "BITWISE_SHIFT_LEFT",
             "BITWISE_XOR",
+            "BITWISE_AND",
             "ASSIGN_SHIFT_LEFT",
             "ASSIGN_SHIFT_RIGHT",
         ]:
@@ -507,6 +508,7 @@ class Parser:
             "BITWISE_SHIFT_RIGHT",
             "BITWISE_SHIFT_LEFT",
             "BITWISE_XOR",
+            "BITWISE_AND",
             "ASSIGN_SHIFT_LEFT" "ASSIGN_SHIFT_RIGHT",
         ]:
             op = self.current_token[1]
@@ -538,6 +540,7 @@ class Parser:
             "BITWISE_SHIFT_RIGHT",
             "BITWISE_SHIFT_LEFT",
             "BITWISE_XOR",
+            "BITWISE_AND",
             "EQUAL",
             "ASSIGN_AND",
             "ASSIGN_OR",
@@ -599,6 +602,7 @@ class Parser:
             "BITWISE_SHIFT_RIGHT",
             "BITWISE_SHIFT_LEFT",
             "BITWISE_XOR",
+            "BITWISE_AND",
             "ASSIGN_SHIFT_RIGHT",
             "ASSIGN_SHIFT_LEFT",
         ]:
@@ -790,6 +794,7 @@ class Parser:
             "BITWISE_SHIFT_RIGHT",
             "BITWISE_SHIFT_LEFT",
             "BITWISE_XOR",
+            "BITWISE_AND",
             "ASSIGN_SHIFT_RIGHT",
             "ASSIGN_SHIFT_LEFT",
         ]:


Test patch:
diff --git a/tests/test_backend/test_directx/test_codegen.py b/tests/test_backend/test_directx/test_codegen.py
index a7ca79d..ed7112b 100644
--- a/tests/test_backend/test_directx/test_codegen.py
+++ b/tests/test_backend/test_directx/test_codegen.py
@@ -160,6 +160,56 @@ def test_for_codegen():
         pytest.fail("For loop parsing or code generation not implemented.")
 
 
+def test_while_codegen():
+    code = """
+    struct VSInput {
+    float4 position : POSITION;
+    float4 color : TEXCOORD0;
+    };
+
+    struct VSOutput {
+        float4 out_position : TEXCOORD0;
+    };
+
+    VSOutput VSMain(VSInput input) {
+        VSOutput output;
+        output.out_position = input.position;
+        int i = 0;
+        while (i < 10) {
+            output.out_position = input.color;
+            i = i + 1;  // Increment the loop variable
+        }
+        return output;
+    }
+
+    struct PSInput {
+        float4 in_position : TEXCOORD0;
+    };
+
+    struct PSOutput {
+        float4 out_color : SV_TARGET0;
+    };
+
+    PSOutput PSMain(PSInput input) {
+        PSOutput output;
+        output.out_color = input.in_position;
+        int i = 0;
+        while (i < 10) {
+            output.out_color = float4(1.0, 1.0, 1.0, 1.0);
+            i = i + 1;  // Increment the loop variable
+        }
+        return output;
+    }
+    """
+    try:
+        tokens = tokenize_code(code)
+        ast = parse_code(tokens)
+        generated_code = generate_code(ast)
+        print(generated_code)
+    except SyntaxError:
+        pytest.fail("While loop parsing or code generation not implemented.")
+
+
 def test_else_codegen():
     code = """
     struct VSInput {
diff --git a/tests/test_backend/test_directx/test_parser.py b/tests/test_backend/test_directx/test_parser.py
index eed91e9..7d3cc2d 100644
--- a/tests/test_backend/test_directx/test_parser.py
+++ b/tests/test_backend/test_directx/test_parser.py
@@ -75,6 +75,25 @@ def test_for_parsing():
         pytest.fail("for parsing not implemented.")
 
 
+def test_while_parsing():
+    code = """
+    VSOutput VSMain(VSInput input) {
+        VSOutput output;
+        int i = 0;
+        while (i < 10) {
+            output.out_position = input.position;
+            i = i + 1;
+        }
+        return output;
+    }
+    """
+    try:
+        tokens = tokenize_code(code)
+        parse_code(tokens)
+    except SyntaxError:
+        pytest.fail("while parsing not implemented")
+
+
 def test_else_parsing():
     code = """
     PSOutput PSMain(PSInput input) {
diff --git a/tests/test_translator/test_codegen/test_directx_codegen.py b/tests/test_translator/test_codegen/test_directx_codegen.py
index 686dbf4..ca816a2 100644
--- a/tests/test_translator/test_codegen/test_directx_codegen.py
+++ b/tests/test_translator/test_codegen/test_directx_codegen.py
@@ -559,5 +559,42 @@ def test_bitwise_operators():
         pytest.fail("Bitwise Shift codegen not implemented")
 
 
+def test_bitwise_and_operator():
+    code = """
+    shader main {
+    struct VSInput {
+        vec2 texCoord @ TEXCOORD0;
+    };
+    struct VSOutput {
+        vec4 color @ COLOR;
+    };
+    sampler2D iChannel0;
+    vertex {
+        VSOutput main(VSInput input) {
+            VSOutput output;
+            // Use bitwise AND on texture coordinates (for testing purposes)
+            output.color = vec4(float(int(input.texCoord.x * 100.0) & 15), 
+                                float(int(input.texCoord.y * 100.0) & 15), 
+                                0.0, 1.0);
+            return output;
+        }
+    }
+    fragment {
+        vec4 main(VSOutput input) @ gl_FragColor {
+            // Simple fragment shader to display the result of the AND operation
+            return vec4(input.color.rgb, 1.0);
+        }
+    }
+}
+    """
+    try:
+        tokens = tokenize_code(code)
+        ast = parse_code(tokens)
+        generated_code = generate_code(ast)
+        print(generated_code)
+    except SyntaxError:
+        pytest.fail("Bitwise AND codegen not implemented")
+
+
 if __name__ == "__main__":
     pytest.main()
diff --git a/tests/test_translator/test_parser.py b/tests/test_translator/test_parser.py
index 2bcd5c7..737e1d6 100644
--- a/tests/test_translator/test_parser.py
+++ b/tests/test_translator/test_parser.py
@@ -551,5 +551,40 @@ def test_xor_operator():
         pytest.fail("Bitwise XOR not working")
 
 
+def test_and_operator():
+    code = """
+    shader main {
+    struct VSInput {
+        vec2 texCoord @ TEXCOORD0;
+    };
+    struct VSOutput {
+        vec4 color @ COLOR;
+    };
+    sampler2D iChannel0;
+    vertex {
+        VSOutput main(VSInput input) {
+            VSOutput output;
+            // Use bitwise AND on texture coordinates (for testing purposes)
+            output.color = vec4(float(int(input.texCoord.x * 100.0) & 15), 
+                                float(int(input.texCoord.y * 100.0) & 15), 
+                                0.0, 1.0);
+            return output;
+        }
+    }
+    fragment {
+        vec4 main(VSOutput input) @ gl_FragColor {
+            // Simple fragment shader to display the result of the AND operation
+            return vec4(input.color.rgb, 1.0);
+        }
+    }
+}
+    """
+    try:
+        tokens = tokenize_code(code)
+        parse_code(tokens)
+    except SyntaxError:
+        pytest.fail("Bitwise AND not working")
+
+
 if __name__ == "__main__":
     pytest.main()