Move introspect to python/introspect
PiperOrigin-RevId: 728695024 Change-Id: I96433e1e9ee509704d66bd4c1be1906732e4cc55
This commit is contained in:
committed by
Copybara-Service
parent
6a86247810
commit
b0e9d08673
@@ -36,9 +36,9 @@ cp -r "${package_dir}"/* .
|
||||
# Generate header files.
|
||||
old_pythonpath="${PYTHONPATH}"
|
||||
if [[ "$(uname)" == CYGWIN* || "$(uname)" == MINGW* ]]; then
|
||||
export PYTHONPATH="${old_pythonpath};${package_dir}/.."
|
||||
export PYTHONPATH="${old_pythonpath};${package_dir}/mujoco/python/.."
|
||||
else
|
||||
export PYTHONPATH="${old_pythonpath}:${package_dir}/.."
|
||||
export PYTHONPATH="${old_pythonpath}:${package_dir}/mujoco/python/.."
|
||||
fi
|
||||
python "${package_dir}"/mujoco/codegen/generate_enum_traits.py > \
|
||||
mujoco/enum_traits.h
|
||||
|
||||
@@ -18,5 +18,7 @@ pyproject_hooks==1.2.0 \
|
||||
--hash=sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913
|
||||
tomli==2.1.0; python_version < '3.11' \
|
||||
--hash=sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391
|
||||
typing-extensions==4.12.2 \
|
||||
--hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d
|
||||
zipp==3.21.0 \
|
||||
--hash=sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931
|
||||
|
||||
@@ -215,7 +215,7 @@ if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/enum_traits.h)
|
||||
else()
|
||||
add_custom_command(
|
||||
OUTPUT enum_traits.h
|
||||
COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${mujoco_SOURCE_DIR}/mujoco ${Python3_EXECUTABLE}
|
||||
COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${mujoco_SOURCE_DIR}/mujoco/python/mujoco ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/codegen/generate_enum_traits.py > enum_traits.h
|
||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/codegen/generate_enum_traits.py
|
||||
)
|
||||
@@ -233,7 +233,7 @@ if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/function_traits.h)
|
||||
else()
|
||||
add_custom_command(
|
||||
OUTPUT function_traits.h
|
||||
COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${mujoco_SOURCE_DIR}/mujoco ${Python3_EXECUTABLE}
|
||||
COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${mujoco_SOURCE_DIR}/mujoco/python/mujoco ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/codegen/generate_function_traits.py > function_traits.h
|
||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/codegen/generate_function_traits.py
|
||||
)
|
||||
@@ -408,7 +408,7 @@ target_link_libraries(
|
||||
if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/specs.cc.inc)
|
||||
add_custom_command(
|
||||
OUTPUT specs.cc.inc
|
||||
COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${mujoco_SOURCE_DIR}/mujoco ${Python3_EXECUTABLE}
|
||||
COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${mujoco_SOURCE_DIR}/mujoco/python ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/codegen/generate_spec_bindings.py > specs.cc.inc
|
||||
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/codegen/generate_spec_bindings.py
|
||||
)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# 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.
|
||||
# ==============================================================================
|
||||
@@ -0,0 +1,298 @@
|
||||
# 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('(struct )?[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 = (
|
||||
name == 'void *(*)(void *)' or
|
||||
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)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class StructFieldDecl:
|
||||
"""Represents a field in a struct or union declaration."""
|
||||
|
||||
name: str
|
||||
type: Union[
|
||||
ValueType,
|
||||
ArrayType,
|
||||
PointerType,
|
||||
'AnonymousStructDecl',
|
||||
'AnonymousUnionDecl',
|
||||
]
|
||||
doc: str
|
||||
array_extent: Optional[Tuple[Union[str, int], ...]] = None
|
||||
|
||||
def __str__(self):
|
||||
return self.type.decl(self.name)
|
||||
|
||||
@property
|
||||
def decltype(self) -> str:
|
||||
return self.type.decl()
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class AnonymousStructDecl:
|
||||
"""Represents an anonymous struct declaration."""
|
||||
|
||||
fields: Tuple[Union[StructFieldDecl, 'AnonymousUnionDecl'], ...]
|
||||
|
||||
def __init__(self, fields: Sequence[StructFieldDecl]):
|
||||
self.fields = tuple(fields)
|
||||
|
||||
def __str__(self):
|
||||
return self.decl()
|
||||
|
||||
def _inner_decl(self):
|
||||
return '; '.join(str(field) for field in self.fields) + ';'
|
||||
|
||||
def decl(self, name_or_decl: Optional[str] = None):
|
||||
parts = ['struct', f'{{{self._inner_decl()}}}']
|
||||
if name_or_decl:
|
||||
parts.append(name_or_decl)
|
||||
return ' '.join(parts)
|
||||
|
||||
|
||||
class AnonymousUnionDecl(AnonymousStructDecl):
|
||||
"""Represents an anonymous union declaration."""
|
||||
|
||||
def decl(self, name_or_decl: Optional[str] = None):
|
||||
parts = ['union', f'{{{self._inner_decl()}}}']
|
||||
if name_or_decl:
|
||||
parts.append(name_or_decl)
|
||||
return ' '.join(parts)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class StructDecl:
|
||||
"""Represents a struct declaration."""
|
||||
|
||||
name: str
|
||||
declname: str
|
||||
fields: Tuple[Union[StructFieldDecl, AnonymousUnionDecl], ...]
|
||||
|
||||
def __init__(self, name: str,
|
||||
declname: str,
|
||||
fields: Sequence[Union[StructFieldDecl, AnonymousUnionDecl]]):
|
||||
self.name = name
|
||||
self.declname = declname
|
||||
self.fields = tuple(fields)
|
||||
|
||||
def decl(self, name_or_decl: Optional[str] = None) -> str:
|
||||
parts = [self.name]
|
||||
if name_or_decl:
|
||||
parts.append(name_or_decl)
|
||||
return ' '.join(parts)
|
||||
@@ -0,0 +1,179 @@
|
||||
# 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 . 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]')
|
||||
|
||||
def test_struct_decl(self):
|
||||
struct = ast_nodes.StructDecl(
|
||||
name='mystruct',
|
||||
declname='struct mystruct_',
|
||||
fields=[
|
||||
ast_nodes.StructFieldDecl(
|
||||
name='foo',
|
||||
type=ast_nodes.ValueType('int'),
|
||||
doc='',
|
||||
)
|
||||
],
|
||||
)
|
||||
self.assertEqual(struct.decl('var'), 'mystruct var')
|
||||
|
||||
def test_anonymous_struct_decl(self):
|
||||
struct = ast_nodes.AnonymousStructDecl(
|
||||
fields=[
|
||||
ast_nodes.StructFieldDecl(
|
||||
name='foo',
|
||||
type=ast_nodes.ValueType('int'),
|
||||
doc='',
|
||||
),
|
||||
ast_nodes.StructFieldDecl(
|
||||
name='bar',
|
||||
type=ast_nodes.ArrayType(
|
||||
inner_type=ast_nodes.ValueType('float'), extents=(3,)
|
||||
),
|
||||
doc='',
|
||||
),
|
||||
],
|
||||
)
|
||||
self.assertEqual(str(struct), 'struct {int foo; float bar[3];}')
|
||||
self.assertEqual(struct.decl('var'), 'struct {int foo; float bar[3];} var')
|
||||
self.assertEqual(struct.fields[0].decltype, 'int')
|
||||
self.assertEqual(struct.fields[1].decltype, 'float [3]')
|
||||
|
||||
def test_anonymous_union_decl(self):
|
||||
union = ast_nodes.AnonymousUnionDecl(
|
||||
fields=[
|
||||
ast_nodes.StructFieldDecl(
|
||||
name='foo',
|
||||
type=ast_nodes.ValueType('int'),
|
||||
doc='',
|
||||
),
|
||||
ast_nodes.StructFieldDecl(
|
||||
name='bar',
|
||||
type=ast_nodes.ArrayType(
|
||||
inner_type=ast_nodes.ValueType('float'), extents=(3,)
|
||||
),
|
||||
doc='',
|
||||
),
|
||||
],
|
||||
)
|
||||
self.assertEqual(str(union), 'union {int foo; float bar[3];}')
|
||||
self.assertEqual(union.decl('var'), 'union {int foo; float bar[3];} var')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
absltest.main()
|
||||
@@ -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)
|
||||
@@ -0,0 +1,128 @@
|
||||
# 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 -x c mujoco.h
|
||||
"""
|
||||
|
||||
import json
|
||||
from typing import Any, Mapping, Sequence
|
||||
|
||||
from absl import app
|
||||
from absl import flags
|
||||
|
||||
from introspect import ast_nodes
|
||||
from . import formatter
|
||||
|
||||
_JSON_PATH = 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
|
||||
if 'inner' in child:
|
||||
value = int(child['inner'][0].get('value', next_idx))
|
||||
else:
|
||||
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(_JSON_PATH.value, '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 .ast_nodes import EnumDecl
|
||||
|
||||
ENUMS: Mapping[str, EnumDecl] = {enums_str}
|
||||
'''.strip()) # `print` adds a trailing newline
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(main)
|
||||
@@ -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 -x c mujoco.h
|
||||
"""
|
||||
|
||||
import json
|
||||
from typing import Any, Mapping, Sequence
|
||||
|
||||
from absl import app
|
||||
from absl import flags
|
||||
|
||||
from introspect import ast_nodes
|
||||
from introspect import type_parsing
|
||||
from . import formatter
|
||||
|
||||
_HEADER_PATH = flags.DEFINE_string(
|
||||
'header_path', None, 'Path to the original mujoco.h')
|
||||
_JSON_PATH = 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}', ' ')
|
||||
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(_JSON_PATH.value, 'r', encoding='utf-8') as f:
|
||||
root = json.load(f)
|
||||
|
||||
with open(_HEADER_PATH.value, '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 .ast_nodes import ArrayType
|
||||
from .ast_nodes import FunctionDecl
|
||||
from .ast_nodes import FunctionParameterDecl
|
||||
from .ast_nodes import PointerType
|
||||
from .ast_nodes import ValueType
|
||||
|
||||
FUNCTIONS: Mapping[str, FunctionDecl] = {functions_str}
|
||||
'''.strip()) # `print` adds a trailing newline
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(main)
|
||||
@@ -0,0 +1,276 @@
|
||||
# Copyright 2023 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 structs.py.
|
||||
|
||||
The JSON input can be generated via:
|
||||
clang -Xclang -ast-dump=json -fsyntax-only -fparse-all-comments -x c mujoco.h
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from typing import Any, Mapping, Sequence, Union
|
||||
|
||||
from absl import app
|
||||
from absl import flags
|
||||
|
||||
from introspect import ast_nodes
|
||||
from introspect import type_parsing
|
||||
from . import formatter
|
||||
|
||||
_JSON_PATH = flags.DEFINE_string(
|
||||
'json_path', None,
|
||||
'Path to the JSON file representing the Clang AST for mujoco.h')
|
||||
|
||||
ClangJsonNode = Mapping[str, Any]
|
||||
|
||||
_ANONYMOUS_KEY_PATTERN = re.compile(r'\d+:\d+(?=\))')
|
||||
_EXCLUDED = (
|
||||
'mjpPlugin',
|
||||
'mjpPlugin_',
|
||||
'mjpResourceProvider',
|
||||
'mjpResourceProvider_',
|
||||
'mjResource',
|
||||
'mjResource_',
|
||||
)
|
||||
|
||||
_ARRAY_COMMENT_PATTERN = re.compile(r'(.+?)\s\s+\((.+) x (.+)\)\Z')
|
||||
|
||||
|
||||
def traverse(node, visitor):
|
||||
visitor.visit(node)
|
||||
children = node.get('inner', [])
|
||||
for child in children:
|
||||
traverse(child, visitor)
|
||||
|
||||
|
||||
class _AnonymousTypePlaceholder(ast_nodes.ValueType):
|
||||
|
||||
def __init__(self, anonymous_key: str):
|
||||
self.name = anonymous_key
|
||||
self.is_const = False
|
||||
self.is_volatile = False
|
||||
|
||||
|
||||
class MjStructVisitor:
|
||||
"""A Clang AST JSON node visitor for MuJoCo API struct declarations."""
|
||||
|
||||
def __init__(self):
|
||||
self._structs = {}
|
||||
self._anonymous = {}
|
||||
self._typedefs = {}
|
||||
|
||||
def _normalize_type(
|
||||
self, declname: str
|
||||
) -> Union[
|
||||
ast_nodes.ValueType, ast_nodes.PointerType, ast_nodes.ArrayType]:
|
||||
"""Resolves anonymous structs/unions and looks up existing typedefs."""
|
||||
# Check for anonymous struct/union.
|
||||
if '(unnamed ' in declname:
|
||||
m = _ANONYMOUS_KEY_PATTERN.search(declname)
|
||||
if not m:
|
||||
raise RuntimeError('cannot parse anonymous key from {m!r}')
|
||||
return _AnonymousTypePlaceholder(m.group(0))
|
||||
|
||||
# Lookup typedef name and use it instead if one exists.
|
||||
for k, v in self._typedefs.items():
|
||||
if declname == v.declname:
|
||||
return type_parsing.parse_type(k)
|
||||
|
||||
# No valid normalization, just parse the declname.
|
||||
return type_parsing.parse_type(declname)
|
||||
|
||||
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}', ' ').strip()
|
||||
else:
|
||||
strings = []
|
||||
for child in node['inner']:
|
||||
strings.append(self._make_comment(child))
|
||||
return ''.join(strings).strip()
|
||||
|
||||
def _make_field(
|
||||
self, node: ClangJsonNode
|
||||
) -> Union[ast_nodes.StructFieldDecl, _AnonymousTypePlaceholder]:
|
||||
"""Makes a StructFieldDecl object from a Clang AST FieldDecl node."""
|
||||
doc = ''
|
||||
for child in node.get('inner', ()):
|
||||
if child['kind'] == 'FullComment':
|
||||
doc = self._make_comment(child)
|
||||
if 'name' in node:
|
||||
field_type = self._normalize_type(node['type']['qualType'])
|
||||
m = _ARRAY_COMMENT_PATTERN.match(doc)
|
||||
if m is None:
|
||||
array_extent = None
|
||||
else:
|
||||
doc = m.group(1)
|
||||
array_extent_0 = m.group(2)
|
||||
array_extent_1 = m.group(3)
|
||||
try:
|
||||
array_extent_1 = int(array_extent_1)
|
||||
except ValueError:
|
||||
pass
|
||||
if array_extent_1 == 1:
|
||||
array_extent = (array_extent_0,)
|
||||
else:
|
||||
array_extent = (array_extent_0, array_extent_1)
|
||||
return ast_nodes.StructFieldDecl(
|
||||
name=node['name'], type=field_type, doc=doc,
|
||||
array_extent=array_extent)
|
||||
else:
|
||||
return _AnonymousTypePlaceholder(self._make_anonymous_key(node))
|
||||
|
||||
def _make_struct(
|
||||
self, node: ClangJsonNode
|
||||
) -> Union[ast_nodes.AnonymousStructDecl, ast_nodes.StructDecl]:
|
||||
"""Makes a Decl object from a Clang AST RecordDecl node."""
|
||||
name = f"{node['tagUsed']} {node['name']}" if 'name' in node else ''
|
||||
fields = []
|
||||
for child in node.get('inner', ()):
|
||||
child_kind = child.get('kind')
|
||||
if child_kind == 'FieldDecl':
|
||||
fields.append(self._make_field(child))
|
||||
|
||||
if name:
|
||||
return ast_nodes.StructDecl(name=name, declname=name, fields=fields)
|
||||
elif node['tagUsed'] == 'union':
|
||||
return ast_nodes.AnonymousUnionDecl(fields=fields)
|
||||
else:
|
||||
return ast_nodes.AnonymousStructDecl(fields=fields)
|
||||
|
||||
def _is_mujoco_type(self, node: ClangJsonNode) -> bool:
|
||||
node_name = node.get('name', '')
|
||||
included_from = os.path.basename(
|
||||
node['loc'].get('includedFrom', {}).get('file', '')
|
||||
)
|
||||
return node_name not in _EXCLUDED and (
|
||||
node_name.startswith('mj')
|
||||
or included_from == 'mujoco.h'
|
||||
or included_from.startswith('mj')
|
||||
)
|
||||
|
||||
def _make_anonymous_key(self, node: ClangJsonNode) -> str:
|
||||
line = node['loc']['line']
|
||||
col = node['loc']['col']
|
||||
return f'{line}:{col}'
|
||||
|
||||
def visit(self, node: ClangJsonNode) -> None:
|
||||
"""Visits a JSON node."""
|
||||
if node.get('kind') == 'RecordDecl' and self._is_mujoco_type(node):
|
||||
struct_decl = self._make_struct(node)
|
||||
if hasattr(struct_decl, 'name'):
|
||||
self._structs[struct_decl.name] = struct_decl
|
||||
else:
|
||||
anonymous_key = self._make_anonymous_key(node)
|
||||
if anonymous_key in self._anonymous:
|
||||
raise RuntimeError(
|
||||
f'duplicate key for anonymous struct: {anonymous_key}')
|
||||
self._anonymous[anonymous_key] = struct_decl
|
||||
elif (node.get('kind') == 'TypedefDecl' and
|
||||
node['type']['qualType'].startswith('struct mj') and
|
||||
node['name'] not in _EXCLUDED):
|
||||
declname = node['type']['qualType']
|
||||
try:
|
||||
struct = self._structs[declname]
|
||||
except KeyError:
|
||||
self._typedefs[node['name']] = ast_nodes.StructDecl(
|
||||
name=node['name'], declname=declname, fields=())
|
||||
else:
|
||||
self._typedefs[node['name']] = ast_nodes.StructDecl(
|
||||
name=node['name'], declname=struct.declname, fields=struct.fields)
|
||||
|
||||
def resolve_all_anonymous(self) -> None:
|
||||
"""Replaces anonymous struct placeholders with corresponding decl."""
|
||||
for struct in itertools.chain(
|
||||
self._structs.values(), self._typedefs.values()
|
||||
):
|
||||
fields = []
|
||||
for field in struct.fields:
|
||||
if isinstance(field, _AnonymousTypePlaceholder):
|
||||
fields.append(self._anonymous[field.name])
|
||||
elif isinstance(field.type, _AnonymousTypePlaceholder):
|
||||
fields.append(
|
||||
ast_nodes.StructFieldDecl(
|
||||
name=field.name,
|
||||
type=self._anonymous[field.type.name],
|
||||
doc=field.doc,
|
||||
)
|
||||
)
|
||||
else:
|
||||
fields.append(field)
|
||||
struct.fields = tuple(fields)
|
||||
|
||||
@property
|
||||
def structs(self) -> Mapping[str, ast_nodes.StructDecl]:
|
||||
return self._structs
|
||||
|
||||
@property
|
||||
def typedefs(self) -> Mapping[str, ast_nodes.StructDecl]:
|
||||
return self._typedefs
|
||||
|
||||
|
||||
def main(argv: Sequence[str]) -> None:
|
||||
if len(argv) > 1:
|
||||
raise app.UsageError('Too many command-line arguments.')
|
||||
|
||||
with open(_JSON_PATH.value, 'r', encoding='utf-8') as f:
|
||||
root = json.load(f)
|
||||
|
||||
visitor = MjStructVisitor()
|
||||
|
||||
traverse(root, visitor)
|
||||
visitor.resolve_all_anonymous()
|
||||
|
||||
structs_str = formatter.format_as_python_code(visitor.typedefs)
|
||||
|
||||
print(f'''
|
||||
# Copyright 2023 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 structs.
|
||||
|
||||
DO NOT EDIT. THIS FILE IS AUTOMATICALLY GENERATED.
|
||||
"""
|
||||
|
||||
from typing import Mapping
|
||||
|
||||
from .ast_nodes import AnonymousStructDecl
|
||||
from .ast_nodes import AnonymousUnionDecl
|
||||
from .ast_nodes import ArrayType
|
||||
from .ast_nodes import PointerType
|
||||
from .ast_nodes import StructDecl
|
||||
from .ast_nodes import StructFieldDecl
|
||||
from .ast_nodes import ValueType
|
||||
|
||||
STRUCTS: Mapping[str, StructDecl] = {structs_str}
|
||||
'''.strip()) # `print` adds a trailing newline
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(main)
|
||||
@@ -0,0 +1,868 @@
|
||||
# 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),
|
||||
('mjDSBL_SENSOR', 4096),
|
||||
('mjDSBL_MIDPHASE', 8192),
|
||||
('mjDSBL_EULERDAMP', 16384),
|
||||
('mjDSBL_AUTORESET', 32768),
|
||||
('mjNDISABLE', 16),
|
||||
]),
|
||||
)),
|
||||
('mjtEnableBit',
|
||||
EnumDecl(
|
||||
name='mjtEnableBit',
|
||||
declname='enum mjtEnableBit_',
|
||||
values=dict([
|
||||
('mjENBL_OVERRIDE', 1),
|
||||
('mjENBL_ENERGY', 2),
|
||||
('mjENBL_FWDINV', 4),
|
||||
('mjENBL_INVDISCRETE', 8),
|
||||
('mjENBL_MULTICCD', 16),
|
||||
('mjENBL_ISLAND', 32),
|
||||
('mjENBL_NATIVECCD', 64),
|
||||
('mjNENABLE', 7),
|
||||
]),
|
||||
)),
|
||||
('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),
|
||||
('mjGEOM_SDF', 8),
|
||||
('mjNGEOMTYPES', 9),
|
||||
('mjGEOM_ARROW', 100),
|
||||
('mjGEOM_ARROW1', 101),
|
||||
('mjGEOM_ARROW2', 102),
|
||||
('mjGEOM_LINE', 103),
|
||||
('mjGEOM_LINEBOX', 104),
|
||||
('mjGEOM_FLEX', 105),
|
||||
('mjGEOM_SKIN', 106),
|
||||
('mjGEOM_LABEL', 107),
|
||||
('mjGEOM_TRIANGLE', 108),
|
||||
('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),
|
||||
]),
|
||||
)),
|
||||
('mjtTextureRole',
|
||||
EnumDecl(
|
||||
name='mjtTextureRole',
|
||||
declname='enum mjtTextureRole_',
|
||||
values=dict([
|
||||
('mjTEXROLE_USER', 0),
|
||||
('mjTEXROLE_RGB', 1),
|
||||
('mjTEXROLE_OCCLUSION', 2),
|
||||
('mjTEXROLE_ROUGHNESS', 3),
|
||||
('mjTEXROLE_METALLIC', 4),
|
||||
('mjTEXROLE_NORMAL', 5),
|
||||
('mjTEXROLE_OPACITY', 6),
|
||||
('mjTEXROLE_EMISSIVE', 7),
|
||||
('mjTEXROLE_RGBA', 8),
|
||||
('mjTEXROLE_ORM', 9),
|
||||
('mjNTEXROLE', 10),
|
||||
]),
|
||||
)),
|
||||
('mjtIntegrator',
|
||||
EnumDecl(
|
||||
name='mjtIntegrator',
|
||||
declname='enum mjtIntegrator_',
|
||||
values=dict([
|
||||
('mjINT_EULER', 0),
|
||||
('mjINT_RK4', 1),
|
||||
('mjINT_IMPLICIT', 2),
|
||||
('mjINT_IMPLICITFAST', 3),
|
||||
]),
|
||||
)),
|
||||
('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_FLEX', 4),
|
||||
('mjEQ_DISTANCE', 5),
|
||||
]),
|
||||
)),
|
||||
('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_BODY', 5),
|
||||
('mjTRN_UNDEFINED', 1000),
|
||||
]),
|
||||
)),
|
||||
('mjtDyn',
|
||||
EnumDecl(
|
||||
name='mjtDyn',
|
||||
declname='enum mjtDyn_',
|
||||
values=dict([
|
||||
('mjDYN_NONE', 0),
|
||||
('mjDYN_INTEGRATOR', 1),
|
||||
('mjDYN_FILTER', 2),
|
||||
('mjDYN_FILTEREXACT', 3),
|
||||
('mjDYN_MUSCLE', 4),
|
||||
('mjDYN_USER', 5),
|
||||
]),
|
||||
)),
|
||||
('mjtGain',
|
||||
EnumDecl(
|
||||
name='mjtGain',
|
||||
declname='enum mjtGain_',
|
||||
values=dict([
|
||||
('mjGAIN_FIXED', 0),
|
||||
('mjGAIN_AFFINE', 1),
|
||||
('mjGAIN_MUSCLE', 2),
|
||||
('mjGAIN_USER', 3),
|
||||
]),
|
||||
)),
|
||||
('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_FLEX', 9),
|
||||
('mjOBJ_MESH', 10),
|
||||
('mjOBJ_SKIN', 11),
|
||||
('mjOBJ_HFIELD', 12),
|
||||
('mjOBJ_TEXTURE', 13),
|
||||
('mjOBJ_MATERIAL', 14),
|
||||
('mjOBJ_PAIR', 15),
|
||||
('mjOBJ_EXCLUDE', 16),
|
||||
('mjOBJ_EQUALITY', 17),
|
||||
('mjOBJ_TENDON', 18),
|
||||
('mjOBJ_ACTUATOR', 19),
|
||||
('mjOBJ_SENSOR', 20),
|
||||
('mjOBJ_NUMERIC', 21),
|
||||
('mjOBJ_TEXT', 22),
|
||||
('mjOBJ_TUPLE', 23),
|
||||
('mjOBJ_KEY', 24),
|
||||
('mjOBJ_PLUGIN', 25),
|
||||
('mjNOBJECT', 26),
|
||||
('mjOBJ_FRAME', 100),
|
||||
]),
|
||||
)),
|
||||
('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_CAMPROJECTION', 8),
|
||||
('mjSENS_JOINTPOS', 9),
|
||||
('mjSENS_JOINTVEL', 10),
|
||||
('mjSENS_TENDONPOS', 11),
|
||||
('mjSENS_TENDONVEL', 12),
|
||||
('mjSENS_ACTUATORPOS', 13),
|
||||
('mjSENS_ACTUATORVEL', 14),
|
||||
('mjSENS_ACTUATORFRC', 15),
|
||||
('mjSENS_JOINTACTFRC', 16),
|
||||
('mjSENS_BALLQUAT', 17),
|
||||
('mjSENS_BALLANGVEL', 18),
|
||||
('mjSENS_JOINTLIMITPOS', 19),
|
||||
('mjSENS_JOINTLIMITVEL', 20),
|
||||
('mjSENS_JOINTLIMITFRC', 21),
|
||||
('mjSENS_TENDONLIMITPOS', 22),
|
||||
('mjSENS_TENDONLIMITVEL', 23),
|
||||
('mjSENS_TENDONLIMITFRC', 24),
|
||||
('mjSENS_FRAMEPOS', 25),
|
||||
('mjSENS_FRAMEQUAT', 26),
|
||||
('mjSENS_FRAMEXAXIS', 27),
|
||||
('mjSENS_FRAMEYAXIS', 28),
|
||||
('mjSENS_FRAMEZAXIS', 29),
|
||||
('mjSENS_FRAMELINVEL', 30),
|
||||
('mjSENS_FRAMEANGVEL', 31),
|
||||
('mjSENS_FRAMELINACC', 32),
|
||||
('mjSENS_FRAMEANGACC', 33),
|
||||
('mjSENS_SUBTREECOM', 34),
|
||||
('mjSENS_SUBTREELINVEL', 35),
|
||||
('mjSENS_SUBTREEANGMOM', 36),
|
||||
('mjSENS_GEOMDIST', 37),
|
||||
('mjSENS_GEOMNORMAL', 38),
|
||||
('mjSENS_GEOMFROMTO', 39),
|
||||
('mjSENS_E_POTENTIAL', 40),
|
||||
('mjSENS_E_KINETIC', 41),
|
||||
('mjSENS_CLOCK', 42),
|
||||
('mjSENS_PLUGIN', 43),
|
||||
('mjSENS_USER', 44),
|
||||
]),
|
||||
)),
|
||||
('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),
|
||||
]),
|
||||
)),
|
||||
('mjtSameFrame',
|
||||
EnumDecl(
|
||||
name='mjtSameFrame',
|
||||
declname='enum mjtSameFrame_',
|
||||
values=dict([
|
||||
('mjSAMEFRAME_NONE', 0),
|
||||
('mjSAMEFRAME_BODY', 1),
|
||||
('mjSAMEFRAME_INERTIA', 2),
|
||||
('mjSAMEFRAME_BODYROT', 3),
|
||||
('mjSAMEFRAME_INERTIAROT', 4),
|
||||
]),
|
||||
)),
|
||||
('mjtLRMode',
|
||||
EnumDecl(
|
||||
name='mjtLRMode',
|
||||
declname='enum mjtLRMode_',
|
||||
values=dict([
|
||||
('mjLRMODE_NONE', 0),
|
||||
('mjLRMODE_MUSCLE', 1),
|
||||
('mjLRMODE_MUSCLEUSER', 2),
|
||||
('mjLRMODE_ALL', 3),
|
||||
]),
|
||||
)),
|
||||
('mjtFlexSelf',
|
||||
EnumDecl(
|
||||
name='mjtFlexSelf',
|
||||
declname='enum mjtFlexSelf_',
|
||||
values=dict([
|
||||
('mjFLEXSELF_NONE', 0),
|
||||
('mjFLEXSELF_NARROW', 1),
|
||||
('mjFLEXSELF_BVH', 2),
|
||||
('mjFLEXSELF_SAP', 3),
|
||||
('mjFLEXSELF_AUTO', 4),
|
||||
]),
|
||||
)),
|
||||
('mjtTaskStatus',
|
||||
EnumDecl(
|
||||
name='mjtTaskStatus',
|
||||
declname='enum mjtTaskStatus_',
|
||||
values=dict([
|
||||
('mjTASK_NEW', 0),
|
||||
('mjTASK_QUEUED', 1),
|
||||
('mjTASK_COMPLETED', 2),
|
||||
]),
|
||||
)),
|
||||
('mjtState',
|
||||
EnumDecl(
|
||||
name='mjtState',
|
||||
declname='enum mjtState_',
|
||||
values=dict([
|
||||
('mjSTATE_TIME', 1),
|
||||
('mjSTATE_QPOS', 2),
|
||||
('mjSTATE_QVEL', 4),
|
||||
('mjSTATE_ACT', 8),
|
||||
('mjSTATE_WARMSTART', 16),
|
||||
('mjSTATE_CTRL', 32),
|
||||
('mjSTATE_QFRC_APPLIED', 64),
|
||||
('mjSTATE_XFRC_APPLIED', 128),
|
||||
('mjSTATE_EQ_ACTIVE', 256),
|
||||
('mjSTATE_MOCAP_POS', 512),
|
||||
('mjSTATE_MOCAP_QUAT', 1024),
|
||||
('mjSTATE_USERDATA', 2048),
|
||||
('mjSTATE_PLUGIN', 4096),
|
||||
('mjNSTATE', 13),
|
||||
('mjSTATE_PHYSICS', 14),
|
||||
('mjSTATE_FULLPHYSICS', 4111),
|
||||
('mjSTATE_USER', 4064),
|
||||
('mjSTATE_INTEGRATION', 8191),
|
||||
]),
|
||||
)),
|
||||
('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_CONSTRAINT', 6),
|
||||
('mjTIMER_ADVANCE', 7),
|
||||
('mjTIMER_POS_KINEMATICS', 8),
|
||||
('mjTIMER_POS_INERTIA', 9),
|
||||
('mjTIMER_POS_COLLISION', 10),
|
||||
('mjTIMER_POS_MAKE', 11),
|
||||
('mjTIMER_POS_PROJECT', 12),
|
||||
('mjTIMER_COL_BROAD', 13),
|
||||
('mjTIMER_COL_NARROW', 14),
|
||||
('mjNTIMER', 15),
|
||||
]),
|
||||
)),
|
||||
('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_FLEX', 10),
|
||||
('mjLABEL_SKIN', 11),
|
||||
('mjLABEL_SELECTION', 12),
|
||||
('mjLABEL_SELPNT', 13),
|
||||
('mjLABEL_CONTACTPOINT', 14),
|
||||
('mjLABEL_CONTACTFORCE', 15),
|
||||
('mjLABEL_ISLAND', 16),
|
||||
('mjNLABEL', 17),
|
||||
]),
|
||||
)),
|
||||
('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_CONTACT', 6),
|
||||
('mjFRAME_WORLD', 7),
|
||||
('mjNFRAME', 8),
|
||||
]),
|
||||
)),
|
||||
('mjtVisFlag',
|
||||
EnumDecl(
|
||||
name='mjtVisFlag',
|
||||
declname='enum mjtVisFlag_',
|
||||
values=dict([
|
||||
('mjVIS_CONVEXHULL', 0),
|
||||
('mjVIS_TEXTURE', 1),
|
||||
('mjVIS_JOINT', 2),
|
||||
('mjVIS_CAMERA', 3),
|
||||
('mjVIS_ACTUATOR', 4),
|
||||
('mjVIS_ACTIVATION', 5),
|
||||
('mjVIS_LIGHT', 6),
|
||||
('mjVIS_TENDON', 7),
|
||||
('mjVIS_RANGEFINDER', 8),
|
||||
('mjVIS_CONSTRAINT', 9),
|
||||
('mjVIS_INERTIA', 10),
|
||||
('mjVIS_SCLINERTIA', 11),
|
||||
('mjVIS_PERTFORCE', 12),
|
||||
('mjVIS_PERTOBJ', 13),
|
||||
('mjVIS_CONTACTPOINT', 14),
|
||||
('mjVIS_ISLAND', 15),
|
||||
('mjVIS_CONTACTFORCE', 16),
|
||||
('mjVIS_CONTACTSPLIT', 17),
|
||||
('mjVIS_TRANSPARENT', 18),
|
||||
('mjVIS_AUTOCONNECT', 19),
|
||||
('mjVIS_COM', 20),
|
||||
('mjVIS_SELECT', 21),
|
||||
('mjVIS_STATIC', 22),
|
||||
('mjVIS_SKIN', 23),
|
||||
('mjVIS_FLEXVERT', 24),
|
||||
('mjVIS_FLEXEDGE', 25),
|
||||
('mjVIS_FLEXFACE', 26),
|
||||
('mjVIS_FLEXSKIN', 27),
|
||||
('mjVIS_BODYBVH', 28),
|
||||
('mjVIS_FLEXBVH', 29),
|
||||
('mjVIS_MESHBVH', 30),
|
||||
('mjVIS_SDFITER', 31),
|
||||
('mjNVISFLAG', 32),
|
||||
]),
|
||||
)),
|
||||
('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),
|
||||
('mjRND_CULL_FACE', 9),
|
||||
('mjNRNDFLAG', 10),
|
||||
]),
|
||||
)),
|
||||
('mjtStereo',
|
||||
EnumDecl(
|
||||
name='mjtStereo',
|
||||
declname='enum mjtStereo_',
|
||||
values=dict([
|
||||
('mjSTEREO_NONE', 0),
|
||||
('mjSTEREO_QUADBUFFERED', 1),
|
||||
('mjSTEREO_SIDEBYSIDE', 2),
|
||||
]),
|
||||
)),
|
||||
('mjtPluginCapabilityBit',
|
||||
EnumDecl(
|
||||
name='mjtPluginCapabilityBit',
|
||||
declname='enum mjtPluginCapabilityBit_',
|
||||
values=dict([
|
||||
('mjPLUGIN_ACTUATOR', 1),
|
||||
('mjPLUGIN_SENSOR', 2),
|
||||
('mjPLUGIN_PASSIVE', 4),
|
||||
('mjPLUGIN_SDF', 8),
|
||||
]),
|
||||
)),
|
||||
('mjtGridPos',
|
||||
EnumDecl(
|
||||
name='mjtGridPos',
|
||||
declname='enum mjtGridPos_',
|
||||
values=dict([
|
||||
('mjGRID_TOPLEFT', 0),
|
||||
('mjGRID_TOPRIGHT', 1),
|
||||
('mjGRID_BOTTOMLEFT', 2),
|
||||
('mjGRID_BOTTOMRIGHT', 3),
|
||||
('mjGRID_TOP', 4),
|
||||
('mjGRID_BOTTOM', 5),
|
||||
('mjGRID_LEFT', 6),
|
||||
('mjGRID_RIGHT', 7),
|
||||
]),
|
||||
)),
|
||||
('mjtFramebuffer',
|
||||
EnumDecl(
|
||||
name='mjtFramebuffer',
|
||||
declname='enum mjtFramebuffer_',
|
||||
values=dict([
|
||||
('mjFB_WINDOW', 0),
|
||||
('mjFB_OFFSCREEN', 1),
|
||||
]),
|
||||
)),
|
||||
('mjtDepthMap',
|
||||
EnumDecl(
|
||||
name='mjtDepthMap',
|
||||
declname='enum mjtDepthMap_',
|
||||
values=dict([
|
||||
('mjDEPTH_ZERONEAR', 0),
|
||||
('mjDEPTH_ZEROFAR', 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),
|
||||
]),
|
||||
)),
|
||||
('mjtGeomInertia',
|
||||
EnumDecl(
|
||||
name='mjtGeomInertia',
|
||||
declname='enum mjtGeomInertia_',
|
||||
values=dict([
|
||||
('mjINERTIA_VOLUME', 0),
|
||||
('mjINERTIA_SHELL', 1),
|
||||
]),
|
||||
)),
|
||||
('mjtMeshInertia',
|
||||
EnumDecl(
|
||||
name='mjtMeshInertia',
|
||||
declname='enum mjtMeshInertia_',
|
||||
values=dict([
|
||||
('mjMESH_INERTIA_CONVEX', 0),
|
||||
('mjMESH_INERTIA_EXACT', 1),
|
||||
('mjMESH_INERTIA_LEGACY', 2),
|
||||
('mjMESH_INERTIA_SHELL', 3),
|
||||
]),
|
||||
)),
|
||||
('mjtBuiltin',
|
||||
EnumDecl(
|
||||
name='mjtBuiltin',
|
||||
declname='enum mjtBuiltin_',
|
||||
values=dict([
|
||||
('mjBUILTIN_NONE', 0),
|
||||
('mjBUILTIN_GRADIENT', 1),
|
||||
('mjBUILTIN_CHECKER', 2),
|
||||
('mjBUILTIN_FLAT', 3),
|
||||
]),
|
||||
)),
|
||||
('mjtMark',
|
||||
EnumDecl(
|
||||
name='mjtMark',
|
||||
declname='enum mjtMark_',
|
||||
values=dict([
|
||||
('mjMARK_NONE', 0),
|
||||
('mjMARK_EDGE', 1),
|
||||
('mjMARK_CROSS', 2),
|
||||
('mjMARK_RANDOM', 3),
|
||||
]),
|
||||
)),
|
||||
('mjtLimited',
|
||||
EnumDecl(
|
||||
name='mjtLimited',
|
||||
declname='enum mjtLimited_',
|
||||
values=dict([
|
||||
('mjLIMITED_FALSE', 0),
|
||||
('mjLIMITED_TRUE', 1),
|
||||
('mjLIMITED_AUTO', 2),
|
||||
]),
|
||||
)),
|
||||
('mjtAlignFree',
|
||||
EnumDecl(
|
||||
name='mjtAlignFree',
|
||||
declname='enum mjtAlignFree_',
|
||||
values=dict([
|
||||
('mjALIGNFREE_FALSE', 0),
|
||||
('mjALIGNFREE_TRUE', 1),
|
||||
('mjALIGNFREE_AUTO', 2),
|
||||
]),
|
||||
)),
|
||||
('mjtInertiaFromGeom',
|
||||
EnumDecl(
|
||||
name='mjtInertiaFromGeom',
|
||||
declname='enum mjtInertiaFromGeom_',
|
||||
values=dict([
|
||||
('mjINERTIAFROMGEOM_FALSE', 0),
|
||||
('mjINERTIAFROMGEOM_TRUE', 1),
|
||||
('mjINERTIAFROMGEOM_AUTO', 2),
|
||||
]),
|
||||
)),
|
||||
('mjtOrientation',
|
||||
EnumDecl(
|
||||
name='mjtOrientation',
|
||||
declname='enum mjtOrientation_',
|
||||
values=dict([
|
||||
('mjORIENTATION_QUAT', 0),
|
||||
('mjORIENTATION_AXISANGLE', 1),
|
||||
('mjORIENTATION_XYAXES', 2),
|
||||
('mjORIENTATION_ZAXIS', 3),
|
||||
('mjORIENTATION_EULER', 4),
|
||||
]),
|
||||
)),
|
||||
('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),
|
||||
('mjEVENT_REDRAW', 7),
|
||||
('mjEVENT_FILESDROP', 8),
|
||||
]),
|
||||
)),
|
||||
('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_EDITFLOAT', 12),
|
||||
('mjITEM_EDITTXT', 13),
|
||||
('mjNITEM', 14),
|
||||
]),
|
||||
)),
|
||||
('mjtSection',
|
||||
EnumDecl(
|
||||
name='mjtSection',
|
||||
declname='enum mjtSection_',
|
||||
values=dict([
|
||||
('mjSECT_CLOSED', 0),
|
||||
('mjSECT_OPEN', 1),
|
||||
('mjSECT_FIXED', 2),
|
||||
]),
|
||||
)),
|
||||
])
|
||||
@@ -0,0 +1,70 @@
|
||||
# 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 . 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_INVDISCRETE', 1<<3),
|
||||
('mjENBL_MULTICCD', 1<<4),
|
||||
('mjENBL_ISLAND', 1<<5),
|
||||
('mjENBL_NATIVECCD', 1<<6),
|
||||
('mjNENABLE', 7)))
|
||||
|
||||
# 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)
|
||||
self.assertEqual(enum_decl.values['mjGEOM_TRIANGLE'], 108)
|
||||
# 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
@@ -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 . import ast_nodes
|
||||
from . import functions
|
||||
from . 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()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,131 @@
|
||||
# Copyright 2023 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 structs.py."""
|
||||
|
||||
import re
|
||||
|
||||
from absl.testing import absltest
|
||||
|
||||
from . import ast_nodes
|
||||
from . import structs
|
||||
from . import type_parsing
|
||||
|
||||
|
||||
class StructsTest(absltest.TestCase):
|
||||
|
||||
def test_mjData(self): # pylint: disable=invalid-name
|
||||
struct_decl = structs.STRUCTS['mjData']
|
||||
self.assertEqual(struct_decl.name, 'mjData')
|
||||
self.assertEqual(struct_decl.declname, 'struct mjData_')
|
||||
|
||||
field_names = set()
|
||||
for field in struct_decl.fields:
|
||||
self.assertNotIn(field.name, field_names)
|
||||
field_names.add(field.name)
|
||||
if field.name == 'warning':
|
||||
self.assertEqual(field.type,
|
||||
type_parsing.parse_type('mjWarningStat[8]'))
|
||||
self.assertEqual(field.doc, 'warning statistics')
|
||||
elif field.name == 'qpos':
|
||||
self.assertEqual(field.type, type_parsing.parse_type('mjtNum*'))
|
||||
self.assertEqual(field.doc, 'position')
|
||||
self.assertEqual(field.array_extent, ('nq',))
|
||||
|
||||
self.assertIn('warning', field_names)
|
||||
self.assertIn('qpos', field_names)
|
||||
|
||||
def test_mjVisual(self): # pylint: disable=invalid-name
|
||||
struct_decl = structs.STRUCTS['mjVisual']
|
||||
self.assertEqual(struct_decl.name, 'mjVisual')
|
||||
self.assertEqual(struct_decl.declname, 'struct mjVisual_')
|
||||
|
||||
outer_fields = set()
|
||||
for outer_field in struct_decl.fields:
|
||||
self.assertNotIn(outer_field.name, outer_fields)
|
||||
outer_fields.add(outer_field.name)
|
||||
self.assertIsInstance(outer_field.type, ast_nodes.AnonymousStructDecl)
|
||||
inner_fields = set()
|
||||
if outer_field.name == 'global':
|
||||
for inner_field in outer_field.type.fields:
|
||||
self.assertNotIn(inner_field.name, inner_fields)
|
||||
inner_fields.add(inner_field.name)
|
||||
if inner_field.name == 'ipd':
|
||||
self.assertEqual(inner_field.type, type_parsing.parse_type('float'))
|
||||
self.assertEqual(
|
||||
inner_field.doc, 'inter-pupilary distance for free camera'
|
||||
)
|
||||
elif inner_field.name == 'offwidth':
|
||||
self.assertEqual(inner_field.type, type_parsing.parse_type('int'))
|
||||
self.assertEqual(inner_field.doc, 'width of offscreen buffer')
|
||||
self.assertIn('ipd', inner_fields)
|
||||
self.assertIn('offwidth', inner_fields)
|
||||
elif outer_field.name == 'headlight':
|
||||
for inner_field in outer_field.type.fields:
|
||||
self.assertNotIn(inner_field.name, inner_fields)
|
||||
inner_fields.add(inner_field.name)
|
||||
if inner_field.name in {'ambient', 'diffuse', 'specular'}:
|
||||
self.assertEqual(inner_field.type,
|
||||
type_parsing.parse_type('float[3]'))
|
||||
self.assertEqual(inner_field.doc,
|
||||
f'{inner_field.name} rgb (alpha=1)')
|
||||
elif inner_field.name == 'active':
|
||||
self.assertEqual(inner_field.type, type_parsing.parse_type('int'))
|
||||
self.assertEqual(inner_field.doc, 'is headlight active')
|
||||
self.assertIn('ambient', inner_fields)
|
||||
self.assertIn('diffuse', inner_fields)
|
||||
self.assertIn('specular', inner_fields)
|
||||
self.assertIn('active', inner_fields)
|
||||
|
||||
self.assertIn('global', outer_fields)
|
||||
self.assertIn('headlight', outer_fields)
|
||||
|
||||
def test_mjuiItem(self): # pylint: disable=invalid-name
|
||||
struct_decl = structs.STRUCTS['mjuiItem']
|
||||
self.assertEqual(struct_decl.name, 'mjuiItem')
|
||||
self.assertEqual(struct_decl.declname, 'struct mjuiItem_')
|
||||
|
||||
found_anonymous_union = False
|
||||
outer_fields = set()
|
||||
for outer_field in struct_decl.fields:
|
||||
if isinstance(outer_field, ast_nodes.AnonymousUnionDecl):
|
||||
self.assertFalse(found_anonymous_union)
|
||||
found_anonymous_union = True
|
||||
inner_fields = set()
|
||||
for inner_field in outer_field.fields:
|
||||
self.assertNotIn(inner_field.name, inner_fields)
|
||||
inner_fields.add(inner_field.name)
|
||||
if inner_field.name == 'single':
|
||||
self.assertEqual(inner_field.type,
|
||||
type_parsing.parse_type('struct mjuiItemSingle_'))
|
||||
self.assertEqual(inner_field.doc, 'check and button')
|
||||
elif inner_field.name == 'multi':
|
||||
self.assertEqual(inner_field.type,
|
||||
type_parsing.parse_type('struct mjuiItemMulti_'))
|
||||
self.assertEqual(inner_field.doc, 'static, radio and select')
|
||||
self.assertIn('single', inner_fields)
|
||||
self.assertIn('multi', inner_fields)
|
||||
else:
|
||||
self.assertNotIn(outer_field.name, outer_fields)
|
||||
outer_fields.add(outer_field.name)
|
||||
if outer_field.name == 'pdata':
|
||||
self.assertEqual(outer_field.type, type_parsing.parse_type('void*'))
|
||||
self.assertEqual(outer_field.doc, 'data pointer (type-specific)')
|
||||
|
||||
self.assertTrue(found_anonymous_union)
|
||||
self.assertIn('pdata', outer_fields)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
absltest.main()
|
||||
@@ -0,0 +1,154 @@
|
||||
# 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 . 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:
|
||||
raise 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."""
|
||||
if type_name == 'void *(*)(void *)':
|
||||
return ast_nodes.ValueType(name=type_name)
|
||||
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.
|
||||
"""
|
||||
if input_str == 'void *(*)(void *)':
|
||||
return ['void *(*)(void *)']
|
||||
|
||||
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 as e:
|
||||
raise ValueError(f'{type_name!r} contains incorrectly nested '
|
||||
f'parentheses') from e
|
||||
|
||||
result = None
|
||||
while type_str_stack:
|
||||
try:
|
||||
result = _parse_maybe_array(type_str_stack.pop(), result)
|
||||
except AssertionError as e:
|
||||
raise ValueError(f'invalid type name {type_name!r}') from e
|
||||
|
||||
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.find('(')])
|
||||
@@ -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 . import ast_nodes
|
||||
from . 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()
|
||||
Reference in New Issue
Block a user