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 13from xdr_ast import public_apis, big_endian, pages_members, pages_member_maxsize 14 15 16def emit_union_declaration(environment: Environment, node: _XdrUnion) -> None: 17 """Emit one declaration pair for an XDR union type""" 18 if node.name in public_apis: 19 template = get_jinja2_template(environment, "declaration", "close") 20 print(template.render(name=node.name)) 21 22 23def emit_union_switch_spec_definition( 24 environment: Environment, node: _XdrDeclaration 25) -> None: 26 """Emit a definition for an XDR union's discriminant""" 27 assert isinstance(node, _XdrBasic) 28 template = get_jinja2_template(environment, "definition", "switch_spec") 29 print( 30 template.render( 31 name=node.name, 32 type=node.spec.type_name, 33 classifier=node.spec.c_classifier, 34 ) 35 ) 36 37 38def emit_union_case_spec_definition( 39 environment: Environment, node: _XdrDeclaration 40) -> None: 41 """Emit a definition for an XDR union's case arm""" 42 if isinstance(node.arm, _XdrVoid): 43 return 44 if isinstance(node.arm, _XdrString): 45 type_name = "char *" 46 classifier = "" 47 else: 48 type_name = node.arm.spec.type_name 49 classifier = node.arm.spec.c_classifier 50 51 assert isinstance(node.arm, (_XdrBasic, _XdrString)) 52 template = get_jinja2_template(environment, "definition", "case_spec") 53 print( 54 template.render( 55 name=node.arm.name, 56 type=type_name, 57 classifier=classifier, 58 ) 59 ) 60 61 62def emit_union_definition(environment: Environment, node: _XdrUnion) -> None: 63 """Emit one XDR union definition""" 64 template = get_jinja2_template(environment, "definition", "open") 65 print(template.render(name=node.name)) 66 67 emit_union_switch_spec_definition(environment, node.discriminant) 68 69 for case in node.cases: 70 emit_union_case_spec_definition(environment, case) 71 72 if node.default is not None: 73 emit_union_case_spec_definition(environment, node.default) 74 75 template = get_jinja2_template(environment, "definition", "close") 76 print(template.render(name=node.name)) 77 78 79def emit_union_switch_spec_decoder( 80 environment: Environment, node: _XdrDeclaration 81) -> None: 82 """Emit a decoder for an XDR union's discriminant""" 83 assert isinstance(node, _XdrBasic) 84 template = get_jinja2_template(environment, "decoder", "switch_spec") 85 print(template.render(name=node.name, type=node.spec.type_name)) 86 87 88def emit_union_arm_decoder( 89 environment: Environment, node: _XdrCaseSpec 90) -> None: 91 """Emit decoder for an XDR union's arm (data only, no case/break)""" 92 93 if isinstance(node.arm, _XdrVoid): 94 return 95 if isinstance(node.arm, _XdrString): 96 type_name = "char *" 97 classifier = "" 98 else: 99 type_name = node.arm.spec.type_name 100 classifier = node.arm.spec.c_classifier 101 102 assert isinstance(node.arm, (_XdrBasic, _XdrString)) 103 template = get_jinja2_template(environment, "decoder", node.arm.template) 104 print( 105 template.render( 106 name=node.arm.name, 107 type=type_name, 108 classifier=classifier, 109 ) 110 ) 111 112 113def emit_union_case_spec_decoder( 114 environment: Environment, node: _XdrCaseSpec, big_endian_discriminant: bool 115) -> None: 116 """Emit decoder functions for an XDR union's case arm""" 117 118 if isinstance(node.arm, _XdrVoid): 119 return 120 if isinstance(node.arm, _XdrString): 121 type_name = "char *" 122 classifier = "" 123 else: 124 type_name = node.arm.spec.type_name 125 classifier = node.arm.spec.c_classifier 126 127 if big_endian_discriminant: 128 template = get_jinja2_template(environment, "decoder", "case_spec_be") 129 else: 130 template = get_jinja2_template(environment, "decoder", "case_spec") 131 for case in node.values: 132 print(template.render(case=case)) 133 134 assert isinstance(node.arm, (_XdrBasic, _XdrString)) 135 template = get_jinja2_template(environment, "decoder", node.arm.template) 136 print( 137 template.render( 138 name=node.arm.name, 139 type=type_name, 140 classifier=classifier, 141 ) 142 ) 143 144 template = get_jinja2_template(environment, "decoder", "break") 145 print(template.render()) 146 147 148def emit_union_default_spec_decoder(environment: Environment, node: _XdrUnion) -> None: 149 """Emit a decoder function for an XDR union's default arm""" 150 default_case = node.default 151 152 # Avoid a gcc warning about a default case with boolean discriminant 153 if default_case is None and node.discriminant.spec.type_name == "bool": 154 return 155 156 template = get_jinja2_template(environment, "decoder", "default_spec") 157 print(template.render()) 158 159 if default_case is None or isinstance(default_case.arm, _XdrVoid): 160 template = get_jinja2_template(environment, "decoder", "break") 161 print(template.render()) 162 return 163 164 assert isinstance(default_case.arm, _XdrBasic) 165 template = get_jinja2_template(environment, "decoder", default_case.arm.template) 166 print( 167 template.render( 168 name=default_case.arm.name, 169 type=default_case.arm.spec.type_name, 170 classifier=default_case.arm.spec.c_classifier, 171 ) 172 ) 173 174 175def emit_union_decoder(environment: Environment, node: _XdrUnion) -> None: 176 """Emit one XDR union decoder""" 177 template = get_jinja2_template(environment, "decoder", "open") 178 print(template.render(name=node.name)) 179 180 # For boolean discriminants, use if statement instead of switch 181 if node.discriminant.spec.type_name == "bool": 182 template = get_jinja2_template(environment, "decoder", "bool_spec") 183 print(template.render(name=node.discriminant.name, type=node.discriminant.spec.type_name)) 184 185 # Find and emit the TRUE case 186 for case in node.cases: 187 if case.values and case.values[0] == "TRUE": 188 emit_union_arm_decoder(environment, case) 189 break 190 191 template = get_jinja2_template(environment, "decoder", "close") 192 print(template.render()) 193 else: 194 emit_union_switch_spec_decoder(environment, node.discriminant) 195 196 for case in node.cases: 197 emit_union_case_spec_decoder( 198 environment, 199 case, 200 node.discriminant.spec.type_name in big_endian, 201 ) 202 203 emit_union_default_spec_decoder(environment, node) 204 205 template = get_jinja2_template(environment, "decoder", "close") 206 print(template.render()) 207 208 209def emit_union_switch_spec_encoder( 210 environment: Environment, node: _XdrDeclaration 211) -> None: 212 """Emit an encoder for an XDR union's discriminant""" 213 assert isinstance(node, _XdrBasic) 214 template = get_jinja2_template(environment, "encoder", "switch_spec") 215 print(template.render(name=node.name, type=node.spec.type_name)) 216 217 218def emit_union_pages_arm_encoder( 219 environment: Environment, union_name: str, arm: _XdrDeclaration, peer: str 220) -> bool: 221 """Emit a page-resident encoder for a "pragma pages" union arm 222 223 Return True when the arm is pages-marked and was emitted here, so 224 the caller skips the ordinary arm encoder. 225 """ 226 if (union_name, arm.name) not in pages_members: 227 return False 228 if peer != "server": 229 raise NotImplementedError( 230 "pragma pages is server-side only; " 231 + peer 232 + " generation is not yet supported" 233 ) 234 template = get_jinja2_template(environment, "encoder", "pages_opaque") 235 print( 236 template.render( 237 name=arm.name, 238 maxsize=pages_member_maxsize(arm), 239 ) 240 ) 241 return True 242 243 244def emit_union_arm_encoder( 245 environment: Environment, node: _XdrCaseSpec, union_name: str, peer: str 246) -> None: 247 """Emit encoder for an XDR union's arm (data only, no case/break)""" 248 249 if isinstance(node.arm, _XdrVoid): 250 return 251 if emit_union_pages_arm_encoder(environment, union_name, node.arm, peer): 252 return 253 if isinstance(node.arm, _XdrString): 254 type_name = "char *" 255 else: 256 type_name = node.arm.spec.type_name 257 258 assert isinstance(node.arm, (_XdrBasic, _XdrString)) 259 template = get_jinja2_template(environment, "encoder", node.arm.template) 260 print( 261 template.render( 262 name=node.arm.name, 263 type=type_name, 264 ) 265 ) 266 267 268def emit_union_case_spec_encoder( 269 environment: Environment, 270 node: _XdrCaseSpec, 271 big_endian_discriminant: bool, 272 union_name: str, 273 peer: str, 274) -> None: 275 """Emit encoder functions for an XDR union's case arm""" 276 277 if isinstance(node.arm, _XdrVoid): 278 return 279 if isinstance(node.arm, _XdrString): 280 type_name = "char *" 281 else: 282 type_name = node.arm.spec.type_name 283 if big_endian_discriminant: 284 template = get_jinja2_template(environment, "encoder", "case_spec_be") 285 else: 286 template = get_jinja2_template(environment, "encoder", "case_spec") 287 for case in node.values: 288 print(template.render(case=case)) 289 290 if not emit_union_pages_arm_encoder(environment, union_name, node.arm, peer): 291 template = get_jinja2_template(environment, "encoder", node.arm.template) 292 print( 293 template.render( 294 name=node.arm.name, 295 type=type_name, 296 ) 297 ) 298 299 template = get_jinja2_template(environment, "encoder", "break") 300 print(template.render()) 301 302 303def emit_union_default_spec_encoder( 304 environment: Environment, node: _XdrUnion, peer: str 305) -> None: 306 """Emit an encoder function for an XDR union's default arm""" 307 default_case = node.default 308 309 # Avoid a gcc warning about a default case with boolean discriminant 310 if default_case is None and node.discriminant.spec.type_name == "bool": 311 return 312 313 template = get_jinja2_template(environment, "encoder", "default_spec") 314 print(template.render()) 315 316 if default_case is None or isinstance(default_case.arm, _XdrVoid): 317 template = get_jinja2_template(environment, "encoder", "break") 318 print(template.render()) 319 return 320 321 if emit_union_pages_arm_encoder(environment, node.name, default_case.arm, peer): 322 return 323 324 template = get_jinja2_template(environment, "encoder", default_case.arm.template) 325 print( 326 template.render( 327 name=default_case.arm.name, 328 type=default_case.arm.spec.type_name, 329 ) 330 ) 331 332 333def emit_union_encoder(environment, node: _XdrUnion, peer: str) -> None: 334 """Emit one XDR union encoder""" 335 template = get_jinja2_template(environment, "encoder", "open") 336 print(template.render(name=node.name)) 337 338 # For boolean discriminants, use if statement instead of switch 339 if node.discriminant.spec.type_name == "bool": 340 template = get_jinja2_template(environment, "encoder", "bool_spec") 341 print(template.render(name=node.discriminant.name, type=node.discriminant.spec.type_name)) 342 343 # Find and emit the TRUE case 344 for case in node.cases: 345 if case.values and case.values[0] == "TRUE": 346 emit_union_arm_encoder(environment, case, node.name, peer) 347 break 348 349 template = get_jinja2_template(environment, "encoder", "close") 350 print(template.render()) 351 else: 352 emit_union_switch_spec_encoder(environment, node.discriminant) 353 354 for case in node.cases: 355 emit_union_case_spec_encoder( 356 environment, 357 case, 358 node.discriminant.spec.type_name in big_endian, 359 node.name, 360 peer, 361 ) 362 363 emit_union_default_spec_encoder(environment, node, peer) 364 365 template = get_jinja2_template(environment, "encoder", "close") 366 print(template.render()) 367 368 369def emit_union_maxsize(environment: Environment, node: _XdrUnion) -> None: 370 """Emit one maxsize macro for an XDR union type""" 371 macro_name = get_header_name().upper() + "_" + node.name + "_sz" 372 template = get_jinja2_template(environment, "maxsize", "union") 373 print( 374 template.render( 375 macro=macro_name, 376 width=" + ".join(node.symbolic_width()), 377 ) 378 ) 379 380 381class XdrUnionGenerator(SourceGenerator): 382 """Generate source code for XDR unions""" 383 384 def __init__(self, language: str, peer: str): 385 """Initialize an instance of this class""" 386 self.environment = create_jinja2_environment(language, "union") 387 self.peer = peer 388 389 def emit_declaration(self, node: _XdrUnion) -> None: 390 """Emit one declaration pair for an XDR union""" 391 emit_union_declaration(self.environment, node) 392 393 def emit_definition(self, node: _XdrUnion) -> None: 394 """Emit one definition for an XDR union""" 395 emit_union_definition(self.environment, node) 396 397 def emit_decoder(self, node: _XdrUnion) -> None: 398 """Emit one decoder function for an XDR union""" 399 emit_union_decoder(self.environment, node) 400 401 def emit_encoder(self, node: _XdrUnion) -> None: 402 """Emit one encoder function for an XDR union""" 403 emit_union_encoder(self.environment, node, self.peer) 404 405 def emit_maxsize(self, node: _XdrUnion) -> None: 406 """Emit one maxsize macro for an XDR union""" 407 emit_union_maxsize(self.environment, node) 408