program.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. #!/usr/bin/env python3
  2. # ex: set filetype=python:
  3. """Generate code for an RPC program's procedures"""
  4. from jinja2 import Environment
  5. from generators import SourceGenerator, create_jinja2_environment, get_jinja2_template
  6. from xdr_ast import _RpcProgram, _RpcVersion, excluded_apis
  7. from xdr_ast import max_widths, get_header_name
  8. def emit_version_definitions(
  9. environment: Environment, program: str, version: _RpcVersion
  10. ) -> None:
  11. """Emit procedure numbers for each RPC version's procedures"""
  12. template = environment.get_template("definition/open.j2")
  13. print(template.render(program=program.upper()))
  14. template = environment.get_template("definition/procedure.j2")
  15. for procedure in version.procedures:
  16. if procedure.name not in excluded_apis:
  17. print(
  18. template.render(
  19. name=procedure.name,
  20. value=procedure.number,
  21. )
  22. )
  23. template = environment.get_template("definition/close.j2")
  24. print(template.render())
  25. def emit_version_declarations(
  26. environment: Environment, program: str, version: _RpcVersion
  27. ) -> None:
  28. """Emit declarations for each RPC version's procedures"""
  29. arguments = dict.fromkeys([])
  30. for procedure in version.procedures:
  31. if procedure.name not in excluded_apis:
  32. arguments[procedure.argument.type_name] = None
  33. if len(arguments) > 0:
  34. print("")
  35. template = environment.get_template("declaration/argument.j2")
  36. for argument in arguments:
  37. print(template.render(program=program, argument=argument))
  38. results = dict.fromkeys([])
  39. for procedure in version.procedures:
  40. if procedure.name not in excluded_apis:
  41. results[procedure.result.type_name] = None
  42. if len(results) > 0:
  43. print("")
  44. template = environment.get_template("declaration/result.j2")
  45. for result in results:
  46. print(template.render(program=program, result=result))
  47. def emit_version_argument_decoders(
  48. environment: Environment, program: str, version: _RpcVersion
  49. ) -> None:
  50. """Emit server argument decoders for each RPC version's procedures"""
  51. arguments = dict.fromkeys([])
  52. for procedure in version.procedures:
  53. if procedure.name not in excluded_apis:
  54. arguments[procedure.argument.type_name] = None
  55. template = environment.get_template("decoder/argument.j2")
  56. for argument in arguments:
  57. print(template.render(program=program, argument=argument))
  58. def emit_version_result_decoders(
  59. environment: Environment, program: str, version: _RpcVersion
  60. ) -> None:
  61. """Emit client result decoders for each RPC version's procedures"""
  62. results = dict.fromkeys([])
  63. for procedure in version.procedures:
  64. if procedure.name not in excluded_apis:
  65. results[procedure.result.type_name] = None
  66. template = environment.get_template("decoder/result.j2")
  67. for result in results:
  68. print(template.render(program=program, result=result))
  69. def emit_version_argument_encoders(
  70. environment: Environment, program: str, version: _RpcVersion
  71. ) -> None:
  72. """Emit client argument encoders for each RPC version's procedures"""
  73. arguments = dict.fromkeys([])
  74. for procedure in version.procedures:
  75. if procedure.name not in excluded_apis:
  76. arguments[procedure.argument.type_name] = None
  77. template = environment.get_template("encoder/argument.j2")
  78. for argument in arguments:
  79. print(template.render(program=program, argument=argument))
  80. def emit_version_result_encoders(
  81. environment: Environment, program: str, version: _RpcVersion
  82. ) -> None:
  83. """Emit server result encoders for each RPC version's procedures"""
  84. results = dict.fromkeys([])
  85. for procedure in version.procedures:
  86. if procedure.name not in excluded_apis:
  87. results[procedure.result.type_name] = None
  88. template = environment.get_template("encoder/result.j2")
  89. for result in results:
  90. print(template.render(program=program, result=result))
  91. class XdrProgramGenerator(SourceGenerator):
  92. """Generate source code for an RPC program's procedures"""
  93. def __init__(self, language: str, peer: str):
  94. """Initialize an instance of this class"""
  95. self.environment = create_jinja2_environment(language, "program")
  96. self.peer = peer
  97. def emit_definition(self, node: _RpcProgram) -> None:
  98. """Emit procedure numbers for each of an RPC programs's procedures"""
  99. raw_name = node.name
  100. program = raw_name.lower().removesuffix("_program").removesuffix("_prog")
  101. for version in node.versions:
  102. emit_version_definitions(self.environment, program, version)
  103. template = self.environment.get_template("definition/program.j2")
  104. print(template.render(name=raw_name, value=node.number))
  105. def emit_declaration(self, node: _RpcProgram) -> None:
  106. """Emit a declaration pair for each of an RPC programs's procedures"""
  107. raw_name = node.name
  108. program = raw_name.lower().removesuffix("_program").removesuffix("_prog")
  109. for version in node.versions:
  110. emit_version_declarations(self.environment, program, version)
  111. def emit_decoder(self, node: _RpcProgram) -> None:
  112. """Emit all decoder functions for an RPC program's procedures"""
  113. raw_name = node.name
  114. program = raw_name.lower().removesuffix("_program").removesuffix("_prog")
  115. match self.peer:
  116. case "server":
  117. for version in node.versions:
  118. emit_version_argument_decoders(
  119. self.environment, program, version,
  120. )
  121. case "client":
  122. for version in node.versions:
  123. emit_version_result_decoders(
  124. self.environment, program, version,
  125. )
  126. def emit_encoder(self, node: _RpcProgram) -> None:
  127. """Emit all encoder functions for an RPC program's procedures"""
  128. raw_name = node.name
  129. program = raw_name.lower().removesuffix("_program").removesuffix("_prog")
  130. match self.peer:
  131. case "server":
  132. for version in node.versions:
  133. emit_version_result_encoders(
  134. self.environment, program, version,
  135. )
  136. case "client":
  137. for version in node.versions:
  138. emit_version_argument_encoders(
  139. self.environment, program, version,
  140. )
  141. def emit_maxsize(self, node: _RpcProgram) -> None:
  142. """Emit maxsize macro for maximum RPC argument size"""
  143. header = get_header_name().upper()
  144. # Find the largest argument across all versions
  145. max_arg_width = 0
  146. max_arg_name = None
  147. for version in node.versions:
  148. for procedure in version.procedures:
  149. if procedure.name in excluded_apis:
  150. continue
  151. arg_name = procedure.argument.type_name
  152. if arg_name == "void":
  153. continue
  154. if arg_name not in max_widths:
  155. continue
  156. if max_widths[arg_name] > max_arg_width:
  157. max_arg_width = max_widths[arg_name]
  158. max_arg_name = arg_name
  159. if max_arg_name is None:
  160. return
  161. macro_name = header + "_MAX_ARGS_SZ"
  162. template = get_jinja2_template(self.environment, "maxsize", "max_args")
  163. print(
  164. template.render(
  165. macro=macro_name,
  166. width=header + "_" + max_arg_name + "_sz",
  167. )
  168. )