Source code for crosstl.backend.GLSL.openglCrossglCodegen

"""Reverse code generator that emits CrossGL from GLSL AST nodes."""

from .OpenglAst import (
    ShaderNode,
    VariableNode,
    AssignmentNode,
    FunctionNode,
    BinaryOpNode,
    UnaryOpNode,
    ReturnNode,
    FunctionCallNode,
    IfNode,
    ForNode,
    WhileNode,
    DoWhileNode,
    LayoutNode,
    VectorConstructorNode,
    ConstantNode,
    MemberAccessNode,
    TernaryOpNode,
    ArrayAccessNode,
    StructNode,
    UniformNode,
    SwitchNode,
    CaseNode,
    BlockNode,
    NumberNode,
    PostfixOpNode,
    BreakNode,
    ContinueNode,
    DiscardNode,
)


[docs] class GLSLToCrossGLConverter: """Serialize OpenGL backend AST nodes back into CrossGL source.""" def __init__(self, shader_type="vertex"): """Initialize GLSL-to-CrossGL mappings for a shader stage.""" self.shader_type = shader_type self.indent_level = 0 self.indent_str = " " # Mapping of GLSL built-in functions to CrossGL equivalents self.function_map = { "dot": "dot", "normalize": "normalize", "sin": "sin", "cos": "cos", "tan": "tan", "asin": "asin", "acos": "acos", "atan": "atan", "pow": "pow", "exp": "exp", "log": "log", "sqrt": "sqrt", "inversesqrt": "inverseSqrt", "abs": "abs", "sign": "sign", "floor": "floor", "ceil": "ceil", "fract": "fract", "mod": "mod", "min": "min", "max": "max", "clamp": "clamp", "mix": "mix", "step": "step", "smoothstep": "smoothstep", "length": "length", "distance": "distance", "reflect": "reflect", "refract": "refract", "texture": "sample", "texture2D": "sample", "textureCube": "sample", } # Mapping of GLSL types to CrossGL types self.type_map = { "float": "float", "int": "int", "uint": "uint", "bool": "bool", "vec2": "vec2", "vec3": "vec3", "vec4": "vec4", "ivec2": "ivec2", "ivec3": "ivec3", "ivec4": "ivec4", "bvec2": "bvec2", "bvec3": "bvec3", "bvec4": "bvec4", "mat2": "mat2", "mat3": "mat3", "mat4": "mat4", "sampler1D": "Texture1D", "sampler2D": "Texture2D", "sampler3D": "Texture3D", "samplerCube": "TextureCube", "sampler1DArray": "Texture1DArray", "sampler2DArray": "Texture2DArray", "samplerCubeArray": "TextureCubeArray", "sampler2DShadow": "Texture2DShadow", "sampler1DShadow": "Texture1DShadow", "sampler1DArrayShadow": "Texture1DArrayShadow", "sampler2DArrayShadow": "Texture2DArrayShadow", "samplerCubeShadow": "TextureCubeShadow", "samplerCubeArrayShadow": "TextureCubeArrayShadow", "sampler2DRect": "Texture2DRect", "sampler2DRectShadow": "Texture2DRectShadow", "samplerBuffer": "TextureBuffer", "sampler2DMS": "Texture2DMS", "sampler2DMSArray": "Texture2DMSArray", "isampler1D": "Texture1DInt", "isampler2D": "Texture2DInt", "isampler3D": "Texture3DInt", "isamplerCube": "TextureCubeInt", "isampler1DArray": "Texture1DArrayInt", "isampler2DArray": "Texture2DArrayInt", "isamplerCubeArray": "TextureCubeArrayInt", "isampler2DRect": "Texture2DRectInt", "isamplerBuffer": "TextureBufferInt", "isampler2DMS": "Texture2DMSInt", "isampler2DMSArray": "Texture2DMSArrayInt", "usampler1D": "Texture1DUint", "usampler2D": "Texture2DUint", "usampler3D": "Texture3DUint", "usamplerCube": "TextureCubeUint", "usampler1DArray": "Texture1DArrayUint", "usampler2DArray": "Texture2DArrayUint", "usamplerCubeArray": "TextureCubeArrayUint", "usampler2DRect": "Texture2DRectUint", "usamplerBuffer": "TextureBufferUint", "usampler2DMS": "Texture2DMSUint", "usampler2DMSArray": "Texture2DMSArrayUint", "image1D": "Image1D", "image2D": "Image2D", "image3D": "Image3D", "imageCube": "ImageCube", "image1DArray": "Image1DArray", "image2DArray": "Image2DArray", "imageCubeArray": "ImageCubeArray", "image2DRect": "Image2DRect", "imageBuffer": "ImageBuffer", "image2DMS": "Image2DMS", "image2DMSArray": "Image2DMSArray", "iimage1D": "Image1DInt", "iimage2D": "Image2DInt", "iimage3D": "Image3DInt", "iimageCube": "ImageCubeInt", "iimage1DArray": "Image1DArrayInt", "iimage2DArray": "Image2DArrayInt", "iimageCubeArray": "ImageCubeArrayInt", "iimage2DRect": "Image2DRectInt", "iimageBuffer": "ImageBufferInt", "iimage2DMS": "Image2DMSInt", "iimage2DMSArray": "Image2DMSArrayInt", "uimage1D": "Image1DUint", "uimage2D": "Image2DUint", "uimage3D": "Image3DUint", "uimageCube": "ImageCubeUint", "uimage1DArray": "Image1DArrayUint", "uimage2DArray": "Image2DArrayUint", "uimageCubeArray": "ImageCubeArrayUint", "uimage2DRect": "Image2DRectUint", "uimageBuffer": "ImageBufferUint", "uimage2DMS": "Image2DMSUint", "uimage2DMSArray": "Image2DMSArrayUint", "void": "void", } # Map of GLSL operators to CrossGL operators self.operator_map = { "PLUS": "+", "MINUS": "-", "MULTIPLY": "*", "DIVIDE": "/", "GREATER_THAN": ">", "LESS_THAN": "<", "LESS_EQUAL": "<=", "GREATER_EQUAL": ">=", "EQUAL": "==", "NOT_EQUAL": "!=", "LOGICAL_AND": "&&", "LOGICAL_OR": "||", "ASSIGN_ADD": "+=", "ASSIGN_SUB": "-=", "ASSIGN_MUL": "*=", "ASSIGN_DIV": "/=", "ASSIGN_MOD": "%=", "MOD": "%", "BITWISE_SHIFT_RIGHT": ">>", "BITWISE_SHIFT_LEFT": "<<", "BITWISE_XOR": "^", "ASSIGN_SHIFT_RIGHT": ">>=", "ASSIGN_SHIFT_LEFT": "<<=", "ASSIGN_AND": "&=", "ASSIGN_OR": "|=", "ASSIGN_XOR": "^=", } # Shader-specific info self.uniform_vars = [] self.inputs = [] self.outputs = [] self.local_vars = [] def indent(self): return self.indent_str * self.indent_level def increase_indent(self): self.indent_level += 1 def decrease_indent(self): self.indent_level -= 1 def stage_struct_name(self): return "".join(part.capitalize() for part in self.shader_type.split("_")) def _qualifier_set(self, var): qualifiers = getattr(var, "qualifiers", None) or [] return {str(q).lower() for q in qualifiers} def _is_input_var(self, var): io_type = str(getattr(var, "io_type", "") or "").upper() qualifiers = self._qualifier_set(var) return io_type == "IN" or "in" in qualifiers or "inout" in qualifiers def _is_output_var(self, var): io_type = str(getattr(var, "io_type", "") or "").upper() qualifiers = self._qualifier_set(var) return io_type == "OUT" or "out" in qualifiers or "inout" in qualifiers def _is_resource_type(self, type_name): if not type_name: return False name = str(type_name) return name.startswith( ("sampler", "isampler", "usampler", "image", "iimage", "uimage") ) def format_layout(self, layout_entry): layout = ( layout_entry.get("layout", {}) if isinstance(layout_entry, dict) else {} ) qualifiers = ( layout_entry.get("qualifiers", []) if isinstance(layout_entry, dict) else [] ) parts = [] for key, value in layout.items(): if value is None: parts.append(str(key)) else: parts.append(f"{key} = {value}") layout_str = f"layout({', '.join(parts)})" if parts else "layout()" if qualifiers: layout_str += " " + " ".join(qualifiers) return layout_str.strip()
[docs] def generate(self, ast): """Generate a complete CrossGL shader from a parsed GLSL AST.""" if ast is None: return "// Empty shader" if not isinstance(ast, ShaderNode): return f"// Unexpected AST node type: {type(ast)}" return self.generate_shader(ast)
def generate_shader(self, node): self.uniform_vars = [] self.inputs = [] self.outputs = [] self.local_vars = [] for var in node.io_variables: if isinstance(var, (LayoutNode, VariableNode)): if self._is_input_var(var): self.inputs.append(var) if self._is_output_var(var): self.outputs.append(var) # Ensure vertex-like stages include gl_Position if self.shader_type in ( "vertex", "geometry", "tessellation_control", "tessellation_evaluation", ): has_position = any( isinstance(var, VariableNode) and var.name == "gl_Position" for var in self.outputs ) if not has_position: builtin = VariableNode( "vec4", "gl_Position", qualifiers=["out"], semantic="gl_Position" ) self.outputs.append(builtin) # Ensure fragment outputs include gl_FragColor if no outputs declared if self.shader_type == "fragment" and not self.outputs: builtin = VariableNode( "vec4", "gl_FragColor", qualifiers=["out"], semantic="gl_FragColor" ) self.outputs.append(builtin) for uniform in node.uniforms: self.uniform_vars.append(uniform) result = "" preprocessor = getattr(node, "preprocessor", []) or [] if preprocessor: for line in preprocessor: result += f"{line}\n" result += "\n" result += "shader main {\n" layouts = getattr(node, "layouts", []) or [] if layouts: for layout in layouts: result += self.indent_str + f"// {self.format_layout(layout)}\n" result += "\n" # Generate struct definitions for struct in node.structs: result += self.indent_str + self.generate_struct(struct) + "\n\n" # Generate input struct if needed if self.inputs and self.shader_type in ( "vertex", "fragment", "geometry", "tessellation_control", "tessellation_evaluation", ): result += self.indent_str + f"struct {self.stage_struct_name()}Input {{\n" self.increase_indent() for input_var in self.inputs: var_type = self.convert_type(input_var.vtype) var_name = input_var.name semantic = "" if getattr(input_var, "semantic", None): semantic = f" @ {input_var.semantic}" result += self.indent() + f"{var_type} {var_name}{semantic};\n" self.decrease_indent() result += self.indent_str + "};\n\n" # Generate output struct for vertex-like stages if self.outputs and self.shader_type in ( "vertex", "geometry", "tessellation_control", "tessellation_evaluation", ): result += self.indent_str + f"struct {self.stage_struct_name()}Output {{\n" self.increase_indent() for output_var in self.outputs: var_type = self.convert_type(output_var.vtype) var_name = output_var.name semantic = "" if getattr(output_var, "semantic", None): semantic = f" @ {output_var.semantic}" result += self.indent() + f"{var_type} {var_name}{semantic};\n" self.decrease_indent() result += self.indent_str + "};\n\n" # Generate uniforms: split resource uniforms from constant data if self.uniform_vars: resource_uniforms = [ u for u in self.uniform_vars if self._is_resource_type(u.vtype) ] data_uniforms = [ u for u in self.uniform_vars if not self._is_resource_type(u.vtype) ] for uniform in resource_uniforms: var_type = self.convert_type(uniform.vtype) var_name = uniform.name array_suffix = "" if getattr(uniform, "array_size", None) is not None: array_suffix = f"[{self.generate_expression(uniform.array_size)}]" result += self.indent_str + f"{var_type} {var_name}{array_suffix};\n" if data_uniforms: result += self.indent_str + "cbuffer Uniforms {\n" self.increase_indent() for uniform in data_uniforms: var_type = self.convert_type(uniform.vtype) var_name = uniform.name array_suffix = "" if getattr(uniform, "array_size", None) is not None: array_suffix = ( f"[{self.generate_expression(uniform.array_size)}]" ) result += self.indent() + f"{var_type} {var_name}{array_suffix};\n" self.decrease_indent() result += self.indent_str + "};\n" result += "\n" # Generate global constants for const_var in getattr(node, "constant", []) or []: result += ( self.indent_str + self.generate_variable_declaration(const_var) + ";\n" ) if getattr(node, "constant", []): result += "\n" # Generate global variables for global_var in getattr(node, "global_variables", []) or []: result += ( self.indent_str + self.generate_variable_declaration(global_var) + ";\n" ) if getattr(node, "global_variables", []): result += "\n" # Generate shader function result += self.indent_str + f"{self.shader_type} {{\n" main_function = None other_functions = [] for function in node.functions: if function.name == "main": main_function = function else: other_functions.append(function) # Generate auxiliary functions first for function in other_functions: self.increase_indent() result += self.indent() + self.generate_function(function) + "\n\n" self.decrease_indent() # Generate the main function if it exists if main_function: self.increase_indent() # Determine function signature based on shader type if self.shader_type == "vertex": result += ( self.indent() + f"{self.stage_struct_name()}Output main({self.stage_struct_name()}Input input)" ) elif self.shader_type == "fragment": output_type = "vec4" output_name = "gl_FragColor" if self.outputs: output_type = self.convert_type(self.outputs[0].vtype) output_name = self.outputs[0].name result += ( self.indent() + f"{output_type} main({self.stage_struct_name()}Input input) @ {output_name}" ) elif self.shader_type == "compute": result += self.indent() + "void main()" else: result += ( self.indent() + f"{self.stage_struct_name()}Output main({self.stage_struct_name()}Input input)" ) result += " {\n" self.increase_indent() # For vertex shaders, create the output struct if self.shader_type == "vertex": result += self.indent() + f"{self.stage_struct_name()}Output output;\n" # For fragment shaders, declare a local output if assignments are used if self.shader_type == "fragment" and self.outputs: output_type = self.convert_type(self.outputs[0].vtype) output_name = self.outputs[0].name result += self.indent() + f"{output_type} {output_name};\n" # Generate statements for the main function for statement in main_function.body: result += self.indent() + self.generate_statement(statement) + "\n" # Add implicit return for stages with output struct if not present if self.shader_type in ( "vertex", "geometry", "tessellation_control", "tessellation_evaluation", ) and not any(isinstance(stmt, ReturnNode) for stmt in main_function.body): result += self.indent() + "return output;\n" # Add implicit return for fragment shaders if not present if self.shader_type == "fragment" and not any( isinstance(stmt, ReturnNode) for stmt in main_function.body ): output_name = self.outputs[0].name if self.outputs else "gl_FragColor" result += self.indent() + f"return {output_name};\n" self.decrease_indent() result += self.indent() + "}\n" self.decrease_indent() result += self.indent_str + "}\n" result += "}\n" return result def generate_struct(self, node): result = f"struct {node.name} {{\n" self.increase_indent() members = getattr(node, "members", None) or getattr(node, "fields", []) for field in members: if isinstance(field, dict): var_type = self.convert_type(field.get("type")) var_name = field.get("name") semantic = "" else: var_type = self.convert_type(getattr(field, "vtype", "")) var_name = getattr(field, "name", "") semantic = "" if getattr(field, "semantic", None): semantic = f" @ {field.semantic}" result += self.indent() + f"{var_type} {var_name}{semantic};\n" self.decrease_indent() result += self.indent() + "};" return result
[docs] def generate_function(self, node): """Render one GLSL function node as a CrossGL function block.""" return_type = self.convert_type(node.return_type) name = node.name params = [] for param in node.params: if isinstance(param, tuple): # (type, name) param_type, param_name = param params.append(f"{self.convert_type(param_type)} {param_name}") elif isinstance(param, VariableNode): params.append(f"{self.convert_type(param.vtype)} {param.name}") params_str = ", ".join(params) result = f"{return_type} {name}({params_str}) {{\n" self.increase_indent() for statement in node.body: result += self.indent() + self.generate_statement(statement) + "\n" self.decrease_indent() result += self.indent() + "}" return result
[docs] def generate_statement(self, node): """Render a GLSL statement node as CrossGL source.""" if isinstance(node, AssignmentNode): return self.generate_assignment(node) + ";" elif isinstance(node, IfNode): return self.generate_if(node) elif isinstance(node, ForNode): return self.generate_for(node) elif isinstance(node, WhileNode): return self.generate_while(node) elif isinstance(node, DoWhileNode): return self.generate_do_while(node) elif isinstance(node, ReturnNode): return self.generate_return(node) + ";" elif isinstance(node, VariableNode): return self.generate_variable_declaration(node) + ";" elif isinstance(node, FunctionCallNode): return self.generate_function_call(node) + ";" elif isinstance(node, SwitchNode): return self.generate_switch_statement(node) elif isinstance(node, BlockNode): return self.generate_block(node) elif isinstance(node, BreakNode): return "break;" elif isinstance(node, ContinueNode): return "continue;" elif isinstance(node, DiscardNode): return "discard;" elif isinstance(node, PostfixOpNode): return self.generate_expression(node) + ";" else: return self.generate_expression(node) + ";"
def generate_assignment(self, node): if hasattr(node, "left") and hasattr(node, "right"): lhs = node.left rhs = node.right op = getattr(node, "operator", "=") if isinstance(lhs, VariableNode) and lhs.vtype: var_type = self.convert_type(lhs.vtype) var_name = lhs.name value = self.generate_expression(rhs) return f"{var_type} {var_name} {op} {value}" left_expr = self.generate_expression(lhs) right_expr = self.generate_expression(rhs) return f"{left_expr} {op} {right_expr}" return self.generate_expression(node) def generate_if(self, node): condition_node = getattr(node, "condition", None) if condition_node is None: condition_node = getattr(node, "if_condition", None) condition = self.generate_expression(condition_node) result = f"if ({condition}) {{\n" # Generate if body self.increase_indent() for statement in getattr(node, "if_body", []) or []: result += self.indent() + self.generate_statement(statement) + "\n" self.decrease_indent() result += self.indent() + "}" else_if_chain = [] if hasattr(node, "else_if_conditions") and hasattr(node, "else_if_bodies"): else_if_chain = list(zip(node.else_if_conditions, node.else_if_bodies)) elif hasattr(node, "else_if_chain"): else_if_chain = node.else_if_chain for elif_condition, elif_body in else_if_chain: elif_cond = self.generate_expression(elif_condition) result += f" else if ({elif_cond}) {{\n" self.increase_indent() for statement in elif_body: result += self.indent() + self.generate_statement(statement) + "\n" self.decrease_indent() result += self.indent() + "}" if node.else_body: result += " else {\n" self.increase_indent() for statement in node.else_body: result += self.indent() + self.generate_statement(statement) + "\n" self.decrease_indent() result += self.indent() + "}" return result def generate_for(self, node): init = self.generate_statement(node.init).rstrip(";") if node.init else "" condition = self.generate_expression(node.condition) if node.condition else "" update_node = getattr(node, "update", None) or getattr(node, "iteration", None) iteration = ( self.generate_statement(update_node).rstrip(";") if update_node else "" ) result = f"for ({init}; {condition}; {iteration}) {{\n" self.increase_indent() for statement in node.body: result += self.indent() + self.generate_statement(statement) + "\n" self.decrease_indent() result += self.indent() + "}" return result def generate_while(self, node): condition = self.generate_expression(node.condition) result = f"while ({condition}) {{\n" self.increase_indent() for statement in node.body: result += self.indent() + self.generate_statement(statement) + "\n" self.decrease_indent() result += self.indent() + "}" return result def generate_do_while(self, node): condition = self.generate_expression(node.condition) result = "while (true) {\n" self.increase_indent() for statement in node.body: result += self.indent() + self.generate_statement(statement) + "\n" result += self.indent() + f"if (!({condition})) {{\n" self.increase_indent() result += self.indent() + "break;\n" self.decrease_indent() result += self.indent() + "}\n" self.decrease_indent() result += self.indent() + "}" return result def generate_block(self, node): result = "{\n" self.increase_indent() for statement in node.statements: result += self.indent() + self.generate_statement(statement) + "\n" self.decrease_indent() result += self.indent() + "}" return result def generate_return(self, node): if node.value is None: return "return" return f"return {self.generate_expression(node.value)}"
[docs] def generate_expression(self, node): """Render an OpenGL backend expression node as CrossGL syntax.""" if node is None: return "" if isinstance(node, str): return node elif isinstance(node, NumberNode): return str(node.value) elif isinstance(node, (int, float)): return str(node) elif isinstance(node, VariableNode): if self.shader_type in ( "vertex", "fragment", "geometry", "tessellation_control", "tessellation_evaluation", ) and any(var.name == node.name for var in self.inputs): return f"input.{node.name}" if self.shader_type in ( "vertex", "geometry", "tessellation_control", "tessellation_evaluation", ) and any(var.name == node.name for var in self.outputs): return f"output.{node.name}" return node.name elif isinstance(node, BinaryOpNode): left = self.generate_expression(node.left) right = self.generate_expression(node.right) operator = self.operator_map.get(node.op, node.op) return f"({left} {operator} {right})" elif isinstance(node, UnaryOpNode): operand = self.generate_expression(node.operand) operator = self.operator_map.get(node.op, node.op) return f"({operator}{operand})" elif isinstance(node, PostfixOpNode): operand = self.generate_expression(node.operand) return f"({operand}{node.op})" elif isinstance(node, AssignmentNode): return self.generate_assignment(node) elif isinstance(node, FunctionCallNode): return self.generate_function_call(node) elif isinstance(node, MemberAccessNode): return self.generate_member_access(node) elif isinstance(node, ArrayAccessNode): return self.generate_array_access(node) elif isinstance(node, TernaryOpNode): condition = self.generate_expression(node.condition) true_expr = self.generate_expression(node.true_expr) false_expr = self.generate_expression(node.false_expr) return f"({condition} ? {true_expr} : {false_expr})" elif isinstance(node, VectorConstructorNode): args = ", ".join(self.generate_expression(arg) for arg in node.args) return f"{self.convert_type(node.type_name)}({args})" else: return str(node)
def generate_function_call(self, node): name = node.name if isinstance(name, MemberAccessNode): name = self.generate_member_access(name) elif isinstance(name, VariableNode): name = name.name if name in [ "vec2", "vec3", "vec4", "ivec2", "ivec3", "ivec4", "bvec2", "bvec3", "bvec4", "uvec2", "uvec3", "uvec4", "dvec2", "dvec3", "dvec4", "mat2", "mat3", "mat4", "mat2x2", "mat2x3", "mat2x4", "mat3x2", "mat3x3", "mat3x4", "mat4x2", "mat4x3", "mat4x4", ]: args = ", ".join(self.generate_expression(arg) for arg in node.args) return f"{self.convert_type(name)}({args})" mapped_name = self.function_map.get(name, name) args = ", ".join(self.generate_expression(arg) for arg in node.args) return f"{mapped_name}({args})" def generate_member_access(self, node): object_name = "" if isinstance(node.object, VariableNode): if self.shader_type in ( "vertex", "fragment", "geometry", "tessellation_control", "tessellation_evaluation", ) and any(var.name == node.object.name for var in self.inputs): object_name = f"input.{node.object.name}" elif self.shader_type in ( "vertex", "geometry", "tessellation_control", "tessellation_evaluation", ) and any(var.name == node.object.name for var in self.outputs): object_name = f"output.{node.object.name}" else: object_name = node.object.name else: object_name = self.generate_expression(node.object) return f"{object_name}.{node.member}"
[docs] def generate_array_access(self, node): """Generate CrossGL code for an array access expression Args: node: ArrayAccessNode representing a GLSL array access Returns: str: The CrossGL array access expression """ array = self.generate_expression(node.array) index = self.generate_expression(node.index) return f"{array}[{index}]"
[docs] def convert_type(self, type_name): """Convert a GLSL type to its CrossGL equivalent Args: type_name: The GLSL type name Returns: str: The equivalent CrossGL type name """ return self.type_map.get(type_name, type_name)
[docs] def generate_variable_declaration(self, node): """Generate CrossGL code for a variable declaration Args: node: VariableNode representing a GLSL variable declaration Returns: str: The CrossGL variable declaration """ var_type = self.convert_type(node.vtype) var_name = node.name qualifiers = {str(q).lower() for q in getattr(node, "qualifiers", None) or []} prefix = ( "const " if getattr(node, "is_const", False) or "const" in qualifiers else "" ) array_suffix = "" if node.array_size is not None: array_size = self.generate_expression(node.array_size) array_suffix = f"[{array_size}]" if getattr(node, "value", None) is not None: value = self.generate_expression(node.value) return f"{prefix}{var_type} {var_name}{array_suffix} = {value}" return f"{prefix}{var_type} {var_name}{array_suffix}"
[docs] def generate_switch_statement(self, node): """Generate CrossGL code for a switch statement Args: node: SwitchNode representing a GLSL switch statement Returns: str: The CrossGL switch statement """ expression = self.generate_expression(node.expression) result = f"switch ({expression}) {{\n" # Generate case statements for case in node.cases: case_value = self.generate_expression(case.value) result += self.indent() + f"case {case_value}:\n" self.increase_indent() for statement in case.statements: result += self.indent() + self.generate_statement(statement) + "\n" self.decrease_indent() # Generate default case if present if node.default: result += self.indent() + "default:\n" self.increase_indent() for statement in node.default: result += self.indent() + self.generate_statement(statement) + "\n" self.decrease_indent() result += self.indent() + "}" return result