diff --git a/crosstl/backend/DirectX/DirectxLexer.py b/crosstl/backend/DirectX/DirectxLexer.py index 22c12f81..e2ac1da8 100644 --- a/crosstl/backend/DirectX/DirectxLexer.py +++ b/crosstl/backend/DirectX/DirectxLexer.py @@ -9,6 +9,7 @@ [ ("COMMENT_SINGLE", r"//.*"), ("COMMENT_MULTI", r"/\*[\s\S]*?\*/"), + ("BITWISE_NOT", r"~"), ("INCLUDE", r"\#include\b"), ("STRUCT", r"\bstruct\b"), ("CBUFFER", r"\bcbuffer\b"), diff --git a/crosstl/backend/DirectX/DirectxParser.py b/crosstl/backend/DirectX/DirectxParser.py index 79de030f..572847c1 100644 --- a/crosstl/backend/DirectX/DirectxParser.py +++ b/crosstl/backend/DirectX/DirectxParser.py @@ -527,7 +527,7 @@ def parse_multiplicative(self): return left def parse_unary(self): - if self.current_token[0] in ["PLUS", "MINUS"]: + if self.current_token[0] in ["PLUS", "MINUS", "BITWISE_NOT"]: op = self.current_token[1] self.eat(self.current_token[0]) operand = self.parse_unary() diff --git a/crosstl/backend/Metal/MetalLexer.py b/crosstl/backend/Metal/MetalLexer.py index 963b22c8..349144cc 100644 --- a/crosstl/backend/Metal/MetalLexer.py +++ b/crosstl/backend/Metal/MetalLexer.py @@ -8,6 +8,7 @@ [ ("COMMENT_SINGLE", r"//.*"), ("COMMENT_MULTI", r"/\*[\s\S]*?\*/"), + ("BITWISE_NOT", r"~"), ("PREPROCESSOR", r"#\w+"), ("STRUCT", r"\bstruct\b"), ("CONSTANT", r"\bconstant\b"), diff --git a/crosstl/backend/Metal/MetalParser.py b/crosstl/backend/Metal/MetalParser.py index d121507b..01da9d6f 100644 --- a/crosstl/backend/Metal/MetalParser.py +++ b/crosstl/backend/Metal/MetalParser.py @@ -460,7 +460,7 @@ def parse_multiplicative(self): return left def parse_unary(self): - if self.current_token[0] in ["PLUS", "MINUS"]: + if self.current_token[0] in ["PLUS", "MINUS", "BITWISE_NOT"]: op = self.current_token[1] self.eat(self.current_token[0]) operand = self.parse_unary() diff --git a/crosstl/backend/Mojo/MojoLexer.py b/crosstl/backend/Mojo/MojoLexer.py index 4faa8f3a..2d692516 100644 --- a/crosstl/backend/Mojo/MojoLexer.py +++ b/crosstl/backend/Mojo/MojoLexer.py @@ -8,6 +8,7 @@ [ ("COMMENT_SINGLE", r"#.*"), ("COMMENT_MULTI", r'"""[\s\S]*?"""'), + ("BITWISE_NOT", r"~"), ("STRUCT", r"\bstruct\b"), ("LET", r"\blet\b"), ("VAR", r"\bvar\b"), diff --git a/crosstl/backend/Mojo/MojoParser.py b/crosstl/backend/Mojo/MojoParser.py index e2ac3031..4daa727c 100644 --- a/crosstl/backend/Mojo/MojoParser.py +++ b/crosstl/backend/Mojo/MojoParser.py @@ -468,7 +468,7 @@ def parse_multiplicative(self): return left def parse_unary(self): - if self.current_token[0] in ["PLUS", "MINUS"]: + if self.current_token[0] in ["PLUS", "MINUS", "BITWISE_NOT"]: op = self.current_token[1] self.eat(self.current_token[0]) operand = self.parse_unary() diff --git a/crosstl/backend/Opengl/OpenglParser.py b/crosstl/backend/Opengl/OpenglParser.py index 370334cb..01f30c4e 100644 --- a/crosstl/backend/Opengl/OpenglParser.py +++ b/crosstl/backend/Opengl/OpenglParser.py @@ -729,11 +729,11 @@ def parse_unary(self): ASTNode: An ASTNode object representing the unary expression """ - if self.current_token[0] in ["PLUS", "MINUS"]: - op = self.current_token[0] - self.eat(op) - expr = self.parse_unary() - return UnaryOpNode(op, expr) + if self.current_token[0] in ["PLUS", "MINUS", "BITWISE_NOT"]: + op = self.current_token[1] + self.eat(self.current_token[0]) + operand = self.parse_unary() + return UnaryOpNode(op, operand) return self.parse_primary() def parse_primary(self): diff --git a/crosstl/backend/Vulkan/VulkanLexer.py b/crosstl/backend/Vulkan/VulkanLexer.py index b89f4d58..00aa5d57 100644 --- a/crosstl/backend/Vulkan/VulkanLexer.py +++ b/crosstl/backend/Vulkan/VulkanLexer.py @@ -8,6 +8,7 @@ [ ("COMMENT_SINGLE", r"//.*"), ("COMMENT_MULTI", r"/\*[\s\S]*?\*/"), + ("BITWISE_NOT", r"~"), ("WHITESPACE", r"\s+"), ("SEMANTIC", r":\w+"), ("PRE_INCREMENT", r"\+\+(?=\w)"), diff --git a/crosstl/backend/Vulkan/VulkanParser.py b/crosstl/backend/Vulkan/VulkanParser.py index f72d68d0..b0015d0c 100644 --- a/crosstl/backend/Vulkan/VulkanParser.py +++ b/crosstl/backend/Vulkan/VulkanParser.py @@ -445,6 +445,11 @@ def parse_primary(self): value = self.parse_primary() return UnaryOpNode("-", value) + if self.current_token[0] == "BITWISE_NOT": + self.eat("BITWISE_NOT") + value = self.parse_primary() + return UnaryOpNode("~", value) + if ( self.current_token[0] == "IDENTIFIER" or self.current_token[1] in VALID_DATA_TYPES @@ -663,3 +668,11 @@ def parse_uniform(self): self.eat("IDENTIFIER") self.eat("SEMICOLON") return UniformNode(name, var_type) + + def parse_unary(self): + if self.current_token[0] in ["PLUS", "MINUS", "BITWISE_NOT"]: + op = self.current_token[1] + self.eat(self.current_token[0]) + operand = self.parse_unary() + return UnaryOpNode(op, operand) + return self.parse_primary() diff --git a/crosstl/backend/slang/SlangLexer.py b/crosstl/backend/slang/SlangLexer.py index 44dc39f4..e7740b89 100644 --- a/crosstl/backend/slang/SlangLexer.py +++ b/crosstl/backend/slang/SlangLexer.py @@ -8,6 +8,7 @@ [ ("COMMENT_SINGLE", r"//.*"), ("COMMENT_MULTI", r"/\*[\s\S]*?\*/"), + ("BITWISE_NOT", r"~"), ("STRUCT", r"\bstruct\b"), ("CBUFFER", r"\bcbuffer\b"), ("TYPE_SHADER", r'\[shader\("(vertex|fragment|compute)"\)\]'), diff --git a/crosstl/backend/slang/SlangParser.py b/crosstl/backend/slang/SlangParser.py index daa5200d..02ea2630 100644 --- a/crosstl/backend/slang/SlangParser.py +++ b/crosstl/backend/slang/SlangParser.py @@ -497,7 +497,7 @@ def parse_multiplicative(self): return left def parse_unary(self): - if self.current_token[0] in ["PLUS", "MINUS"]: + if self.current_token[0] in ["PLUS", "MINUS", "BITWISE_NOT"]: op = self.current_token[1] self.eat(self.current_token[0]) operand = self.parse_unary() diff --git a/crosstl/translator/lexer.py b/crosstl/translator/lexer.py index 21929417..84cad4f3 100644 --- a/crosstl/translator/lexer.py +++ b/crosstl/translator/lexer.py @@ -7,6 +7,7 @@ ("COMMENT_SINGLE", r"//.*"), ("COMMENT_MULTI", r"/\*[\s\S]*?\*/"), ("SHADER", r"\bshader\b"), + ("BITWISE_NOT", r"~"), ("VOID", r"\bvoid\b"), ("STRUCT", r"\bstruct\b"), ("CBUFFER", r"\bcbuffer\b"), diff --git a/crosstl/translator/parser.py b/crosstl/translator/parser.py index c17f72a5..18c76d2f 100644 --- a/crosstl/translator/parser.py +++ b/crosstl/translator/parser.py @@ -690,11 +690,9 @@ def parse_unary(self): This method parses a unary expression in the shader code. Returns: - ASTNode: An ASTNode object representing the unary expression - """ - if self.current_token[0] in ["PLUS", "MINUS"]: + if self.current_token[0] in ["PLUS", "MINUS", "BITWISE_NOT"]: op = self.current_token[0] self.eat(op) expr = self.parse_unary() diff --git a/tests/test_backend/test_directx/test_codegen.py b/tests/test_backend/test_directx/test_codegen.py index 292a824a..0ffca73e 100644 --- a/tests/test_backend/test_directx/test_codegen.py +++ b/tests/test_backend/test_directx/test_codegen.py @@ -718,5 +718,21 @@ def test_half_dtype_codegen(): pytest.fail("half dtype parsing or code generation not implemented.") +def test_bitwise_not_codegen(): + code = """ + void main() { + int a = 5; + int b = ~a; // Bitwise NOT + } + """ + try: + tokens = tokenize_code(code) + ast = parse_code(tokens) + generated_code = generate_code(ast) + print(generated_code) + except SyntaxError: + pytest.fail("Bitwise NOT operator 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 02f78a2a..cf1b7b05 100644 --- a/tests/test_backend/test_directx/test_lexer.py +++ b/tests/test_backend/test_directx/test_lexer.py @@ -299,5 +299,20 @@ def test_half_dtype_tokenization(): pytest.fail("half dtype tokenization is not implemented.") +def test_bitwise_not_tokenization(): + code = """ + int a = ~5; // Bitwise NOT + """ + tokens = tokenize_code(code) + + has_not = False + for token in tokens: + if token == ("BITWISE_NOT", "~"): + has_not = True + break + + assert has_not, "Bitwise NOT operator (~) not tokenized correctly" + + 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 7af3a86f..fb1a46df 100644 --- a/tests/test_backend/test_directx/test_parser.py +++ b/tests/test_backend/test_directx/test_parser.py @@ -415,5 +415,19 @@ def test_double_dtype_parsing(): pytest.fail("half dtype not implemented.") +def test_bitwise_not_parsing(): + code = """ + void main() { + int a = 5; + int b = ~a; // Bitwise NOT + } + """ + try: + tokens = tokenize_code(code) + parse_code(tokens) + except SyntaxError: + pytest.fail("Bitwise NOT operator parsing not implemented") + + if __name__ == "__main__": pytest.main() diff --git a/tests/test_backend/test_metal/test_codegen.py b/tests/test_backend/test_metal/test_codegen.py index 12a0426a..5e507296 100644 --- a/tests/test_backend/test_metal/test_codegen.py +++ b/tests/test_backend/test_metal/test_codegen.py @@ -331,5 +331,21 @@ def test_else_if(): print(generated_code) +def test_bitwise_not_codegen(): + code = """ + void main() { + int a = 5; + int b = ~a; // Bitwise NOT + } + """ + try: + tokens = tokenize_code(code) + ast = parse_code(tokens) + generated_code = generate_code(ast) + print(generated_code) + except SyntaxError: + pytest.fail("Bitwise NOT operator code generation not implemented") + + if __name__ == "__main__": pytest.main() diff --git a/tests/test_backend/test_metal/test_lexer.py b/tests/test_backend/test_metal/test_lexer.py index 8ea7b838..2569891f 100644 --- a/tests/test_backend/test_metal/test_lexer.py +++ b/tests/test_backend/test_metal/test_lexer.py @@ -142,5 +142,20 @@ def test_mod_tokenization(): assert has_mod, "Modulus operator (%) not tokenized correctly" +def test_bitwise_not_tokenization(): + code = """ + int a = ~5; // Bitwise NOT + """ + tokens = tokenize_code(code) + + has_not = False + for token in tokens: + if token == ("BITWISE_NOT", "~"): + has_not = True + break + + assert has_not, "Bitwise NOT operator (~) not tokenized correctly" + + if __name__ == "__main__": pytest.main() diff --git a/tests/test_backend/test_metal/test_parser.py b/tests/test_backend/test_metal/test_parser.py index 15b2f6ff..35e97647 100644 --- a/tests/test_backend/test_metal/test_parser.py +++ b/tests/test_backend/test_metal/test_parser.py @@ -167,5 +167,19 @@ def test_mod_parsing(): pytest.fail("Modulus operator parsing not implemented") +def test_bitwise_not_parsing(): + code = """ + void main() { + int a = 5; + int b = ~a; // Bitwise NOT + } + """ + try: + tokens = tokenize_code(code) + parse_code(tokens) + except SyntaxError: + pytest.fail("Bitwise NOT operator parsing not implemented") + + if __name__ == "__main__": pytest.main() diff --git a/tests/test_backend/test_mojo/test_lexer.py b/tests/test_backend/test_mojo/test_lexer.py index 95f7d18b..3b987289 100644 --- a/tests/test_backend/test_mojo/test_lexer.py +++ b/tests/test_backend/test_mojo/test_lexer.py @@ -25,5 +25,18 @@ def test_mod_tokenization(): assert has_mod, "Modulus operator (%) not tokenized correctly" +def test_bitwise_not_tokenization(): + code = """ + int a = ~5; // Bitwise NOT + """ + tokens = tokenize_code(code) + has_not = False + for token in tokens: + if token == ("BITWISE_NOT", "~"): + has_not = True + break + assert has_not, "Bitwise NOT operator (~) not tokenized correctly" + + if __name__ == "__main__": pytest.main() diff --git a/tests/test_backend/test_opengl/test_lexer.py b/tests/test_backend/test_opengl/test_lexer.py index ab677383..61c490ce 100644 --- a/tests/test_backend/test_opengl/test_lexer.py +++ b/tests/test_backend/test_opengl/test_lexer.py @@ -128,17 +128,27 @@ def test_mod_tokenization(): int a = 10 % 3; // Basic modulus """ tokens = tokenize_code(code) - - # Find the modulus operator in tokens has_mod = False for token in tokens: if token == ("MOD", "%"): has_mod = True break - assert has_mod, "Modulus operator (%) not tokenized correctly" +def test_bitwise_not_tokenization(): + code = """ + int a = ~5; // Bitwise NOT + """ + tokens = tokenize_code(code) + has_not = False + for token in tokens: + if token == ("BITWISE_NOT", "~"): + has_not = True + break + assert has_not, "Bitwise NOT operator (~) not tokenized correctly" + + def test_unsigned_int_dtype_tokenization(): code = """ double ComputeArea(double radius) { diff --git a/tests/test_backend/test_slang/test_lexer.py b/tests/test_backend/test_slang/test_lexer.py index 1ef8bd23..2f7245b5 100644 --- a/tests/test_backend/test_slang/test_lexer.py +++ b/tests/test_backend/test_slang/test_lexer.py @@ -106,16 +106,26 @@ def test_mod_tokenization(): int a = 10 % 3; // Basic modulus """ tokens = tokenize_code(code) - - # Find the modulus operator in tokens has_mod = False for token in tokens: if token == ("MOD", "%"): has_mod = True break - assert has_mod, "Modulus operator (%) not tokenized correctly" +def test_bitwise_not_tokenization(): + code = """ + int a = ~5; // Bitwise NOT + """ + tokens = tokenize_code(code) + has_not = False + for token in tokens: + if token == ("BITWISE_NOT", "~"): + has_not = True + break + assert has_not, "Bitwise NOT operator (~) not tokenized correctly" + + if __name__ == "__main__": pytest.main() diff --git a/tests/test_backend/test_vulkan/test_lexer.py b/tests/test_backend/test_vulkan/test_lexer.py index e68db359..bd8938e7 100644 --- a/tests/test_backend/test_vulkan/test_lexer.py +++ b/tests/test_backend/test_vulkan/test_lexer.py @@ -25,5 +25,18 @@ def test_mod_tokenization(): assert has_mod, "Modulus operator (%) not tokenized correctly" +def test_bitwise_not_tokenization(): + code = """ + int a = ~5; // Bitwise NOT + """ + tokens = tokenize_code(code) + has_not = False + for token in tokens: + if token == ("BITWISE_NOT", "~"): + has_not = True + break + assert has_not, "Bitwise NOT operator (~) not tokenized correctly" + + if __name__ == "__main__": pytest.main() diff --git a/tests/test_backend/test_vulkan/test_parser.py b/tests/test_backend/test_vulkan/test_parser.py index ec7789aa..36f9976d 100644 --- a/tests/test_backend/test_vulkan/test_parser.py +++ b/tests/test_backend/test_vulkan/test_parser.py @@ -36,5 +36,19 @@ def test_mod_parsing(): pytest.fail("Modulus operator parsing not implemented") +def test_bitwise_not_parsing(): + code = """ + void main() { + int a = 5; + int b = ~a; // Bitwise NOT + } + """ + try: + tokens = tokenize_code(code) + parse_code(tokens) + except SyntaxError: + pytest.fail("Bitwise NOT operator parsing not implemented") + + if __name__ == "__main__": pytest.main() diff --git a/tests/test_translator/test_lexer.py b/tests/test_translator/test_lexer.py index 35c778a1..f4793b03 100644 --- a/tests/test_translator/test_lexer.py +++ b/tests/test_translator/test_lexer.py @@ -256,5 +256,19 @@ def test_illegal_character(): tokenize_code(code) +def test_bitwise_not_tokenization(): + code = """ + int a = 5; + int b = ~a; // Bitwise NOT + """ + tokens = tokenize_code(code) + has_not = False + for token in tokens: + if token == ("BITWISE_NOT", "~"): + has_not = True + break + assert has_not, "Bitwise NOT operator (~) not tokenized correctly" + + if __name__ == "__main__": pytest.main() diff --git a/tests/test_translator/test_parser.py b/tests/test_translator/test_parser.py index 8f3ca4a9..e2e34636 100644 --- a/tests/test_translator/test_parser.py +++ b/tests/test_translator/test_parser.py @@ -2,6 +2,7 @@ import pytest from typing import List from crosstl.translator.parser import Parser +from crosstl.translator.ast import ShaderNode def tokenize_code(code: str) -> List: @@ -621,5 +622,42 @@ def test_modulus_operations(): pytest.fail("Modulus operations not working") +def test_bitwise_not(): + code = """ + shader test { + void main() { + int a = 5; + int b = ~a; // Bitwise NOT + } + } + """ + try: + tokens = tokenize_code(code) + ast = parse_code(tokens) + assert isinstance(ast, ShaderNode) + except SyntaxError: + pytest.fail("Bitwise NOT operator parsing failed") + + +def test_bitwise_expressions(): + code = """ + shader test { + void main() { + int a = 5; + int b = ~a; // Bitwise NOT + int c = a & b; // Bitwise AND + int d = a | b; // Bitwise OR + int e = a ^ b; // Bitwise XOR + } + } + """ + try: + tokens = tokenize_code(code) + ast = parse_code(tokens) + assert isinstance(ast, ShaderNode) + except SyntaxError: + pytest.fail("Bitwise expressions parsing failed") + + if __name__ == "__main__": pytest.main()