Version 2.1.2: Python bindings, OBJ assets support, bugfixes.

PiperOrigin-RevId: 434731612
Change-Id: I0cfda3e7a3d1c72036764986efc252ffa1b8c6b0
This commit is contained in:
Saran Tunyasuvunakool
2022-03-15 14:04:44 +00:00
parent 175d25cd9f
commit 3577e2cf8b
304 changed files with 23906 additions and 1013 deletions
+220
View File
@@ -0,0 +1,220 @@
# Copyright 2022 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.
# ==============================================================================
"""Classes that roughly correspond to Clang AST node types."""
import collections
import dataclasses
import re
from typing import Dict, Optional, Sequence, Tuple, Union
# We are relying on Clang to do the actual source parsing and are only doing
# a little bit of extra parsing of function parameter type declarations here.
# These patterns are here for sanity checking rather than actual parsing.
VALID_TYPE_NAME_PATTERN = re.compile('[A-Za-z_][A-Za-z0-9_]*')
C_INVALID_TYPE_NAMES = frozenset([
'auto', 'break', 'case', 'const', 'continue', 'default', 'do', 'else',
'enum', 'extern', 'for', 'goto', 'if', 'inline', 'register', 'restrict',
'return', 'sizeof', 'static', 'struct', 'switch', 'typedef', 'union',
'volatile', 'while', '_Alignas', '_Atomic', '_Generic', '_Imaginary',
'_Noreturn', '_Static_assert', '_Thread_local', '__attribute__', '_Pragma'])
def _is_valid_integral_type(type_str: str):
"""Checks if a string is a valid integral type."""
parts = re.split(r'\s+', type_str)
counter = collections.defaultdict(lambda: 0)
wildcard_counter = 0
for part in parts:
if part in ('signed', 'unsigned', 'short', 'long', 'int', 'char'):
counter[part] += 1
elif VALID_TYPE_NAME_PATTERN.fullmatch(part):
# a non-keyword can be a typedef for int
wildcard_counter += 1
else:
return False
if (counter['signed'] + counter['unsigned'] > 1 or
counter['short'] > 1 or counter['long'] > 2 or
(counter['short'] and counter['long']) or
((counter['short'] or counter['long']) and counter['char']) or
counter['char'] + counter['int'] + wildcard_counter > 1):
return False
else:
return True
@dataclasses.dataclass
class ValueType:
"""Represents a C type that is neither a pointer type nor an array type."""
name: str
is_const: bool = False
is_volatile: bool = False
def __init__(self, name: str, is_const: bool = False,
is_volatile: bool = False):
is_valid_type_name = (
VALID_TYPE_NAME_PATTERN.fullmatch(name) or
_is_valid_integral_type(name)) and name not in C_INVALID_TYPE_NAMES
if not is_valid_type_name:
raise ValueError(f'{name!r} is not a valid value type name')
self.name = name
self.is_const = is_const
self.is_volatile = is_volatile
def decl(self, name_or_decl: Optional[str] = None) -> str:
parts = []
if self.is_const:
parts.append('const')
if self.is_volatile:
parts.append('volatile')
parts.append(self.name)
if name_or_decl:
parts.append(name_or_decl)
return ' '.join(parts)
def __str__(self):
return self.decl()
@dataclasses.dataclass
class ArrayType:
"""Represents a C array type."""
inner_type: Union[ValueType, 'PointerType']
extents: Tuple[int]
def __init__(self, inner_type: Union[ValueType, 'PointerType'],
extents: Sequence[int]):
self.inner_type = inner_type
self.extents = tuple(extents)
@property
def _extents_str(self) -> str:
return ''.join(f'[{n}]' for n in self.extents)
def decl(self, name_or_decl: Optional[str] = None) -> str:
name_or_decl = name_or_decl or ''
return self.inner_type.decl(f'{name_or_decl}{self._extents_str}')
def __str__(self):
return self.decl()
@dataclasses.dataclass
class PointerType:
"""Represents a C pointer type."""
inner_type: Union[ValueType, ArrayType, 'PointerType']
is_const: bool = False
is_volatile: bool = False
is_restrict: bool = False
def decl(self, name_or_decl: Optional[str] = None) -> str:
"""Creates a string that declares an object of this type."""
parts = ['*']
if self.is_const:
parts.append('const')
if self.is_volatile:
parts.append('volatile')
if self.is_restrict:
parts.append('restrict')
if name_or_decl:
parts.append(name_or_decl)
ptr_decl = ' '.join(parts)
if isinstance(self.inner_type, ArrayType):
ptr_decl = f'({ptr_decl})'
return self.inner_type.decl(ptr_decl)
def __str__(self):
return self.decl()
@dataclasses.dataclass
class FunctionParameterDecl:
"""Represents a parameter in a function declaration.
Note that according to the C language rule, a function parameter of array
type undergoes array-to-pointer decay, and therefore appears as a pointer
parameter in an actual C AST. We retain the arrayness of a parameter here
since the array's extents are informative.
"""
name: str
type: Union[ValueType, ArrayType, PointerType]
def __str__(self):
return self.type.decl(self.name)
@property
def decltype(self) -> str:
return self.type.decl()
@dataclasses.dataclass
class FunctionDecl:
"""Represents a function declaration."""
name: str
return_type: Union[ValueType, ArrayType, PointerType]
parameters: Tuple[FunctionParameterDecl]
doc: str
def __init__(self, name: str,
return_type: Union[ValueType, ArrayType, PointerType],
parameters: Sequence[FunctionParameterDecl],
doc: str):
self.name = name
self.return_type = return_type
self.parameters = tuple(parameters)
self.doc = doc
def __str__(self):
param_str = ', '.join(str(p) for p in self.parameters)
return f'{self.return_type} {self.name}({param_str})'
@property
def decltype(self) -> str:
param_str = ', '.join(str(p.decltype) for p in self.parameters)
return f'{self.return_type} ({param_str})'
class _EnumDeclValues(Dict[str, int]):
"""A dict with modified stringified representation.
The __repr__ method of this class adds a trailing comma to the list of values.
This is done as a hint for code formatters to place one item per line when
the stringified OrderedDict is used in generated Python code.
"""
def __repr__(self):
out = super().__repr__()
if self:
out = re.sub(r'\(\[(.+)\]\)\Z', r'([\1,])', out)
return re.sub(r'\A_EnumDeclValues', 'dict', out)
@dataclasses.dataclass
class EnumDecl:
"""Represents an enum declaration."""
name: str
declname: str
values: Dict[str, int]
def __init__(self, name: str, declname: str, values: Dict[str, int]):
self.name = name
self.declname = declname
self.values = _EnumDeclValues(values)
+123
View File
@@ -0,0 +1,123 @@
# Copyright 2022 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.
# ==============================================================================
"""Tests for ast_nodes.py."""
from absl.testing import absltest
from google3.third_party.mujoco.introspect import ast_nodes
class AstNodesTest(absltest.TestCase):
def test_value_type(self):
value_type = ast_nodes.ValueType('int')
self.assertEqual(str(value_type), 'int')
self.assertEqual(value_type.decl('var'), 'int var')
const_value_type = ast_nodes.ValueType('double', is_const=True)
self.assertEqual(str(const_value_type), 'const double')
self.assertEqual(const_value_type.decl('var2'), 'const double var2')
def test_pointer_type(self):
pointer_type = ast_nodes.PointerType(ast_nodes.ValueType('int'))
self.assertEqual(str(pointer_type), 'int *')
self.assertEqual(pointer_type.decl('var'), 'int * var')
const_pointer_type = ast_nodes.PointerType(
ast_nodes.ValueType('double'), is_const=True)
self.assertEqual(str(const_pointer_type), 'double * const')
self.assertEqual(const_pointer_type.decl('var2'), 'double * const var2')
pointer_to_const_type = ast_nodes.PointerType(
ast_nodes.ValueType('float', is_const=True))
self.assertEqual(str(pointer_to_const_type), 'const float *')
self.assertEqual(pointer_to_const_type.decl('var3'), 'const float * var3')
restrict_volatile_pointer_to_const_type = ast_nodes.PointerType(
ast_nodes.ValueType('char', is_const=True),
is_volatile=True, is_restrict=True)
self.assertEqual(str(restrict_volatile_pointer_to_const_type),
'const char * volatile restrict')
self.assertEqual(
restrict_volatile_pointer_to_const_type.decl('var4'),
'const char * volatile restrict var4')
pointer_to_array_type = ast_nodes.PointerType(
ast_nodes.ArrayType(ast_nodes.ValueType('long'), (3,)))
self.assertEqual(str(pointer_to_array_type), 'long (*)[3]')
self.assertEqual(pointer_to_array_type.decl('var5'), 'long (* var5)[3]')
const_pointer_to_array_type = ast_nodes.PointerType(
ast_nodes.ArrayType(ast_nodes.ValueType('unsigned int'), (4,)),
is_const=True)
self.assertEqual(
str(const_pointer_to_array_type), 'unsigned int (* const)[4]')
self.assertEqual(
const_pointer_to_array_type.decl('var6'),
'unsigned int (* const var6)[4]')
def test_array_type(self):
array_type = ast_nodes.ArrayType(ast_nodes.ValueType('int'), (4,))
self.assertEqual(str(array_type), 'int [4]')
self.assertEqual(array_type.decl('var'), 'int var[4]')
array_2d_type = ast_nodes.ArrayType(
ast_nodes.ValueType('double', is_const=True), (2, 3))
self.assertEqual(str(array_2d_type), 'const double [2][3]')
self.assertEqual(array_2d_type.decl('var2'), 'const double var2[2][3]')
array_to_pointer_type = ast_nodes.ArrayType(
ast_nodes.PointerType(ast_nodes.ValueType('char', is_const=True)), (5,))
self.assertEqual(str(array_to_pointer_type), 'const char * [5]')
self.assertEqual(array_to_pointer_type.decl('var3'), 'const char * var3[5]')
array_to_const_pointer_type = ast_nodes.ArrayType(
ast_nodes.PointerType(ast_nodes.ValueType('float'), is_const=True),
(7,))
self.assertEqual(str(array_to_const_pointer_type), 'float * const [7]')
self.assertEqual(
array_to_const_pointer_type.decl('var4'), 'float * const var4[7]')
def test_complex_type(self):
complex_type = ast_nodes.ArrayType(
extents=[9],
inner_type=ast_nodes.PointerType(
ast_nodes.PointerType(
is_const=True,
inner_type=ast_nodes.ArrayType(
extents=[7],
inner_type=ast_nodes.PointerType(
is_const=True,
inner_type=ast_nodes.PointerType(
ast_nodes.ArrayType(
extents=(3, 4),
inner_type=ast_nodes.ValueType(
'unsigned int', is_const=True)
)
)
)
)
)
)
)
self.assertEqual(str(complex_type),
'const unsigned int (* * const (* const * [9])[7])[3][4]')
self.assertEqual(
complex_type.decl('var'),
'const unsigned int (* * const (* const * var[9])[7])[3][4]')
if __name__ == '__main__':
absltest.main()
+149
View File
@@ -0,0 +1,149 @@
# Copyright 2022 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 for formatting AST node as Python code."""
import contextlib
import dataclasses
from typing import Any, Iterable, Mapping, Sequence
INDENT_WIDTH = 4
MAX_LINE_WIDTH = 80
SIMPLE_TYPES = frozenset([int, float, str, bool, bytes, type(None)])
def format_as_python_code(obj: Any) -> str:
"""Formats an AST node object as well-indented Python code."""
formatter = _Formatter()
formatter.add(obj)
return str(formatter)
def _is_all_simple(seq: Iterable[Any]) -> bool:
return all(type(obj) in SIMPLE_TYPES for obj in seq)
class _Formatter:
"""A helper for pretty-printing AST nodes as Python code."""
def __init__(self):
self._line_prefix = ''
self._lines = []
self._add_to_last_line = False
@contextlib.contextmanager
def _indent(self, width: int = INDENT_WIDTH):
self._line_prefix += ' ' * width
yield
self._line_prefix = self._line_prefix[:-width]
@contextlib.contextmanager
def _append_at_end(self, s):
yield
self._lines[-1] += s
def _add_line(self, line: str, no_break: bool = False):
if self._add_to_last_line:
self._lines[-1] += line
else:
self._lines.append(self._line_prefix + line)
self._add_to_last_line = no_break
def _add_dict(self, obj: Mapping[Any, Any]):
"""Adds a dict to the formatted output."""
self._add_line('dict([')
with self._indent():
for k, v in obj.items():
# Try to fit everything into a single line first.
if _is_all_simple((k, v)):
single_line = f'({k!r}, {v!r}),'
if len(self._line_prefix) + len(single_line) <= MAX_LINE_WIDTH:
self._add_line(single_line)
continue
self._add_line(f"('{k}',")
with self._append_at_end('),'):
with self._indent(1):
self.add(v)
self._add_line('])')
def _add_dataclass(self, obj: Any):
"""Adds a dataclass object to the formatted output."""
# Filter out default values.
kv_pairs = []
for k in dataclasses.fields(obj):
v = getattr(obj, k.name)
if v != k.default:
kv_pairs.append((k, v))
# Try to fit everything into a single line first.
if _is_all_simple(v for _, v in kv_pairs):
single_line = ', '.join(f'{k.name}={v!r}' for k, v in kv_pairs)
single_line = f'{obj.__class__.__name__}({single_line})'
if len(self._line_prefix) + len(single_line) <= MAX_LINE_WIDTH:
self._add_line(single_line)
return
self._add_line(obj.__class__.__name__ + '(')
with self._indent():
for k, v in kv_pairs:
self._add_line(k.name + '=', no_break=True)
with self._append_at_end(','):
self.add(v)
self._add_line(')')
def _add_sequence(self, obj: Sequence[Any]) -> None:
"""Adds a sequence to the formatted output."""
default_str = repr(obj)
open_token, close_token = default_str[0], default_str[-1]
# Try to fit everything into a single line first.
if _is_all_simple(obj):
single_line = (
f"{open_token}{', '.join(repr(o) for o in obj)}{close_token}")
if close_token == ')' and len(obj) == 1:
single_line = f'{single_line[:-1]},)'
if len(self._line_prefix) + len(single_line) <= MAX_LINE_WIDTH:
self._add_line(single_line)
return
self._add_line(open_token)
with self._indent():
for v in obj:
with self._append_at_end(','):
self.add(v)
self._add_line(close_token)
def add(self, obj: Any) -> None:
"""Adds an object to the formatted output."""
if _is_all_simple((obj,)):
self._add_line(repr(obj))
elif dataclasses.is_dataclass(obj):
self._add_dataclass(obj)
elif isinstance(obj, Mapping):
self._add_dict(obj)
elif isinstance(obj, Sequence):
self._add_sequence(obj)
else:
raise NotImplementedError
def __str__(self):
lines = []
for line in self._lines:
if len(line) > MAX_LINE_WIDTH:
lines.append(f'{line} # pylint: disable=line-too-long')
else:
lines.append(line)
return '\n'.join(lines)
+126
View File
@@ -0,0 +1,126 @@
# Copyright 2022 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.
# ==============================================================================
"""Generates enums.py.
The JSON input can be generated via:
clang -Xclang -ast-dump=json -fsyntax-only -fparse-all-comments mujoco.h
"""
import json
from typing import Any, Mapping, Sequence
from absl import app
from absl import flags
from google3.third_party.mujoco.introspect import ast_nodes
from google3.third_party.mujoco.introspect.codegen import formatter
FLAGS = flags.FLAGS
flags.DEFINE_string(
'json_path', None,
'Path to the JSON file representing the Clang AST for mujoco.h')
ClangJsonNode = Mapping[str, Any]
def traverse(node, visitor):
visitor.visit(node)
children = node.get('inner', [])
for child in children:
traverse(child, visitor)
class MjEnumVisitor:
"""A Clang AST JSON node visitor for MuJoCo API enum declarations."""
def __init__(self):
self._enums = {}
self._typedefs = {}
def _make_enum(self, node: ClangJsonNode) -> ast_nodes.EnumDecl:
"""Makes a EnumDecl from a Clang AST EnumDecl node."""
name = f"enum {node['name']}"
values = []
for child in node['inner']:
child_kind = child.get('kind')
if child_kind == 'EnumConstantDecl':
next_idx = values[-1][1] + 1 if values else 0
value = int(child['inner'][0].get('value', next_idx))
values.append((child['name'], value))
return ast_nodes.EnumDecl(name=name, declname=name, values=dict(values))
def visit(self, node: ClangJsonNode) -> None:
if (node.get('kind') == 'EnumDecl' and
node.get('name', '').startswith('mj')):
enum_decl = self._make_enum(node)
self._enums[enum_decl.name] = enum_decl
elif (node.get('kind') == 'TypedefDecl' and
node['type']['qualType'].startswith('enum mj')):
enum = self._enums[node['type']['qualType']]
self._typedefs[node['name']] = ast_nodes.EnumDecl(
name=node['name'], declname=enum.declname, values=dict(enum.values))
@property
def enums(self) -> Mapping[str, ast_nodes.EnumDecl]:
return self._enums
@property
def typedefs(self) -> Mapping[str, ast_nodes.EnumDecl]:
return self._typedefs
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
with open(FLAGS.json_path, 'r', encoding='utf-8') as f:
root = json.load(f)
visitor = MjEnumVisitor()
traverse(root, visitor)
enums_str = formatter.format_as_python_code(visitor.typedefs)
print(f'''
# Copyright 2022 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.
# ==============================================================================
"""Provides information about MuJoCo API enums.
DO NOT EDIT. THIS FILE IS AUTOMATICALLY GENERATED.
"""
from typing import Mapping
from google3.third_party.mujoco.introspect.ast_nodes import EnumDecl
ENUMS: Mapping[str, EnumDecl] = {enums_str}
'''.strip()) # `print` adds a trailing newline
if __name__ == '__main__':
app.run(main)
+159
View File
@@ -0,0 +1,159 @@
# Copyright 2022 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.
# ==============================================================================
"""Generates functions.py.
The JSON input can be generated via:
clang -Xclang -ast-dump=json -fsyntax-only -fparse-all-comments mujoco.h
"""
import json
from typing import Any, Mapping, Sequence
from absl import app
from absl import flags
from google3.third_party.mujoco.introspect import ast_nodes
from google3.third_party.mujoco.introspect import type_parsing
from google3.third_party.mujoco.introspect.codegen import formatter
FLAGS = flags.FLAGS
flags.DEFINE_string('header_path', None, 'Path to the original mujoco.h')
flags.DEFINE_string(
'json_path', None,
'Path to the JSON file representing the Clang AST for mujoco.h')
ClangJsonNode = Mapping[str, Any]
def traverse(node, visitor):
visitor.visit(node)
children = node.get('inner', [])
for child in children:
traverse(child, visitor)
class MjFunctionVisitor:
"""A Clang AST JSON node visitor for MuJoCo API function declarations."""
def __init__(self, raw_header):
self._raw_header = raw_header
self._functions = {}
def _make_function(self, node: ClangJsonNode) -> ast_nodes.FunctionDecl:
"""Makes a FunctionDecl from a Clang AST FunctionDecl node."""
name = node['name']
return_type = type_parsing.parse_function_return_type(
node['type']['qualType'])
parameters = []
comments = []
for child in node['inner']:
child_kind = child.get('kind')
if child_kind == 'ParmVarDecl':
parameters.append(self._make_parameter(child))
if child_kind == 'FullComment':
comments.append(self._make_comment(child))
comment = ' '.join(comments).strip()
return ast_nodes.FunctionDecl(
name=name, return_type=return_type, parameters=parameters, doc=comment)
def _make_parameter(
self, node: ClangJsonNode) -> ast_nodes.FunctionParameterDecl:
"""Makes a ParameterDecl from a Clang AST ParmVarDecl node."""
name = node['name']
type_name = node['type']['qualType']
# For a pointer parameters, look up in the original header to see if
# n array extent was declared there.
if type_name.endswith('*'):
decl_begin = node['range']['begin']['offset']
decl_end = node['range']['end']['offset'] + node['range']['end']['tokLen']
decl = self._raw_header[decl_begin:decl_end]
name_begin = node['loc']['offset'] - decl_begin
name_end = name_begin + node['loc']['tokLen']
type_name = decl[:name_begin] + decl[name_end:]
return ast_nodes.FunctionParameterDecl(
name=name, type=type_parsing.parse_type(type_name))
def _make_comment(self, node: ClangJsonNode) -> str:
"""Makes a comment string from a Clang AST FullComment node."""
kind = node.get('kind')
if kind == 'TextComment':
return node['text'].replace('\N{NO-BREAK SPACE}', '&nbsp;')
else:
strings = []
for child in node['inner']:
strings.append(self._make_comment(child))
return ''.join(strings)
def visit(self, node: ClangJsonNode) -> None:
if (node.get('kind') == 'FunctionDecl' and
node.get('name', '').startswith('mj')):
func_decl = self._make_function(node)
self._functions[func_decl.name] = func_decl
@property
def functions(self) -> Mapping[str, ast_nodes.FunctionDecl]:
return self._functions
def main(argv: Sequence[str]) -> None:
if len(argv) > 1:
raise app.UsageError('Too many command-line arguments.')
with open(FLAGS.json_path, 'r', encoding='utf-8') as f:
root = json.load(f)
with open(FLAGS.header_path, 'r') as f:
visitor = MjFunctionVisitor(f.read())
traverse(root, visitor)
functions_str = formatter.format_as_python_code(visitor.functions)
print(f'''
# Copyright 2022 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.
# ==============================================================================
"""Provides information about MuJoCo API functions.
DO NOT EDIT. THIS FILE IS AUTOMATICALLY GENERATED.
"""
from typing import Mapping
from google3.third_party.mujoco.introspect.ast_nodes import ArrayType
from google3.third_party.mujoco.introspect.ast_nodes import FunctionDecl
from google3.third_party.mujoco.introspect.ast_nodes import FunctionParameterDecl
from google3.third_party.mujoco.introspect.ast_nodes import PointerType
from google3.third_party.mujoco.introspect.ast_nodes import ValueType
FUNCTIONS: Mapping[str, FunctionDecl] = {functions_str}
'''.strip()) # `print` adds a trailing newline
if __name__ == '__main__':
app.run(main)
+633
View File
@@ -0,0 +1,633 @@
# Copyright 2022 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.
# ==============================================================================
"""Provides information about MuJoCo API enums.
DO NOT EDIT. THIS FILE IS AUTOMATICALLY GENERATED.
"""
from typing import Mapping
from .ast_nodes import EnumDecl
ENUMS: Mapping[str, EnumDecl] = dict([
('mjtDisableBit',
EnumDecl(
name='mjtDisableBit',
declname='enum mjtDisableBit_',
values=dict([
('mjDSBL_CONSTRAINT', 1),
('mjDSBL_EQUALITY', 2),
('mjDSBL_FRICTIONLOSS', 4),
('mjDSBL_LIMIT', 8),
('mjDSBL_CONTACT', 16),
('mjDSBL_PASSIVE', 32),
('mjDSBL_GRAVITY', 64),
('mjDSBL_CLAMPCTRL', 128),
('mjDSBL_WARMSTART', 256),
('mjDSBL_FILTERPARENT', 512),
('mjDSBL_ACTUATION', 1024),
('mjDSBL_REFSAFE', 2048),
('mjNDISABLE', 12),
]),
)),
('mjtEnableBit',
EnumDecl(
name='mjtEnableBit',
declname='enum mjtEnableBit_',
values=dict([
('mjENBL_OVERRIDE', 1),
('mjENBL_ENERGY', 2),
('mjENBL_FWDINV', 4),
('mjENBL_SENSORNOISE', 8),
('mjNENABLE', 4),
]),
)),
('mjtJoint',
EnumDecl(
name='mjtJoint',
declname='enum mjtJoint_',
values=dict([
('mjJNT_FREE', 0),
('mjJNT_BALL', 1),
('mjJNT_SLIDE', 2),
('mjJNT_HINGE', 3),
]),
)),
('mjtGeom',
EnumDecl(
name='mjtGeom',
declname='enum mjtGeom_',
values=dict([
('mjGEOM_PLANE', 0),
('mjGEOM_HFIELD', 1),
('mjGEOM_SPHERE', 2),
('mjGEOM_CAPSULE', 3),
('mjGEOM_ELLIPSOID', 4),
('mjGEOM_CYLINDER', 5),
('mjGEOM_BOX', 6),
('mjGEOM_MESH', 7),
('mjNGEOMTYPES', 8),
('mjGEOM_ARROW', 100),
('mjGEOM_ARROW1', 101),
('mjGEOM_ARROW2', 102),
('mjGEOM_LINE', 103),
('mjGEOM_SKIN', 104),
('mjGEOM_LABEL', 105),
('mjGEOM_NONE', 1001),
]),
)),
('mjtCamLight',
EnumDecl(
name='mjtCamLight',
declname='enum mjtCamLight_',
values=dict([
('mjCAMLIGHT_FIXED', 0),
('mjCAMLIGHT_TRACK', 1),
('mjCAMLIGHT_TRACKCOM', 2),
('mjCAMLIGHT_TARGETBODY', 3),
('mjCAMLIGHT_TARGETBODYCOM', 4),
]),
)),
('mjtTexture',
EnumDecl(
name='mjtTexture',
declname='enum mjtTexture_',
values=dict([
('mjTEXTURE_2D', 0),
('mjTEXTURE_CUBE', 1),
('mjTEXTURE_SKYBOX', 2),
]),
)),
('mjtIntegrator',
EnumDecl(
name='mjtIntegrator',
declname='enum mjtIntegrator_',
values=dict([
('mjINT_EULER', 0),
('mjINT_RK4', 1),
]),
)),
('mjtCollision',
EnumDecl(
name='mjtCollision',
declname='enum mjtCollision_',
values=dict([
('mjCOL_ALL', 0),
('mjCOL_PAIR', 1),
('mjCOL_DYNAMIC', 2),
]),
)),
('mjtCone',
EnumDecl(
name='mjtCone',
declname='enum mjtCone_',
values=dict([
('mjCONE_PYRAMIDAL', 0),
('mjCONE_ELLIPTIC', 1),
]),
)),
('mjtJacobian',
EnumDecl(
name='mjtJacobian',
declname='enum mjtJacobian_',
values=dict([
('mjJAC_DENSE', 0),
('mjJAC_SPARSE', 1),
('mjJAC_AUTO', 2),
]),
)),
('mjtSolver',
EnumDecl(
name='mjtSolver',
declname='enum mjtSolver_',
values=dict([
('mjSOL_PGS', 0),
('mjSOL_CG', 1),
('mjSOL_NEWTON', 2),
]),
)),
('mjtEq',
EnumDecl(
name='mjtEq',
declname='enum mjtEq_',
values=dict([
('mjEQ_CONNECT', 0),
('mjEQ_WELD', 1),
('mjEQ_JOINT', 2),
('mjEQ_TENDON', 3),
('mjEQ_DISTANCE', 4),
]),
)),
('mjtWrap',
EnumDecl(
name='mjtWrap',
declname='enum mjtWrap_',
values=dict([
('mjWRAP_NONE', 0),
('mjWRAP_JOINT', 1),
('mjWRAP_PULLEY', 2),
('mjWRAP_SITE', 3),
('mjWRAP_SPHERE', 4),
('mjWRAP_CYLINDER', 5),
]),
)),
('mjtTrn',
EnumDecl(
name='mjtTrn',
declname='enum mjtTrn_',
values=dict([
('mjTRN_JOINT', 0),
('mjTRN_JOINTINPARENT', 1),
('mjTRN_SLIDERCRANK', 2),
('mjTRN_TENDON', 3),
('mjTRN_SITE', 4),
('mjTRN_UNDEFINED', 1000),
]),
)),
('mjtDyn',
EnumDecl(
name='mjtDyn',
declname='enum mjtDyn_',
values=dict([
('mjDYN_NONE', 0),
('mjDYN_INTEGRATOR', 1),
('mjDYN_FILTER', 2),
('mjDYN_MUSCLE', 3),
('mjDYN_USER', 4),
]),
)),
('mjtGain',
EnumDecl(
name='mjtGain',
declname='enum mjtGain_',
values=dict([
('mjGAIN_FIXED', 0),
('mjGAIN_MUSCLE', 1),
('mjGAIN_USER', 2),
]),
)),
('mjtBias',
EnumDecl(
name='mjtBias',
declname='enum mjtBias_',
values=dict([
('mjBIAS_NONE', 0),
('mjBIAS_AFFINE', 1),
('mjBIAS_MUSCLE', 2),
('mjBIAS_USER', 3),
]),
)),
('mjtObj',
EnumDecl(
name='mjtObj',
declname='enum mjtObj_',
values=dict([
('mjOBJ_UNKNOWN', 0),
('mjOBJ_BODY', 1),
('mjOBJ_XBODY', 2),
('mjOBJ_JOINT', 3),
('mjOBJ_DOF', 4),
('mjOBJ_GEOM', 5),
('mjOBJ_SITE', 6),
('mjOBJ_CAMERA', 7),
('mjOBJ_LIGHT', 8),
('mjOBJ_MESH', 9),
('mjOBJ_SKIN', 10),
('mjOBJ_HFIELD', 11),
('mjOBJ_TEXTURE', 12),
('mjOBJ_MATERIAL', 13),
('mjOBJ_PAIR', 14),
('mjOBJ_EXCLUDE', 15),
('mjOBJ_EQUALITY', 16),
('mjOBJ_TENDON', 17),
('mjOBJ_ACTUATOR', 18),
('mjOBJ_SENSOR', 19),
('mjOBJ_NUMERIC', 20),
('mjOBJ_TEXT', 21),
('mjOBJ_TUPLE', 22),
('mjOBJ_KEY', 23),
]),
)),
('mjtConstraint',
EnumDecl(
name='mjtConstraint',
declname='enum mjtConstraint_',
values=dict([
('mjCNSTR_EQUALITY', 0),
('mjCNSTR_FRICTION_DOF', 1),
('mjCNSTR_FRICTION_TENDON', 2),
('mjCNSTR_LIMIT_JOINT', 3),
('mjCNSTR_LIMIT_TENDON', 4),
('mjCNSTR_CONTACT_FRICTIONLESS', 5),
('mjCNSTR_CONTACT_PYRAMIDAL', 6),
('mjCNSTR_CONTACT_ELLIPTIC', 7),
]),
)),
('mjtConstraintState',
EnumDecl(
name='mjtConstraintState',
declname='enum mjtConstraintState_',
values=dict([
('mjCNSTRSTATE_SATISFIED', 0),
('mjCNSTRSTATE_QUADRATIC', 1),
('mjCNSTRSTATE_LINEARNEG', 2),
('mjCNSTRSTATE_LINEARPOS', 3),
('mjCNSTRSTATE_CONE', 4),
]),
)),
('mjtSensor',
EnumDecl(
name='mjtSensor',
declname='enum mjtSensor_',
values=dict([
('mjSENS_TOUCH', 0),
('mjSENS_ACCELEROMETER', 1),
('mjSENS_VELOCIMETER', 2),
('mjSENS_GYRO', 3),
('mjSENS_FORCE', 4),
('mjSENS_TORQUE', 5),
('mjSENS_MAGNETOMETER', 6),
('mjSENS_RANGEFINDER', 7),
('mjSENS_JOINTPOS', 8),
('mjSENS_JOINTVEL', 9),
('mjSENS_TENDONPOS', 10),
('mjSENS_TENDONVEL', 11),
('mjSENS_ACTUATORPOS', 12),
('mjSENS_ACTUATORVEL', 13),
('mjSENS_ACTUATORFRC', 14),
('mjSENS_BALLQUAT', 15),
('mjSENS_BALLANGVEL', 16),
('mjSENS_JOINTLIMITPOS', 17),
('mjSENS_JOINTLIMITVEL', 18),
('mjSENS_JOINTLIMITFRC', 19),
('mjSENS_TENDONLIMITPOS', 20),
('mjSENS_TENDONLIMITVEL', 21),
('mjSENS_TENDONLIMITFRC', 22),
('mjSENS_FRAMEPOS', 23),
('mjSENS_FRAMEQUAT', 24),
('mjSENS_FRAMEXAXIS', 25),
('mjSENS_FRAMEYAXIS', 26),
('mjSENS_FRAMEZAXIS', 27),
('mjSENS_FRAMELINVEL', 28),
('mjSENS_FRAMEANGVEL', 29),
('mjSENS_FRAMELINACC', 30),
('mjSENS_FRAMEANGACC', 31),
('mjSENS_SUBTREECOM', 32),
('mjSENS_SUBTREELINVEL', 33),
('mjSENS_SUBTREEANGMOM', 34),
('mjSENS_USER', 35),
]),
)),
('mjtStage',
EnumDecl(
name='mjtStage',
declname='enum mjtStage_',
values=dict([
('mjSTAGE_NONE', 0),
('mjSTAGE_POS', 1),
('mjSTAGE_VEL', 2),
('mjSTAGE_ACC', 3),
]),
)),
('mjtDataType',
EnumDecl(
name='mjtDataType',
declname='enum mjtDataType_',
values=dict([
('mjDATATYPE_REAL', 0),
('mjDATATYPE_POSITIVE', 1),
('mjDATATYPE_AXIS', 2),
('mjDATATYPE_QUATERNION', 3),
]),
)),
('mjtLRMode',
EnumDecl(
name='mjtLRMode',
declname='enum mjtLRMode_',
values=dict([
('mjLRMODE_NONE', 0),
('mjLRMODE_MUSCLE', 1),
('mjLRMODE_MUSCLEUSER', 2),
('mjLRMODE_ALL', 3),
]),
)),
('mjtWarning',
EnumDecl(
name='mjtWarning',
declname='enum mjtWarning_',
values=dict([
('mjWARN_INERTIA', 0),
('mjWARN_CONTACTFULL', 1),
('mjWARN_CNSTRFULL', 2),
('mjWARN_VGEOMFULL', 3),
('mjWARN_BADQPOS', 4),
('mjWARN_BADQVEL', 5),
('mjWARN_BADQACC', 6),
('mjWARN_BADCTRL', 7),
('mjNWARNING', 8),
]),
)),
('mjtTimer',
EnumDecl(
name='mjtTimer',
declname='enum mjtTimer_',
values=dict([
('mjTIMER_STEP', 0),
('mjTIMER_FORWARD', 1),
('mjTIMER_INVERSE', 2),
('mjTIMER_POSITION', 3),
('mjTIMER_VELOCITY', 4),
('mjTIMER_ACTUATION', 5),
('mjTIMER_ACCELERATION', 6),
('mjTIMER_CONSTRAINT', 7),
('mjTIMER_POS_KINEMATICS', 8),
('mjTIMER_POS_INERTIA', 9),
('mjTIMER_POS_COLLISION', 10),
('mjTIMER_POS_MAKE', 11),
('mjTIMER_POS_PROJECT', 12),
('mjNTIMER', 13),
]),
)),
('mjtGridPos',
EnumDecl(
name='mjtGridPos',
declname='enum mjtGridPos_',
values=dict([
('mjGRID_TOPLEFT', 0),
('mjGRID_TOPRIGHT', 1),
('mjGRID_BOTTOMLEFT', 2),
('mjGRID_BOTTOMRIGHT', 3),
]),
)),
('mjtFramebuffer',
EnumDecl(
name='mjtFramebuffer',
declname='enum mjtFramebuffer_',
values=dict([
('mjFB_WINDOW', 0),
('mjFB_OFFSCREEN', 1),
]),
)),
('mjtFontScale',
EnumDecl(
name='mjtFontScale',
declname='enum mjtFontScale_',
values=dict([
('mjFONTSCALE_50', 50),
('mjFONTSCALE_100', 100),
('mjFONTSCALE_150', 150),
('mjFONTSCALE_200', 200),
('mjFONTSCALE_250', 250),
('mjFONTSCALE_300', 300),
]),
)),
('mjtFont',
EnumDecl(
name='mjtFont',
declname='enum mjtFont_',
values=dict([
('mjFONT_NORMAL', 0),
('mjFONT_SHADOW', 1),
('mjFONT_BIG', 2),
]),
)),
('mjtButton',
EnumDecl(
name='mjtButton',
declname='enum mjtButton_',
values=dict([
('mjBUTTON_NONE', 0),
('mjBUTTON_LEFT', 1),
('mjBUTTON_RIGHT', 2),
('mjBUTTON_MIDDLE', 3),
]),
)),
('mjtEvent',
EnumDecl(
name='mjtEvent',
declname='enum mjtEvent_',
values=dict([
('mjEVENT_NONE', 0),
('mjEVENT_MOVE', 1),
('mjEVENT_PRESS', 2),
('mjEVENT_RELEASE', 3),
('mjEVENT_SCROLL', 4),
('mjEVENT_KEY', 5),
('mjEVENT_RESIZE', 6),
]),
)),
('mjtItem',
EnumDecl(
name='mjtItem',
declname='enum mjtItem_',
values=dict([
('mjITEM_END', -2),
('mjITEM_SECTION', -1),
('mjITEM_SEPARATOR', 0),
('mjITEM_STATIC', 1),
('mjITEM_BUTTON', 2),
('mjITEM_CHECKINT', 3),
('mjITEM_CHECKBYTE', 4),
('mjITEM_RADIO', 5),
('mjITEM_RADIOLINE', 6),
('mjITEM_SELECT', 7),
('mjITEM_SLIDERINT', 8),
('mjITEM_SLIDERNUM', 9),
('mjITEM_EDITINT', 10),
('mjITEM_EDITNUM', 11),
('mjITEM_EDITTXT', 12),
('mjNITEM', 13),
]),
)),
('mjtCatBit',
EnumDecl(
name='mjtCatBit',
declname='enum mjtCatBit_',
values=dict([
('mjCAT_STATIC', 1),
('mjCAT_DYNAMIC', 2),
('mjCAT_DECOR', 4),
('mjCAT_ALL', 7),
]),
)),
('mjtMouse',
EnumDecl(
name='mjtMouse',
declname='enum mjtMouse_',
values=dict([
('mjMOUSE_NONE', 0),
('mjMOUSE_ROTATE_V', 1),
('mjMOUSE_ROTATE_H', 2),
('mjMOUSE_MOVE_V', 3),
('mjMOUSE_MOVE_H', 4),
('mjMOUSE_ZOOM', 5),
('mjMOUSE_SELECT', 6),
]),
)),
('mjtPertBit',
EnumDecl(
name='mjtPertBit',
declname='enum mjtPertBit_',
values=dict([
('mjPERT_TRANSLATE', 1),
('mjPERT_ROTATE', 2),
]),
)),
('mjtCamera',
EnumDecl(
name='mjtCamera',
declname='enum mjtCamera_',
values=dict([
('mjCAMERA_FREE', 0),
('mjCAMERA_TRACKING', 1),
('mjCAMERA_FIXED', 2),
('mjCAMERA_USER', 3),
]),
)),
('mjtLabel',
EnumDecl(
name='mjtLabel',
declname='enum mjtLabel_',
values=dict([
('mjLABEL_NONE', 0),
('mjLABEL_BODY', 1),
('mjLABEL_JOINT', 2),
('mjLABEL_GEOM', 3),
('mjLABEL_SITE', 4),
('mjLABEL_CAMERA', 5),
('mjLABEL_LIGHT', 6),
('mjLABEL_TENDON', 7),
('mjLABEL_ACTUATOR', 8),
('mjLABEL_CONSTRAINT', 9),
('mjLABEL_SKIN', 10),
('mjLABEL_SELECTION', 11),
('mjLABEL_SELPNT', 12),
('mjLABEL_CONTACTFORCE', 13),
('mjNLABEL', 14),
]),
)),
('mjtFrame',
EnumDecl(
name='mjtFrame',
declname='enum mjtFrame_',
values=dict([
('mjFRAME_NONE', 0),
('mjFRAME_BODY', 1),
('mjFRAME_GEOM', 2),
('mjFRAME_SITE', 3),
('mjFRAME_CAMERA', 4),
('mjFRAME_LIGHT', 5),
('mjFRAME_WORLD', 6),
('mjNFRAME', 7),
]),
)),
('mjtVisFlag',
EnumDecl(
name='mjtVisFlag',
declname='enum mjtVisFlag_',
values=dict([
('mjVIS_CONVEXHULL', 0),
('mjVIS_TEXTURE', 1),
('mjVIS_JOINT', 2),
('mjVIS_ACTUATOR', 3),
('mjVIS_CAMERA', 4),
('mjVIS_LIGHT', 5),
('mjVIS_TENDON', 6),
('mjVIS_RANGEFINDER', 7),
('mjVIS_CONSTRAINT', 8),
('mjVIS_INERTIA', 9),
('mjVIS_SCLINERTIA', 10),
('mjVIS_PERTFORCE', 11),
('mjVIS_PERTOBJ', 12),
('mjVIS_CONTACTPOINT', 13),
('mjVIS_CONTACTFORCE', 14),
('mjVIS_CONTACTSPLIT', 15),
('mjVIS_TRANSPARENT', 16),
('mjVIS_AUTOCONNECT', 17),
('mjVIS_COM', 18),
('mjVIS_SELECT', 19),
('mjVIS_STATIC', 20),
('mjVIS_SKIN', 21),
('mjNVISFLAG', 22),
]),
)),
('mjtRndFlag',
EnumDecl(
name='mjtRndFlag',
declname='enum mjtRndFlag_',
values=dict([
('mjRND_SHADOW', 0),
('mjRND_WIREFRAME', 1),
('mjRND_REFLECTION', 2),
('mjRND_ADDITIVE', 3),
('mjRND_SKYBOX', 4),
('mjRND_FOG', 5),
('mjRND_HAZE', 6),
('mjRND_SEGMENT', 7),
('mjRND_IDCOLOR', 8),
('mjNRNDFLAG', 9),
]),
)),
('mjtStereo',
EnumDecl(
name='mjtStereo',
declname='enum mjtStereo_',
values=dict([
('mjSTEREO_NONE', 0),
('mjSTEREO_QUADBUFFERED', 1),
('mjSTEREO_SIDEBYSIDE', 2),
]),
)),
])
+66
View File
@@ -0,0 +1,66 @@
# Copyright 2022 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.
# ==============================================================================
"""Tests for enums.py."""
from absl.testing import absltest
from google3.third_party.mujoco.introspect import enums
class EnumsTest(absltest.TestCase):
# "simple" enum that just starts at zero and increment by one.
def test_mjtJoint(self): # pylint: disable=invalid-name
enum_decl = enums.ENUMS['mjtJoint']
self.assertEqual(enum_decl.name, 'mjtJoint')
self.assertEqual(enum_decl.declname, 'enum mjtJoint_')
self.assertEqual(
tuple(enum_decl.values.items()), (('mjJNT_FREE', 0),
('mjJNT_BALL', 1),
('mjJNT_SLIDE', 2),
('mjJNT_HINGE', 3)))
# all values explicitly specified
def test_mjtEnableBit(self): # pylint: disable=invalid-name
enum_decl = enums.ENUMS['mjtEnableBit']
self.assertEqual(enum_decl.name, 'mjtEnableBit')
self.assertEqual(enum_decl.declname, 'enum mjtEnableBit_')
self.assertEqual(
tuple(enum_decl.values.items()), (('mjENBL_OVERRIDE', 1<<0),
('mjENBL_ENERGY', 1<<1),
('mjENBL_FWDINV', 1<<2),
('mjENBL_SENSORNOISE', 1<<3),
('mjNENABLE', 4)))
# values mostly increment by one with occasional overrides
def test_mjtGeom(self): # pylint: disable=invalid-name
enum_decl = enums.ENUMS['mjtGeom']
self.assertEqual(enum_decl.name, 'mjtGeom')
self.assertEqual(enum_decl.declname, 'enum mjtGeom_')
self.assertEqual(enum_decl.values['mjGEOM_PLANE'], 0)
self.assertEqual(enum_decl.values['mjGEOM_HFIELD'], 1)
self.assertEqual(enum_decl.values['mjGEOM_SPHERE'], 2)
# Skip a few...
self.assertEqual(enum_decl.values['mjGEOM_ARROW'], 100)
self.assertEqual(enum_decl.values['mjGEOM_ARROW1'], 101)
self.assertEqual(enum_decl.values['mjGEOM_ARROW2'], 102)
# Skip a few...
self.assertEqual(enum_decl.values['mjGEOM_NONE'], 1001)
if __name__ == '__main__':
absltest.main()
File diff suppressed because it is too large Load Diff
+68
View File
@@ -0,0 +1,68 @@
# Copyright 2022 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.
# ==============================================================================
"""Tests for functions.py."""
from absl.testing import absltest
from google3.third_party.mujoco.introspect import ast_nodes
from google3.third_party.mujoco.introspect import functions
from google3.third_party.mujoco.introspect import type_parsing
class FunctionsTest(absltest.TestCase):
def test_mj_copyData(self): # pylint: disable=invalid-name
func_decl = functions.FUNCTIONS['mj_copyData']
self.assertEqual(func_decl.name, 'mj_copyData')
self.assertEqual(func_decl.return_type, type_parsing.parse_type('mjData*'))
self.assertEqual(
func_decl.parameters,
(ast_nodes.FunctionParameterDecl(
name='dest', type=type_parsing.parse_type('mjData*')),
ast_nodes.FunctionParameterDecl(
name='m', type=type_parsing.parse_type('const mjModel*')),
ast_nodes.FunctionParameterDecl(
name='src', type=type_parsing.parse_type('const mjData*'))))
self.assertEqual(
func_decl.doc, 'Copy mjData. '
'm is only required to contain the size fields from MJMODEL_INTS.')
def test_mju_transformSpatial(self): # pylint: disable=invalid-name
func_decl = functions.FUNCTIONS['mju_transformSpatial']
self.assertEqual(func_decl.name, 'mju_transformSpatial')
self.assertEqual(func_decl.return_type, type_parsing.parse_type('void'))
self.assertEqual(
func_decl.parameters,
(ast_nodes.FunctionParameterDecl(
name='res', type=type_parsing.parse_type('mjtNum[6]')),
ast_nodes.FunctionParameterDecl(
name='vec', type=type_parsing.parse_type('const mjtNum[6]')),
ast_nodes.FunctionParameterDecl(
name='flg_force', type=type_parsing.parse_type('int')),
ast_nodes.FunctionParameterDecl(
name='newpos', type=type_parsing.parse_type('const mjtNum[3]')),
ast_nodes.FunctionParameterDecl(
name='oldpos', type=type_parsing.parse_type('const mjtNum[3]')),
ast_nodes.FunctionParameterDecl(
name='rotnew2old',
type=type_parsing.parse_type('const mjtNum[9]'))))
self.assertEqual(
func_decl.doc, 'Coordinate transform of 6D motion or force vector in ' +
'rotation:translation format. rotnew2old is 3-by-3, ' +
'NULL means no rotation; flg_force specifies force or motion type.')
if __name__ == '__main__':
absltest.main()
+148
View File
@@ -0,0 +1,148 @@
# Copyright 2022 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.
# ==============================================================================
"""Functions for parsing C type declarations."""
import collections
import re
from typing import Mapping, MutableSequence, Optional, Sequence, Tuple, Union
from google3.third_party.mujoco.introspect import ast_nodes
ARRAY_EXTENTS_PATTERN = re.compile(r'(\[[^\]]+\]\s*)+\Z')
ARRAY_N_PATTERN = re.compile(r'\[([^\]]+)\]')
STARTS_WITH_CONST_PATTERN = re.compile(r'\Aconst(?![A-Za-z0-9_])')
ENDS_WITH_CONST_PATTERN = re.compile(r'(?<![A-Za-z0-9_])const\Z')
def _parse_qualifiers(
type_name: str,
qualifiers: Sequence[str]) -> Tuple[str, Mapping[str, bool]]:
"""Separates qualifiers from the rest of the type name."""
parts = re.split(r'\s+', type_name)
counter = collections.defaultdict(lambda: 0)
non_qualifiers = []
for part in parts:
if part in qualifiers:
counter[part] += 1
if counter[part] > 1:
ValueError('duplicate qualifier: {part!r}')
else:
non_qualifiers.append(part)
is_qualifier = dict()
for qualifier in qualifiers:
is_qualifier[f'is_{qualifier}'] = bool(counter[qualifier])
return ' '.join(non_qualifiers), is_qualifier
def _parse_maybe_array(
type_name: str, innermost_type: Optional[Union[ast_nodes.ValueType,
ast_nodes.PointerType]]
) -> Union[ast_nodes.ValueType, ast_nodes.PointerType, ast_nodes.ArrayType]:
"""Internal-only helper that parses a type that may be an array type."""
array_match = ARRAY_EXTENTS_PATTERN.search(type_name)
if array_match:
extents = tuple(
int(s.strip()) for s in ARRAY_N_PATTERN.findall(array_match.group(0)))
inner_type_str = type_name[:array_match.start()]
return ast_nodes.ArrayType(
inner_type=_parse_maybe_pointer(inner_type_str.strip(), innermost_type),
extents=extents)
else:
return _parse_maybe_pointer(type_name, innermost_type)
def _parse_maybe_pointer(
type_name: str, innermost_type: Optional[Union[ast_nodes.ValueType,
ast_nodes.PointerType]]
) -> Union[ast_nodes.ValueType, ast_nodes.PointerType, ast_nodes.ArrayType]:
"""Internal-only helper that parses a type that may be a pointer type."""
p = type_name.rfind('*')
if p != -1:
leftover, is_qualifier = _parse_qualifiers(
type_name[p + 1:].strip(), ('const', 'volatile', 'restrict'))
if leftover:
raise ValueError('invalid qualifier for pointer: {leftover!r}')
inner_type_str = type_name[:p].strip()
if inner_type_str:
inner_type = _parse_maybe_pointer(inner_type_str, innermost_type)
else:
assert innermost_type is not None
inner_type = innermost_type
return ast_nodes.PointerType(inner_type=inner_type, **is_qualifier)
else:
assert innermost_type is None # value type should be innermost
type_name, is_qualifier = _parse_qualifiers(
type_name.strip(), ('const', 'volatile'))
return ast_nodes.ValueType(name=type_name, **is_qualifier)
def _peel_nested_parens(input_str: str) -> MutableSequence[str]:
"""Extracts substrings from a string with nested parentheses.
The returned sequence starts from the substring enclosed in the innermost
parentheses and moves subsequently outwards. The contents of the inner
substrings are removed from the outer ones. For example, given the string
'lorem ipsum(dolor sit (consectetur adipiscing) amet)sed do eiusmod',
this function produces the sequence
['consectetur adipiscing', 'dolor sit amet', 'lorem ipsumsed do eiusmod'].
Args:
input_str: An input_str string consisting of zero or more nested
parentheses.
Returns:
A sequence of substrings enclosed with in respective parentheses. See the
description above for the precise detail of the output.
"""
start = input_str.find('(')
end = input_str.rfind(')')
if start == -1 and end == -1:
return [input_str]
else:
# Assertions to be re-raised into a meaningful error by the caller.
assert start != -1 # '(' should be present if there is a ')'
assert end != -1 # ')' should be present if there is a '('
assert start < end # '(' should come before ')'
out = _peel_nested_parens(input_str[start + 1:end])
out.append(input_str[:start] + input_str[end + 1:])
return out
def parse_type(
type_name: str
) -> Union[ast_nodes.ValueType, ast_nodes.PointerType, ast_nodes.ArrayType]:
"""Parses a string that represents a C type into an AST node."""
try:
type_str_stack = _peel_nested_parens(type_name.strip())
except AssertionError:
raise ValueError(f'{type_name!r} contains incorrectly nested parentheses')
result = None
while type_str_stack:
try:
result = _parse_maybe_array(type_str_stack.pop(), result)
except AssertionError:
raise ValueError(f'invalid type name {type_name!r}')
assert result # hint for pytype that `result` isn't None
return result
def parse_function_return_type(
type_name: str
) -> Union[ast_nodes.ValueType, ast_nodes.PointerType, ast_nodes.ArrayType]:
return parse_type(type_name[:type_name.rfind('(')])
+58
View File
@@ -0,0 +1,58 @@
# Copyright 2022 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.
# ==============================================================================
"""Tests for type_parsing.py."""
from absl.testing import absltest
from google3.third_party.mujoco.introspect import ast_nodes
from google3.third_party.mujoco.introspect import type_parsing
class TypeParsingTest(absltest.TestCase):
def test_parse_complex_type(self):
parsed_type = type_parsing.parse_type(
'int unsigned volatile long const long'+
'(**const(*const restrict*[9])[7])[3][4]')
expected_type = ast_nodes.ArrayType(
extents=[9],
inner_type=ast_nodes.PointerType(
ast_nodes.PointerType(
is_const=True,
is_restrict=True,
inner_type=ast_nodes.ArrayType(
extents=[7],
inner_type=ast_nodes.PointerType(
is_const=True,
inner_type=ast_nodes.PointerType(
ast_nodes.ArrayType(
extents=(3, 4),
inner_type=ast_nodes.ValueType(
'int unsigned long long',
is_const=True, is_volatile=True)
)
)
)
)
)
)
)
self.assertEqual(parsed_type, expected_type)
if __name__ == '__main__':
absltest.main()