blob: 37f9655c83fec52bee5da9903e44020850f0070c [file]
#!/usr/bin/env python3
# ex: set filetype=python:
"""Generate code for an RPC program's procedures"""
from jinja2 import Environment
from generators import SourceGenerator, create_jinja2_environment, get_jinja2_template
from xdr_ast import _RpcProgram, _RpcVersion, excluded_apis
from xdr_ast import max_widths, get_header_name
def emit_version_definitions(
environment: Environment, program: str, version: _RpcVersion
) -> None:
"""Emit procedure numbers for each RPC version's procedures"""
template = environment.get_template("definition/open.j2")
print(template.render(program=program.upper()))
template = environment.get_template("definition/procedure.j2")
for procedure in version.procedures:
if procedure.name not in excluded_apis:
print(
template.render(
name=procedure.name,
value=procedure.number,
)
)
template = environment.get_template("definition/close.j2")
print(template.render())
def emit_version_declarations(
environment: Environment, program: str, version: _RpcVersion
) -> None:
"""Emit declarations for each RPC version's procedures"""
arguments = dict.fromkeys([])
for procedure in version.procedures:
if procedure.name not in excluded_apis:
if procedure.argument.type_name == "void":
continue
arguments[procedure.argument.type_name] = None
if len(arguments) > 0:
print("")
template = environment.get_template("declaration/argument.j2")
for argument in arguments:
print(template.render(program=program, argument=argument))
results = dict.fromkeys([])
for procedure in version.procedures:
if procedure.name not in excluded_apis:
if procedure.result.type_name == "void":
continue
results[procedure.result.type_name] = None
if len(results) > 0:
print("")
template = environment.get_template("declaration/result.j2")
for result in results:
print(template.render(program=program, result=result))
def emit_version_argument_decoders(
environment: Environment, program: str, version: _RpcVersion
) -> None:
"""Emit server argument decoders for each RPC version's procedures"""
arguments = dict.fromkeys([])
for procedure in version.procedures:
if procedure.name not in excluded_apis:
if procedure.argument.type_name == "void":
continue
arguments[procedure.argument.type_name] = None
template = environment.get_template("decoder/argument.j2")
for argument in arguments:
print(template.render(program=program, argument=argument))
def emit_version_result_decoders(
environment: Environment, program: str, version: _RpcVersion
) -> None:
"""Emit client result decoders for each RPC version's procedures"""
results = dict.fromkeys([])
for procedure in version.procedures:
if procedure.name not in excluded_apis:
results[procedure.result.type_name] = None
template = environment.get_template("decoder/result.j2")
for result in results:
print(template.render(program=program, result=result))
def emit_version_argument_encoders(
environment: Environment, program: str, version: _RpcVersion
) -> None:
"""Emit client argument encoders for each RPC version's procedures"""
arguments = dict.fromkeys([])
for procedure in version.procedures:
if procedure.name not in excluded_apis:
arguments[procedure.argument.type_name] = None
template = environment.get_template("encoder/argument.j2")
for argument in arguments:
print(template.render(program=program, argument=argument))
def emit_version_result_encoders(
environment: Environment, program: str, version: _RpcVersion
) -> None:
"""Emit server result encoders for each RPC version's procedures"""
results = dict.fromkeys([])
for procedure in version.procedures:
if procedure.name not in excluded_apis:
if procedure.result.type_name == "void":
continue
results[procedure.result.type_name] = None
template = environment.get_template("encoder/result.j2")
for result in results:
print(template.render(program=program, result=result))
class XdrProgramGenerator(SourceGenerator):
"""Generate source code for an RPC program's procedures"""
def __init__(self, language: str, peer: str):
"""Initialize an instance of this class"""
self.environment = create_jinja2_environment(language, "program")
self.peer = peer
def emit_definition(self, node: _RpcProgram) -> None:
"""Emit procedure numbers for each of an RPC programs's procedures"""
raw_name = node.name
program = raw_name.lower().removesuffix("_program").removesuffix("_prog")
for version in node.versions:
emit_version_definitions(self.environment, program, version)
template = self.environment.get_template("definition/program.j2")
print(template.render(name=raw_name, value=node.number))
def emit_declaration(self, node: _RpcProgram) -> None:
"""Emit a declaration pair for each of an RPC programs's procedures"""
raw_name = node.name
program = raw_name.lower().removesuffix("_program").removesuffix("_prog")
for version in node.versions:
emit_version_declarations(self.environment, program, version)
def emit_decoder(self, node: _RpcProgram) -> None:
"""Emit all decoder functions for an RPC program's procedures"""
raw_name = node.name
program = raw_name.lower().removesuffix("_program").removesuffix("_prog")
match self.peer:
case "server":
for version in node.versions:
emit_version_argument_decoders(
self.environment, program, version,
)
case "client":
for version in node.versions:
emit_version_result_decoders(
self.environment, program, version,
)
def emit_encoder(self, node: _RpcProgram) -> None:
"""Emit all encoder functions for an RPC program's procedures"""
raw_name = node.name
program = raw_name.lower().removesuffix("_program").removesuffix("_prog")
match self.peer:
case "server":
for version in node.versions:
emit_version_result_encoders(
self.environment, program, version,
)
case "client":
for version in node.versions:
emit_version_argument_encoders(
self.environment, program, version,
)
def emit_maxsize(self, node: _RpcProgram) -> None:
"""Emit maxsize macro for maximum RPC argument size"""
header = get_header_name().upper()
# Find the largest argument across all versions
max_arg_width = 0
max_arg_name = None
for version in node.versions:
for procedure in version.procedures:
if procedure.name in excluded_apis:
continue
arg_name = procedure.argument.type_name
if arg_name == "void":
continue
if arg_name not in max_widths:
continue
if max_widths[arg_name] > max_arg_width:
max_arg_width = max_widths[arg_name]
max_arg_name = arg_name
if max_arg_name is None:
return
macro_name = header + "_MAX_ARGS_SZ"
template = get_jinja2_template(self.environment, "maxsize", "max_args")
print(
template.render(
macro=macro_name,
width=header + "_" + max_arg_name + "_sz",
)
)