Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 113 additions & 31 deletions python_tools/json_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@
_KEY_ALIASES = 'aliases'
_KEY_SUBCLASSES = 'subclasses'
_KEY_IS_COMPONENT = 'isComponent'
_KEY_COMPONENT_PORT_NAME = 'componentPortName'
_KEY_COMPONENT_PORT_TYPE = 'componentPortType'
_KEY_COMPONENT_ARGS = 'componentArgs'


def ignoreModule(module_name: str) -> bool:
Expand Down Expand Up @@ -120,6 +119,8 @@ def _getClassName(self, o, containing_class_name: str = None) -> str:
o = o.replace(exported_full_class_name + ']', exported_class_name + ']')
if o.find(exported_full_class_name + ',') != -1:
o = o.replace(exported_full_class_name + ',', exported_class_name + ',')
o = o.replace('typing.SupportsInt', 'int')
o = o.replace('typing.SupportsFloat', 'float')
return o
raise Exception(f'Invalid argument {o}')

Expand All @@ -136,6 +137,13 @@ def _createFunctionIsEnumValue(
self, enum_cls: type) -> typing.Callable[[object], bool]:
return lambda value: type(value) == enum_cls

def _createArgData(self, arg_name: str, arg_type: str, default_value: str = ''):
arg_data = {}
arg_data[_KEY_ARGUMENT_NAME] = arg_name
arg_data[_KEY_ARGUMENT_TYPE] = arg_type
arg_data[_KEY_ARGUMENT_DEFAULT_VALUE] = default_value if default_value else ''
return arg_data

def _processModule(self, module) -> dict:
module_data = {}
module_name = self._getModuleName(module)
Expand Down Expand Up @@ -194,14 +202,10 @@ def _processModule(self, module) -> dict:
continue
args = []
for i in range(len(arg_names)):
arg_data = {}
arg_data[_KEY_ARGUMENT_NAME] = arg_names[i]
arg_data[_KEY_ARGUMENT_TYPE] = self._getClassName(arg_types[i])
if arg_default_values[i] is not None:
arg_data[_KEY_ARGUMENT_DEFAULT_VALUE] = arg_default_values[i]
else:
arg_data[_KEY_ARGUMENT_DEFAULT_VALUE] = ''
args.append(arg_data)
args.append(self._createArgData(
arg_names[i],
self._getClassName(arg_types[i]),
arg_default_values[i] if arg_default_values[i] is not None else ''))
function_data = {}
function_data[_KEY_FUNCTION_NAME] = function_name
function_data[_KEY_FUNCTION_RETURN_TYPE] = self._getClassName(return_type)
Expand Down Expand Up @@ -352,22 +356,14 @@ def _processClass(self, cls):
declaring_class_name = self._getClassName(arg_type, class_name)
# Don't append the self argument to the args array.
continue
arg_data = {}
arg_data[_KEY_ARGUMENT_NAME] = arg_name
arg_data[_KEY_ARGUMENT_TYPE] = self._getClassName(arg_type, class_name)
if arg_default_values[i] is not None:
arg_data[_KEY_ARGUMENT_DEFAULT_VALUE] = arg_default_values[i]
else:
arg_data[_KEY_ARGUMENT_DEFAULT_VALUE] = ''
args.append(arg_data)
args.append(self._createArgData(
arg_name,
self._getClassName(arg_type, class_name),
arg_default_values[i] if arg_default_values[i] is not None else ''))
constructor_data[_KEY_FUNCTION_ARGS] = args
constructor_data[_KEY_FUNCTION_DECLARING_CLASS_NAME] = declaring_class_name
constructor_data[_KEY_FUNCTION_RETURN_TYPE] = declaring_class_name
componentPortTuple = python_util.getComponentPortNameAndType(declaring_class_name, value)
if componentPortTuple is not None:
constructor_data[_KEY_COMPONENT_PORT_NAME] = componentPortTuple[0]
constructor_data[_KEY_COMPONENT_PORT_TYPE] = componentPortTuple[1]
class_data[_KEY_IS_COMPONENT] = True
self._processComponent(class_data, constructor_data, declaring_class_name, arg_names, arg_types, arg_default_values)
constructors.append(constructor_data)
class_data[_KEY_CONSTRUCTORS] = constructors

Expand Down Expand Up @@ -406,14 +402,10 @@ def _processClass(self, cls):
found_self_arg = True
if arg_type != full_class_name:
declaring_class_name = self._getClassName(arg_type, class_name)
arg_data = {}
arg_data[_KEY_ARGUMENT_NAME] = arg_name
arg_data[_KEY_ARGUMENT_TYPE] = self._getClassName(arg_type, class_name)
if arg_default_values[i] is not None:
arg_data[_KEY_ARGUMENT_DEFAULT_VALUE] = arg_default_values[i]
else:
arg_data[_KEY_ARGUMENT_DEFAULT_VALUE] = ''
args.append(arg_data)
args.append(self._createArgData(
arg_name,
self._getClassName(arg_type, class_name),
arg_default_values[i] if arg_default_values[i] is not None else ''))
function_data = {}
function_data[_KEY_FUNCTION_NAME] = function_name
function_data[_KEY_FUNCTION_RETURN_TYPE] = self._getClassName(return_type, class_name)
Expand Down Expand Up @@ -456,6 +448,96 @@ def _processClass(self, cls):
class_data[_KEY_ENUMS] = sorted(enums, key=lambda enum_data: enum_data[_KEY_ENUM_CLASS_NAME])
return class_data


def _processComponent(self, class_data, constructor_data, declaring_class_name, arg_names, arg_types, arg_default_values):
"""Determine whether this is a component and, if so, update the class_data and
constructor_data."""

# TODO(lizlooney): Replace the following temporary fake code with code that
# looks at doc string and/or parameter type aliases to tell whether this is
# a component and what the args are.

if declaring_class_name == 'wpilib_placeholders.ExpansionHubMotor':
args = []
args.append(self._createArgData('expansion_hub_motor', 'SYSTEMCORE_USB_PORT__EXPANSION_HUB_MOTOR_PORT'))
constructor_data[_KEY_COMPONENT_ARGS] = args
constructor_data[_KEY_IS_COMPONENT] = True
class_data[_KEY_IS_COMPONENT] = True
return

if declaring_class_name == 'wpilib_placeholders.ExpansionHubServo':
args = []
args.append(self._createArgData('expansion_hub_servo', 'SYSTEMCORE_USB_PORT__EXPANSION_HUB_SERVO_PORT'))
constructor_data[_KEY_COMPONENT_ARGS] = args
constructor_data[_KEY_IS_COMPONENT] = True
class_data[_KEY_IS_COMPONENT] = True
return

if declaring_class_name == 'wpilib.AddressableLED':
args = []
args.append(self._createArgData('smart_io_port', 'SYSTEMCORE_SMART_IO_PORT'))
constructor_data[_KEY_COMPONENT_ARGS] = args
constructor_data[_KEY_IS_COMPONENT] = True
class_data[_KEY_IS_COMPONENT] = True
return

if declaring_class_name == 'wpilib.AnalogEncoder':
if (len(arg_names) == 4 and
arg_names[0] == 'self' and
arg_names[1] == 'channel' and
arg_names[2] == 'fullRange' and
arg_names[3] == 'expectedZero'):
args = []
args.append(self._createArgData('smart_io_port', 'SYSTEMCORE_SMART_IO_PORT'))
args.append(self._createArgData('full_range', self._getClassName(arg_types[2]), '1.0'))
args.append(self._createArgData('expected_zero', self._getClassName(arg_types[3]), '0.0'))
constructor_data[_KEY_COMPONENT_ARGS] = args
constructor_data[_KEY_COMPONENT_ARGS]
constructor_data[_KEY_IS_COMPONENT] = True
class_data[_KEY_IS_COMPONENT] = True
return

if declaring_class_name == 'wpilib.AnalogPotentiometer':
if (len(arg_names) == 4 and
arg_names[0] == 'self' and
arg_names[1] == 'channel' and
arg_names[2] == 'fullRange' and
arg_names[3] == 'offset'):
args = []
args.append(self._createArgData('smart_io_port', 'SYSTEMCORE_SMART_IO_PORT'))
args.append(self._createArgData('full_range', self._getClassName(arg_types[2]), arg_default_values[2]))
args.append(self._createArgData('offset', self._getClassName(arg_types[3]), arg_default_values[3]))
constructor_data[_KEY_COMPONENT_ARGS] = args
constructor_data[_KEY_IS_COMPONENT] = True
class_data[_KEY_IS_COMPONENT] = True
return

if declaring_class_name == 'wpilib.DigitalInput':
args = []
args.append(self._createArgData('smart_io_port', 'SYSTEMCORE_SMART_IO_PORT'))
constructor_data[_KEY_COMPONENT_ARGS] = args
constructor_data[_KEY_IS_COMPONENT] = True
class_data[_KEY_IS_COMPONENT] = True
return

if declaring_class_name == 'wpilib.OnboardIMU':
if (len(arg_names) == 2 and
arg_names[0] == 'self' and
arg_names[1] == 'mountOrientation'):
args = []
args.append(self._createArgData('mount_orientation', self._getClassName(arg_types[1]), arg_default_values[1]))
constructor_data[_KEY_COMPONENT_ARGS] = args
constructor_data[_KEY_IS_COMPONENT] = True
class_data[_KEY_IS_COMPONENT] = True
return

if declaring_class_name == 'wpilib.PWMSparkMax':
args = []
args.append(self._createArgData('smart_io_port', 'SYSTEMCORE_SMART_IO_PORT'))
constructor_data[_KEY_COMPONENT_ARGS] = args
constructor_data[_KEY_IS_COMPONENT] = True
class_data[_KEY_IS_COMPONENT] = True

def _processClasses(self):
class_data_list = []
for cls in self._getPublicClasses():
Expand Down
32 changes: 8 additions & 24 deletions python_tools/python_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,16 @@ def isEnum(object):
inspect.isroutine(object.__init__) and
inspect.ismethoddescriptor(object.__init__) and
hasattr(object.__init__, "__doc__") and
object.__init__.__doc__ == f"__init__(self: {getFullClassName(object)}, value: int) -> None\n" and
(
object.__init__.__doc__ == f"__init__(self: {getFullClassName(object)}, value: int) -> None\n" or
object.__init__.__doc__ == f"__init__(self: {getFullClassName(object)}, value: typing.SupportsInt) -> None\n"
) and
hasattr(object, "name") and
inspect.isdatadescriptor(object.name) and
object.name.__doc__ == 'name(self: object) -> str\n' and
(
object.name.__doc__ == 'name(self: object) -> str\n' or
object.name.__doc__ == 'name(self: object, /) -> str\n'
) and
hasattr(object, "value") and
inspect.isdatadescriptor(object.value))

Expand Down Expand Up @@ -579,25 +585,3 @@ def collectSubclasses(classes: list[type]) -> dict[str, list[str]]:
if subclass_name not in subclass_names:
subclass_names.append(subclass_name)
return dict_class_name_to_subclass_names


def getComponentPortNameAndType(declaring_class_name, constructor) -> (str, str):
"""Determine whether this is a component and, if so, get the port types that
correspond to the constructor parameters. The returned value is a single
string consisting of one or more port types. Multiple port types are
separated by __ (two underscores). Each port type matches one of the PortType
enum values in src/storage/module_content.ts."""

# TODO(lizlooney): Replace the following temporary fake code with code that
# looks at doc string and/or parameter type aliases to tell whether this is
# a component and what the port types are.
if declaring_class_name == "wpilib_placeholders.ExpansionHubMotor":
return ("expansion_hub_motor", "SYSTEMCORE_USB_PORT__EXPANSION_HUB_MOTOR_PORT")
if declaring_class_name == "wpilib_placeholders.ExpansionHubServo":
return ("expansion_hub_servo", "SYSTEMCORE_USB_PORT__EXPANSION_HUB_SERVO_PORT")
if declaring_class_name == "wpilib.PWMSparkMax":
return ("smart_io_port", "SYSTEMCORE_SMART_IO_PORT")
if declaring_class_name == "wpilib.AddressableLED":
return ("smart_io_port", "SYSTEMCORE_SMART_IO_PORT")

return None
10 changes: 5 additions & 5 deletions src/blocks/mrc_class_method_def.ts
Original file line number Diff line number Diff line change
Expand Up @@ -471,20 +471,20 @@ export const pythonFromBlock = function (
branch = generator.PASS;
}

const params = block.mrcParameters;
let paramString = 'self';

if (generator.getModuleType() === storageModule.ModuleType.MECHANISM && block.mrcPythonMethodName === '__init__') {
const ports: string[] = generator.getComponentPortParameters();
if (ports.length) {
paramString += ', ' + ports.join(', ');
const mechanismInitArgNames: string[] = generator.getMechanismInitArgNames();
if (mechanismInitArgNames.length) {
paramString += ', ' + mechanismInitArgNames.join(', ');
}
}

if (generator.getModuleType() === storageModule.ModuleType.OPMODE && block.mrcPythonMethodName === '__init__') {
paramString += ', robot';
}

if (params.length != 0) {
if (block.mrcParameters.length != 0) {
block.mrcParameters.forEach(param => {
paramString += ', ' + param.name;
});
Expand Down
Loading