Repository: CrossGL/crosstl

Issue:
switch statement
<!-- BOT_STATE: {"assignee": "RatneshKJaiswal", "assigned_at": "2024-12-26T13:56:57.047108+00:00", "reminder_sent": false, "unassigned": false} -->

Discussion:
@annu3450: please assign #193 to me@Skv9655: please assign #193 to me@Pranav-019: I will work on this issue @abhuday-sudhir:  hi @NripeshN please assign issue #193 to me@jercy12: @NripeshN @samthakur587 please assign to me this Switch Statement #193 issue@AishaRuqshan: I would appreciate the opportunity to take on this task. Please consider assigning #193 to me, as I am confident in my ability to handle it effectively.@RatneshKJaiswal: Hello @NripeshN,

I applied for ML Engineer intern role, and Arunima advised me to contribute to the project. I had created a PR ( #226 ) for the given issue.

Hope you would check it out.@thepriyanshpal: Hi @NripeshN,

I’d like to work on this issue to implement support for the `SWITCH` statement in the DirectX backend. Could you please assign it to me?

Thank you!
@NripeshN: To assign issues to yourself please read this documentation: https://github.com/CrossGL/crosstl/blob/main/CONTRIBUTING.md#-assigning-or-creating-issues

Solution patch:
diff --git a/crosstl/backend/DirectX/DirectxAst.py b/crosstl/backend/DirectX/DirectxAst.py
index 32696638..829894f0 100644
--- a/crosstl/backend/DirectX/DirectxAst.py
+++ b/crosstl/backend/DirectX/DirectxAst.py
@@ -170,3 +170,22 @@ def __repr__(self):
 
     def __str__(self):
         return f"#include {self.path}"
+
+
+class SwitchNode(ASTNode):
+    def __init__(self, condition, cases, default_body=None):
+        self.condition = condition
+        self.cases = cases
+        self.default_body = default_body
+
+    def __repr__(self):
+        return f"SwitchNode(condition={self.condition}, cases={self.cases}, default_body={self.default_body})"
+
+
+class CaseNode(ASTNode):
+    def __init__(self, value, body):
+        self.value = value
+        self.body = body
+
+    def __repr__(self):
+        return f"CaseNode(value={self.value}, body={self.body})"
diff --git a/crosstl/backend/DirectX/DirectxCrossGLCodeGen.py b/crosstl/backend/DirectX/DirectxCrossGLCodeGen.py
index 103f98cc..b77b4489 100644
--- a/crosstl/backend/DirectX/DirectxCrossGLCodeGen.py
+++ b/crosstl/backend/DirectX/DirectxCrossGLCodeGen.py
@@ -148,6 +148,8 @@ def generate_function_body(self, body, indent=0, is_main=False):
                 code += self.generate_do_while_loop(stmt, indent, is_main)
             elif isinstance(stmt, IfNode):
                 code += self.generate_if_statement(stmt, indent, is_main)
+            elif isinstance(stmt, SwitchNode):
+                code += self.generate_switch_statement(stmt, indent)
         return code
 
     def generate_for_loop(self, node, indent, is_main):
@@ -250,3 +252,24 @@ def map_semantic(self, semantic):
             return f"@ {self.semantic_map.get(semantic, semantic)}"
         else:
             return ""
+
+    def generate_switch_statement(self, node, indent=1):
+        code = (
+            "    " * indent
+            + f"switch ({self.generate_expression(node.condition)}) {{\n"
+        )
+
+        for case in node.cases:
+            code += (
+                "    " * (indent + 1)
+                + f"case {self.generate_expression(case.value)}:\n"
+            )
+            code += self.generate_function_body(case.body, indent + 2)
+            code += "    " * (indent + 2) + "break;\n"
+
+        if node.default_body:
+            code += "    " * (indent + 1) + "default:\n"
+            code += self.generate_function_body(node.default_body, indent + 2)
+
+        code += "    " * indent + "}\n"
+        return code
diff --git a/crosstl/backend/DirectX/DirectxLexer.py b/crosstl/backend/DirectX/DirectxLexer.py
index d7e2e254..210ae549 100644
--- a/crosstl/backend/DirectX/DirectxLexer.py
+++ b/crosstl/backend/DirectX/DirectxLexer.py
@@ -61,7 +61,11 @@
     ("MINUS", r"-"),
     ("EQUALS", r"="),
     ("WHITESPACE", r"\s+"),
-    ("STRING", r"\"[^\"]*\""),  # Added for string literals
+    ("STRING", r"\"[^\"]*\""),
+    ("SWITCH", r"\bswitch\b"),
+    ("CASE", r"\bcase\b"),
+    ("DEFAULT", r"\bdefault\b"),
+    ("BREAK", r"\bbreak\b"),
 ]
 
 KEYWORDS = {
@@ -84,6 +88,10 @@
     "while": "WHILE",
     "do": "DO",
     "register": "REGISTER",
+    "switch": "SWITCH",
+    "case": "CASE",
+    "default": "DEFAULT",
+    "break": "BREAK",
 }
 
 
diff --git a/crosstl/backend/DirectX/DirectxParser.py b/crosstl/backend/DirectX/DirectxParser.py
index 011c9fcc..d6fd7ffd 100644
--- a/crosstl/backend/DirectX/DirectxParser.py
+++ b/crosstl/backend/DirectX/DirectxParser.py
@@ -15,6 +15,8 @@
     VariableNode,
     VectorConstructorNode,
     IncludeNode,
+    SwitchNode,
+    CaseNode,
 )
 from .DirectxLexer import HLSLLexer
 
@@ -211,6 +213,8 @@ def parse_statement(self):
             return self.parse_while_statement()
         elif self.current_token[0] == "DO":
             return self.parse_do_while_statement()
+        elif self.current_token[0] == "SWITCH":
+            return self.parse_switch_statement()
         else:
             return self.parse_expression_statement()
 
@@ -583,3 +587,40 @@ def parse_member_access(self, object):
             return self.parse_member_access(MemberAccessNode(object, member))
 
         return MemberAccessNode(object, member)
+
+    def parse_switch_statement(self):
+        self.eat("SWITCH")
+        self.eat("LPAREN")
+        condition = self.parse_expression()
+        self.eat("RPAREN")
+        self.eat("LBRACE")
+
+        cases = []
+        default_body = None
+
+        while self.current_token[0] != "RBRACE":
+            if self.current_token[0] == "CASE":
+                self.eat("CASE")
+                case_value = self.parse_expression()
+                self.eat("COLON")
+                case_body = []
+                while self.current_token[0] not in ["CASE", "DEFAULT", "RBRACE"]:
+                    if self.current_token[0] == "BREAK":
+                        self.eat("BREAK")
+                        self.eat("SEMICOLON")
+                        break
+                    case_body.append(self.parse_statement())
+                cases.append(CaseNode(case_value, case_body))
+            elif self.current_token[0] == "DEFAULT":
+                self.eat("DEFAULT")
+                self.eat("COLON")
+                default_body = []
+                while self.current_token[0] not in ["CASE", "RBRACE"]:
+                    if self.current_token[0] == "BREAK":
+                        self.eat("BREAK")
+                        self.eat("SEMICOLON")
+                        break
+                    default_body.append(self.parse_statement())
+
+        self.eat("RBRACE")
+        return SwitchNode(condition, cases, default_body)


Test patch:
diff --git a/tests/test_backend/test_directx/test_codegen.py b/tests/test_backend/test_directx/test_codegen.py
index 4c0ccfb4..89e2ff38 100644
--- a/tests/test_backend/test_directx/test_codegen.py
+++ b/tests/test_backend/test_directx/test_codegen.py
@@ -539,5 +539,41 @@ def test_include_codegen():
         pytest.fail("Include statement failed to parse or generate code.")
 
 
+def test_switch_case_codegen():
+    code = """
+    struct PSInput {
+        float4 in_position : TEXCOORD0;
+        int value : SV_InstanceID;
+    };
+
+    struct PSOutput {
+        float4 out_color : SV_Target;
+    };
+
+    PSOutput PSMain(PSInput input) {
+        PSOutput output;
+        switch (input.value) {
+            case 1:
+                output.out_color = float4(1.0, 0.0, 0.0, 1.0);
+                break;
+            case 2:
+                output.out_color = float4(0.0, 1.0, 0.0, 1.0);
+                break;
+            default:
+                output.out_color = float4(0.0, 0.0, 1.0, 1.0);
+                break;
+        }
+        return output;
+    }
+    """
+    try:
+        tokens = tokenize_code(code)
+        ast = parse_code(tokens)
+        generated_code = generate_code(ast)
+        print(generated_code)
+    except SyntaxError:
+        pytest.fail("Switch-case 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 9318f12c..04761ac5 100644
--- a/tests/test_backend/test_directx/test_lexer.py
+++ b/tests/test_backend/test_directx/test_lexer.py
@@ -204,5 +204,29 @@ def test_logical_and_tokenization():
         pytest.fail("logical_and tokenization is not implemented.")
 
 
+def test_switch_case_tokenization():
+    code = """
+    PSOutput PSMain(PSInput input) {
+        PSOutput output;
+        switch (input.value) {
+            case 1:
+                output.out_color = float4(1.0, 0.0, 0.0, 1.0);
+                break;
+            case 2:
+                output.out_color = float4(0.0, 1.0, 0.0, 1.0);
+                break;
+            default:
+                output.out_color = float4(0.0, 0.0, 1.0, 1.0);
+                break;
+        }
+        return output;
+    }
+    """
+    try:
+        tokenize_code(code)
+    except SyntaxError:
+        pytest.fail("switch-case tokenization 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 b85b03d5..15f84153 100644
--- a/tests/test_backend/test_directx/test_parser.py
+++ b/tests/test_backend/test_directx/test_parser.py
@@ -307,5 +307,30 @@ def test_logical_and_ops_parsing():
         pytest.fail("logical_and_ops not implemented.")
 
 
+def test_switch_case_parsing():
+    code = """
+    PSOutput PSMain(PSInput input) {
+        PSOutput output;
+        switch (input.value) {
+            case 1:
+                output.out_color = float4(1.0, 0.0, 0.0, 1.0);
+                break;
+            case 2:
+                output.out_color = float4(0.0, 1.0, 0.0, 1.0);
+                break;
+            default:
+                output.out_color = float4(0.0, 0.0, 1.0, 1.0);
+                break;
+        }
+        return output;
+    }
+    """
+    try:
+        tokens = tokenize_code(code)
+        parse_code(tokens)
+    except SyntaxError:
+        pytest.fail("switch-case parsing not implemented.")
+
+
 if __name__ == "__main__":
     pytest.main()