Repository: CrossGL/crosstl

Issue:
Add Parsing for `Logical AND`  Token
Update the parser to handle the LOGICAL_AND token, allowing it to correctly parse expressions involving the && operator.

Discussion:

Patch:
diff --git a/crosstl/src/backend/Opengl/OpenglAst.py b/crosstl/src/backend/Opengl/OpenglAst.py
index 554c5f6..fae46f3 100644
--- a/crosstl/src/backend/Opengl/OpenglAst.py
+++ b/crosstl/src/backend/Opengl/OpenglAst.py
@@ -149,13 +149,17 @@ class AssignmentNode(ASTNode):
 
 
 class IfNode(ASTNode):
-    def __init__(self, condition, if_body, else_body=None):
+    def __init__(self, condition, if_body, else_if_chain=None, else_body=None):
         self.condition = condition
         self.if_body = if_body
+        self.else_if_chain = else_if_chain or []
         self.else_body = else_body
 
     def __repr__(self):
-        return f"IfNode(condition={self.condition}, if_body={self.if_body}, else_body={self.else_body})"
+        return (
+            f"IfNode(condition={self.condition}, if_body={self.if_body}, "
+            f"else_if_chain={self.else_if_chain}, else_body={self.else_body})"
+        )
 
 
 class ForNode(ASTNode):
diff --git a/crosstl/src/backend/Opengl/OpenglLexer.py b/crosstl/src/backend/Opengl/OpenglLexer.py
index 0da1dd3..154ce15 100644
--- a/crosstl/src/backend/Opengl/OpenglLexer.py
+++ b/crosstl/src/backend/Opengl/OpenglLexer.py
@@ -4,6 +4,7 @@ import re
 TOKENS = [
     ("COMMENT_SINGLE", r"//.*"),
     ("COMMENT_MULTI", r"/\*[\s\S]*?\*/"),
+    ("ELSE_IF", r"\belse\s+if\b"),
     ("VERSION", r"#version"),
     ("NUMBER", r"\d+(\.\d+)?"),
     ("CORE", r"\bcore\b"),
@@ -67,6 +68,7 @@ KEYWORDS = {
     "main": "MAIN",
     "vertex": "VERTEX",
     "fragment": "FRAGMENT",
+    "else if": "ELSE_IF",
     "if": "IF",
     "else": "ELSE",
     "for": "FOR",
diff --git a/crosstl/src/backend/Opengl/OpenglParser.py b/crosstl/src/backend/Opengl/OpenglParser.py
index e9408f9..e461a2c 100644
--- a/crosstl/src/backend/Opengl/OpenglParser.py
+++ b/crosstl/src/backend/Opengl/OpenglParser.py
@@ -626,6 +626,29 @@ class GLSLParser:
         self.eat("SEMICOLON")
         return ReturnNode(expr)
 
+    def parse_else_if_chain(self):
+        else_if_chain = []
+        else_body = None
+
+        while self.current_token[0] in ["ELSE_IF", "ELSE"]:
+            if self.current_token[0] == "ELSE_IF":
+                self.eat("ELSE_IF")
+                self.eat("LPAREN")
+                elif_condition = self.parse_expression()
+                self.eat("RPAREN")
+                self.eat("LBRACE")
+                elif_body = self.parse_body()
+                self.eat("RBRACE")
+                else_if_chain.append((elif_condition, elif_body))
+            else:
+                # Handle `ELSE`
+                self.eat("ELSE")
+                self.eat("LBRACE")
+                else_body = self.parse_body()
+                self.eat("RBRACE")
+                break
+        return else_if_chain, else_body
+
     def parse_if(self):
         self.eat("IF")
         self.eat("LPAREN")
@@ -635,19 +658,9 @@ class GLSLParser:
         body = self.parse_body()
         self.eat("RBRACE")
 
-        else_body = None
-        if self.current_token[0] == "ELSE":
-            self.eat("ELSE")
-            if self.current_token[0] == "IF":
-                # Handle nested if
-                else_body = self.parse_if()
-            else:
-                self.eat("LBRACE")
-                else_body = self.parse_body()
-                self.eat("RBRACE")
-            return IfNode(condition, body, else_body)
-        else:
-            return IfNode(condition, body)
+        else_if_chain, else_body = self.parse_else_if_chain()
+
+        return IfNode(condition, body, else_if_chain, else_body)
 
     def parse_for(self):
         self.eat("FOR")
diff --git a/crosstl/src/backend/Opengl/openglCrossglCodegen.py b/crosstl/src/backend/Opengl/openglCrossglCodegen.py
index b21430b..4fdac11 100644
--- a/crosstl/src/backend/Opengl/openglCrossglCodegen.py
+++ b/crosstl/src/backend/Opengl/openglCrossglCodegen.py
@@ -134,14 +134,15 @@ class GLSLToCrossGLConverter:
 
     def generate_if(self, node: IfNode, shader_type, indent=0):
         indent_str = "    " * indent
-        code = f"{indent_str}if {self.generate_expression(node.condition,shader_type)} {{\n"
+        code = f"{indent_str}if {self.generate_expression(node.condition, shader_type)} {{\n"
         for stmt in node.if_body:
             code += self.generate_statement(stmt, shader_type, indent + 1)
         code += f"{indent_str}}}"
-        while isinstance(node.else_body, IfNode):
-            node = node.else_body
-            code += f" else if ({self.generate_expression(node.condition, shader_type)}) {{\n"
-            for stmt in node.if_body:
+
+        # Handle else_if_chain
+        for elif_condition, elif_body in node.else_if_chain:
+            code += f" else if ({self.generate_expression(elif_condition, shader_type)}) {{\n"
+            for stmt in elif_body:
                 code += self.generate_statement(stmt, shader_type, indent + 1)
             code += f"{indent_str}}}"
 
diff --git a/crosstl/src/translator/lexer.py b/crosstl/src/translator/lexer.py
index f613e17..b79abe1 100644
--- a/crosstl/src/translator/lexer.py
+++ b/crosstl/src/translator/lexer.py
@@ -37,6 +37,8 @@ TOKENS = [
     ("ELSE", r"\belse\b"),
     ("FOR", r"\bfor\b"),
     ("RETURN", r"\breturn\b"),
+    ("BITWISE_SHIFT_LEFT", r"<<"),
+    ("BITWISE_SHIFT_RIGHT", r">>"),
     ("LESS_EQUAL", r"<="),
     ("GREATER_EQUAL", r">="),
     ("GREATER_THAN", r">"),
@@ -64,6 +66,11 @@ TOKENS = [
     ("EQUALS", r"="),
     ("QUESTION", r"\?"),
     ("COLON", r":"),
+    ("CONST", r"\bconst\b"),
+    ("BITWISE_AND", r"&"),
+    ("BITWISE_OR", r"\|"),
+    ("BITWISE_XOR", r"\^"),
+    ("BITWISE_NOT", r"~"),
 ]
 
 KEYWORDS = {
@@ -78,6 +85,7 @@ KEYWORDS = {
     "else": "ELSE",
     "for": "FOR",
     "return": "RETURN",
+    "const": "CONST",
 }
 
 
diff --git a/crosstl/src/translator/parser.py b/crosstl/src/translator/parser.py
index e060089..c62c6b1 100644
--- a/crosstl/src/translator/parser.py
+++ b/crosstl/src/translator/parser.py
@@ -861,8 +861,8 @@ class Parser:
             "GREATER_EQUAL",
             "EQUAL",
             "NOT_EQUAL",
-            "AND",
-            "OR",
+            "LOGICAL_AND",
+            "LOGICAL_OR",
             "PLUS",
             "MINUS",
             "MULTIPLY",


Test patch:
diff --git a/tests/test_backend/test_opengl/test_codegen.py b/tests/test_backend/test_opengl/test_codegen.py
index 7a2be74..44deced 100644
--- a/tests/test_backend/test_opengl/test_codegen.py
+++ b/tests/test_backend/test_opengl/test_codegen.py
@@ -82,21 +82,36 @@ def test_if_statement():
     code = """
     #version 450
     // Vertex shader
+    float perlinNoise(vec2 p) {
+        return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);
+    }
+
     layout(location = 0) in vec3 position;
     out vec2 vUV;
 
     void main() {
+        vUV = position.xy * 10.0;
         if (vUV.x > vUV.y) {
             vUV = vec2(0.0, 0.0);
         }
+        else {
+            vUV = vec2(1.0, 1.0);
+        }
     }
     // Fragment shader
     in vec2 vUV;
     layout(location = 0) out vec4 fragColor;
 
     void main() {
-        if (noise > 0.5) {
-            fragColor = vec4(1.0, 1.0, 1.0, 1.0);
+        float noise = perlinNoise(vUV);
+        if (noise > 0.75) {
+            fragColor = vec4(1.0, 0.0, 0.0, 1.0);
+        }
+        else if (noise > 0.5) {
+            fragColor = vec4(0.0, 1.0, 0.0, 1.0);
+        }
+        else {
+            fragColor = vec4(0.0, 0.0, 1.0, 1.0);
         }
     }
     """
diff --git a/tests/test_backend/test_opengl/test_lexer.py b/tests/test_backend/test_opengl/test_lexer.py
index 3c9a49c..9edd7b1 100644
--- a/tests/test_backend/test_opengl/test_lexer.py
+++ b/tests/test_backend/test_opengl/test_lexer.py
@@ -44,6 +44,23 @@ def test_if_statement_tokenization():
         pytest.fail("Struct parsing not implemented.")
 
 
+def test_if_else_condition_tokenization():
+    code_complex_if_else = """
+    if ((a + b) > (c * d)) {
+        return (a - c) / d;
+    } else if ((a - b) < c) {
+        return a + b;
+    }
+    else {
+        return;
+    }
+    """
+    try:
+        tokenize_code(code_complex_if_else)
+    except SyntaxError:
+        pytest.fail("Complex if-else condition parsing not implemented.")
+
+
 def test_for_statement_tokenization():
     code = """
     for (int i = 0; i < 10; i = i + 1) {
diff --git a/tests/test_backend/test_opengl/test_parser.py b/tests/test_backend/test_opengl/test_parser.py
index 3b69f81..28b64e1 100644
--- a/tests/test_backend/test_opengl/test_parser.py
+++ b/tests/test_backend/test_opengl/test_parser.py
@@ -171,6 +171,50 @@ def test_else_statement():
         pytest.fail("Struct parsing not implemented.")
 
 
+def test_else_if_statement():
+    code = """
+    #version 450
+    // Vertex shader
+    float perlinNoise(vec2 p) {
+        return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);
+    }
+
+    layout(location = 0) in vec3 position;
+    out vec2 vUV;
+
+    void main() {
+        vUV = position.xy * 10.0;
+        if (vUV.x > vUV.y) {
+            vUV = vec2(0.0, 0.0);
+        }
+        else {
+            vUV = vec2(1.0, 1.0);
+        }
+    }
+    // Fragment shader
+    in vec2 vUV;
+    layout(location = 0) out vec4 fragColor;
+
+    void main() {
+        float noise = perlinNoise(vUV);
+        if (noise > 0.75) {
+            fragColor = vec4(1.0, 0.0, 0.0, 1.0);
+        }
+        else if (noise > 0.5) {
+            fragColor = vec4(0.0, 1.0, 0.0, 1.0);
+        }
+        else {
+            fragColor = vec4(0.0, 0.0, 1.0, 1.0);
+        }
+    }
+    """
+    try:
+        tokens = tokenize_code(code)
+        parse_code(tokens)
+    except SyntaxError:
+        pytest.fail("Struct parsing not implemented.")
+
+
 def test_function_call():
     code = """
     #version 450
diff --git a/tests/test_translator/test_lexer.py b/tests/test_translator/test_lexer.py
index 83cdda1..dcc1c2d 100644
--- a/tests/test_translator/test_lexer.py
+++ b/tests/test_translator/test_lexer.py
@@ -6,7 +6,7 @@ from typing import List
 def tokenize_code(code: str) -> List:
     """Helper function to tokenize code."""
     lexer = Lexer(code)
-    return lexer.tokenize()
+    return lexer.tokens
 
 
 def test_input_output_tokenization():
@@ -101,6 +101,24 @@ def test_function_call_tokenization():
         pytest.fail("Function call tokenization not implemented.")
 
 
+def test_bitwise_operator_tokenization():
+    code = """
+    int a = 60; // 60 = 0011 1100
+    int b = 13; // 13 = 0000 1101
+    int c = 0;
+    c = a & b; // 12 = 0000 1100
+    c = a | b; // 61 = 0011 1101
+    c = a ^ b; // 49 = 0011 0001
+    c = ~a; // -61 = 1100 0011
+    c = a << 2; // 240 = 1111 0000
+    c = a >> 2; // 15 = 0000 1111
+      """
+    try:
+        tokenize_code(code)
+    except SyntaxError:
+        pytest.fail("Bitwise operator tokenization not implemented.")
+
+
 def test_data_types_tokenization():
     code = """
     int a;
@@ -115,6 +133,21 @@ def test_data_types_tokenization():
         pytest.fail("Data types tokenization not implemented.")
 
 
+def test_operators_tokenization():
+    code = """
+    int a;
+    a = 2 + 1;
+    a = a -  2;
+    a = a / 1;
+    a = a * 2;
+    a = a % 2;
+    """
+    try:
+        tokenize_code(code)
+    except SyntaxError:
+        pytest.fail("Operators tokenization not implemented.")
+
+
 def test_logical_operators_tokenization():
     code = """
     if (0.8 > 0.7 || 0.6 > 0.7) {    
@@ -126,4 +159,33 @@ def test_logical_operators_tokenization():
     try:
         tokenize_code(code)
     except SyntaxError:
-        pytest.fail("Data types tokenization not implemented.")
+        pytest.fail("Logical operators tokenization not implemented.")
+
+
+def test_assignment_operators_tokenization():
+    code = """
+    int a = 1;
+    a += 1;
+    a *= 2;
+    a /= a;
+    a -= -1;
+    a %= 2;
+    a &= 1;
+    a |= 1;
+    a ^= 1;
+    """
+    try:
+        tokenize_code(code)
+    except SyntaxError:
+        pytest.fail("Assignment operators tokenization not implemented.")
+
+
+def test_const_tokenization():
+    code = """
+    const int a;
+    """
+
+    try:
+        tokenize_code(code)
+    except SyntaxError:
+        pytest.fail("Const keyword tokenization failed")
diff --git a/tests/test_translator/test_parser.py b/tests/test_translator/test_parser.py
index 4c2fba6..2e22f74 100644
--- a/tests/test_translator/test_parser.py
+++ b/tests/test_translator/test_parser.py
@@ -207,3 +207,43 @@ def test_function_call():
         parse_code(tokens)
     except SyntaxError:
         pytest.fail("Struct parsing not implemented.")
+
+
+def test_logical_operators():
+    code = """
+        shader LightControl {
+        vertex {
+            input vec3 position;
+            output float isLightOn;
+
+            void main() {
+                if (position.x > 0.3 && position.z < 0.7) {
+                    isLightOn = 1.0;
+                } else {
+                    isLightOn = 0.0;
+                }
+
+                // Set the vertex position
+                gl_Position = vec4(position, 1.0);
+            }
+        }
+
+        fragment {
+            input float isLightOn;
+            output vec4 fragColor;
+
+            void main() {
+                if (isLightOn == 1.0) {
+                    fragColor = vec4(1.0, 1.0, 0.0, 1.0);
+                } else {
+                    fragColor = vec4(0.0, 0.0, 0.0, 1.0);
+                }
+            }
+        }
+    }
+    """
+    try:
+        tokens = tokenize_code(code)
+        parse_code(tokens)
+    except SyntaxError:
+        pytest.fail("Struct parsing not implemented.")