1#!/usr/bin/env python3 2# ex: set filetype=python: 3 4"""Generate code to handle XDR unions""" 5 6from jinja2 import Environment 7 8from generators import SourceGenerator 9from generators import create_jinja2_environment, get_jinja2_template 10 11from xdr_ast import _XdrBasic, _XdrUnion, _XdrVoid, _XdrString, get_header_name 12from xdr_ast import _XdrDeclaration, _XdrCaseSpec, public_apis, big_endian 13 14 15def emit_union_declaration(environment: Environment, node: _XdrUnion) -> None: 16 """Emit one declaration pair for an XDR union type""" 17 if node.name in public_apis: 18 template = get_jinja2_template(environment, "declaration", "close") 19 print(template.render(name=node.name)) 20 21 22def emit_union_switch_spec_definition( 23 environment: Environment, node: _XdrDeclaration 24) -> None: 25 """Emit a definition for an XDR union's discriminant""" 26 assert isinstance(node, _XdrBasic) 27 template = get_jinja2_template(environment, "definition", "switch_spec") 28 print( 29 template.render( 30 name=node.name, 31 type=node.spec.type_name, 32 classifier=node.spec.c_classifier, 33 ) 34 ) 35 36 37def emit_union_case_spec_definition( 38 environment: Environment, node: _XdrDeclaration 39) -> None: 40 """Emit a definition for an XDR union's case arm""" 41 if isinstance(node.arm, _XdrVoid): 42 return 43 if isinstance(node.arm, _XdrString): 44 type_name = "char *" 45 classifier = "" 46 else: 47 type_name = node.arm.spec.type_name 48 classifier = node.arm.spec.c_classifier 49 50 assert isinstance(node.arm, (_XdrBasic, _XdrString)) 51 template = get_jinja2_template(environment, "definition", "case_spec") 52 print( 53 template.render( 54 name=node.arm.name, 55 type=type_name, 56 classifier=classifier, 57 ) 58 ) 59 60 61def emit_union_definition(environment: Environment, node: _XdrUnion) -> None: 62 """Emit one XDR union definition""" 63 template = get_jinja2_template(environment, "definition", "open") 64 print(template.render(name=node.name)) 65 66 emit_union_switch_spec_definition(environment, node.discriminant) 67 68 for case in node.cases: 69 emit_union_case_spec_definition(environment, case) 70 71 if node.default is not None: 72 emit_union_case_spec_definition(environment, node.default) 73 74 template = get_jinja2_template(environment, "definition", "close") 75 print(template.render(name=node.name)) 76 77 78def emit_union_switch_spec_decoder( 79 environment: Environment, node: _XdrDeclaration 80) -> None: 81 """Emit a decoder for an XDR union's discriminant""" 82 assert isinstance(node, _XdrBasic) 83 template = get_jinja2_template(environment, "decoder", "switch_spec") 84 print(template.render(name=node.name, type=node.spec.type_name)) 85 86 87def emit_union_case_spec_decoder( 88 environment: Environment, node: _XdrCaseSpec, big_endian_discriminant: bool 89) -> None: 90 """Emit decoder functions for an XDR union's case arm""" 91 92 if isinstance(node.arm, _XdrVoid): 93 return 94 if isinstance(node.arm, _XdrString): 95 type_name = "char *" 96 classifier = "" 97 else: 98 type_name = node.arm.spec.type_name 99 classifier = node.arm.spec.c_classifier 100 101 if big_endian_discriminant: 102 template = get_jinja2_template(environment, "decoder", "case_spec_be") 103 else: 104 template = get_jinja2_template(environment, "decoder", "case_spec") 105 for case in node.values: 106 print(template.render(case=case)) 107 108 assert isinstance(node.arm, (_XdrBasic, _XdrString)) 109 template = get_jinja2_template(environment, "decoder", node.arm.template) 110 print( 111 template.render( 112 name=node.arm.name, 113 type=type_name, 114 classifier=classifier, 115 ) 116 ) 117 118 template = get_jinja2_template(environment, "decoder", "break") 119 print(template.render()) 120 121 122def emit_union_default_spec_decoder(environment: Environment, node: _XdrUnion) -> None: 123 """Emit a decoder function for an XDR union's default arm""" 124 default_case = node.default 125 126 # Avoid a gcc warning about a default case with boolean discriminant 127 if default_case is None and node.discriminant.spec.type_name == "bool": 128 return 129 130 template = get_jinja2_template(environment, "decoder", "default_spec") 131 print(template.render()) 132 133 if default_case is None or isinstance(default_case.arm, _XdrVoid): 134 template = get_jinja2_template(environment, "decoder", "break") 135 print(template.render()) 136 return 137 138 assert isinstance(default_case.arm, _XdrBasic) 139 template = get_jinja2_template(environment, "decoder", default_case.arm.template) 140 print( 141 template.render( 142 name=default_case.arm.name, 143 type=default_case.arm.spec.type_name, 144 classifier=default_case.arm.spec.c_classifier, 145 ) 146 ) 147 148 149def emit_union_decoder(environment: Environment, node: _XdrUnion) -> None: 150 """Emit one XDR union decoder""" 151 template = get_jinja2_template(environment, "decoder", "open") 152 print(template.render(name=node.name)) 153 154 emit_union_switch_spec_decoder(environment, node.discriminant) 155 156 for case in node.cases: 157 emit_union_case_spec_decoder( 158 environment, 159 case, 160 node.discriminant.spec.type_name in big_endian, 161 ) 162 163 emit_union_default_spec_decoder(environment, node) 164 165 template = get_jinja2_template(environment, "decoder", "close") 166 print(template.render()) 167 168 169def emit_union_switch_spec_encoder( 170 environment: Environment, node: _XdrDeclaration 171) -> None: 172 """Emit an encoder for an XDR union's discriminant""" 173 assert isinstance(node, _XdrBasic) 174 template = get_jinja2_template(environment, "encoder", "switch_spec") 175 print(template.render(name=node.name, type=node.spec.type_name)) 176 177 178def emit_union_case_spec_encoder( 179 environment: Environment, node: _XdrCaseSpec, big_endian_discriminant: bool 180) -> None: 181 """Emit encoder functions for an XDR union's case arm""" 182 183 if isinstance(node.arm, _XdrVoid): 184 return 185 if isinstance(node.arm, _XdrString): 186 type_name = "char *" 187 else: 188 type_name = node.arm.spec.type_name 189 if big_endian_discriminant: 190 template = get_jinja2_template(environment, "encoder", "case_spec_be") 191 else: 192 template = get_jinja2_template(environment, "encoder", "case_spec") 193 for case in node.values: 194 print(template.render(case=case)) 195 196 template = get_jinja2_template(environment, "encoder", node.arm.template) 197 print( 198 template.render( 199 name=node.arm.name, 200 type=type_name, 201 ) 202 ) 203 204 template = get_jinja2_template(environment, "encoder", "break") 205 print(template.render()) 206 207 208def emit_union_default_spec_encoder(environment: Environment, node: _XdrUnion) -> None: 209 """Emit an encoder function for an XDR union's default arm""" 210 default_case = node.default 211 212 # Avoid a gcc warning about a default case with boolean discriminant 213 if default_case is None and node.discriminant.spec.type_name == "bool": 214 return 215 216 template = get_jinja2_template(environment, "encoder", "default_spec") 217 print(template.render()) 218 219 if default_case is None or isinstance(default_case.arm, _XdrVoid): 220 template = get_jinja2_template(environment, "encoder", "break") 221 print(template.render()) 222 return 223 224 template = get_jinja2_template(environment, "encoder", default_case.arm.template) 225 print( 226 template.render( 227 name=default_case.arm.name, 228 type=default_case.arm.spec.type_name, 229 ) 230 ) 231 232 233def emit_union_encoder(environment, node: _XdrUnion) -> None: 234 """Emit one XDR union encoder""" 235 template = get_jinja2_template(environment, "encoder", "open") 236 print(template.render(name=node.name)) 237 238 emit_union_switch_spec_encoder(environment, node.discriminant) 239 240 for case in node.cases: 241 emit_union_case_spec_encoder( 242 environment, 243 case, 244 node.discriminant.spec.type_name in big_endian, 245 ) 246 247 emit_union_default_spec_encoder(environment, node) 248 249 template = get_jinja2_template(environment, "encoder", "close") 250 print(template.render()) 251 252 253def emit_union_maxsize(environment: Environment, node: _XdrUnion) -> None: 254 """Emit one maxsize macro for an XDR union type""" 255 macro_name = get_header_name().upper() + "_" + node.name + "_sz" 256 template = get_jinja2_template(environment, "maxsize", "union") 257 print( 258 template.render( 259 macro=macro_name, 260 width=" + ".join(node.symbolic_width()), 261 ) 262 ) 263 264 265class XdrUnionGenerator(SourceGenerator): 266 """Generate source code for XDR unions""" 267 268 def __init__(self, language: str, peer: str): 269 """Initialize an instance of this class""" 270 self.environment = create_jinja2_environment(language, "union") 271 self.peer = peer 272 273 def emit_declaration(self, node: _XdrUnion) -> None: 274 """Emit one declaration pair for an XDR union""" 275 emit_union_declaration(self.environment, node) 276 277 def emit_definition(self, node: _XdrUnion) -> None: 278 """Emit one definition for an XDR union""" 279 emit_union_definition(self.environment, node) 280 281 def emit_decoder(self, node: _XdrUnion) -> None: 282 """Emit one decoder function for an XDR union""" 283 emit_union_decoder(self.environment, node) 284 285 def emit_encoder(self, node: _XdrUnion) -> None: 286 """Emit one encoder function for an XDR union""" 287 emit_union_encoder(self.environment, node) 288 289 def emit_maxsize(self, node: _XdrUnion) -> None: 290 """Emit one maxsize macro for an XDR union""" 291 emit_union_maxsize(self.environment, node) 292