Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
ae4c5bb4bThis file is pinned to the selected release. Download the original to open it in your design tool.
# Copyright 2025 DeepMind Technologies Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Utility functions for code generation."""
import os
from introspect import ast_nodes
def write_to_file(filepath: str, content: str) -> None:
"""Writes content to a file."""
output_dir = os.path.dirname(filepath)
try:
if output_dir:
os.makedirs(output_dir, exist_ok=True)
with open(filepath, "w") as f:
chars = f.write(content)
print(f"wrote {chars} characters to file '{filepath}'")
except IOError as e:
print(f"Error writing to output file: {filepath} - {e}")
def decapitalize(input_string: str) -> str:
"""Lowercases the first letter of a string."""
return input_string[:1].lower() + input_string[1:]
def capitalize(input_string: str) -> str:
"""Uppercases the first letter of a string."""
return input_string[:1].upper() + input_string[1:]
def replace_lines_containing_marker(
lines: list[str],
marker: str,
content: list[str],
) -> list[str]:
"""Replaces lines containing a specific marker with new content."""
for i, line in enumerate(lines):
if marker in line:
indent = line[: len(line) - len(line.lstrip(" "))]
replacement_lines = []
for text in content:
if text.strip():
# Prepend indent to ensure the first replacement line matches the
# indentation of the marker and also ensure that text containing
# newlines is also indented correctly.
# TODO(matijak): This is working around an upstream problem, we should
# make it a precondition that content elements do not contain newlines
# and fix callers to ensure that.
replacement_lines.append(
indent + text.replace("\n", f"\n{indent}") + "\n"
)
return lines[:i] + replacement_lines + lines[i + 1 :]
return lines
def get_pointer_return_inner_value_type(
func: ast_nodes.FunctionDecl,
) -> ast_nodes.ValueType | None:
if not isinstance(func.return_type, ast_nodes.PointerType):
return None
if not isinstance(func.return_type.inner_type, ast_nodes.ValueType):
return None
return func.return_type.inner_type
def get_inner_value_type(
param: ast_nodes.FunctionParameterDecl,
) -> ast_nodes.ValueType | None:
if not isinstance(param.type, (ast_nodes.PointerType, ast_nodes.ArrayType)):
return None
if not isinstance(param.type.inner_type, ast_nodes.ValueType):
return None
return param.type.inner_type
def should_be_wrapped(func: ast_nodes.FunctionDecl) -> bool:
"""Checks if a MuJoCo function needs a wrapper function."""
if get_pointer_return_inner_value_type(func):
return True
for param in func.parameters:
if get_inner_value_type(param):
return True
return False
def wrapped_struct_name(c_struct_name: str) -> str:
"""Returns the name of the struct wrapping the given C struct."""
return capitalize(c_struct_name)
def wrapped_function_name(func: ast_nodes.FunctionDecl) -> str:
"""Returns the name of the function wrapping the given C function.
Hard-coded wrappers in the template need changing if the implementation of the
wrapped name is changed
Args:
func: The FunctionDecl of function to wrap.
"""
if should_be_wrapped(func):
return f"{func.name}_wrapper"
return func.name