xref: /openbmc/sdbusplus/tools/sdbusplus/property.py (revision 8aea1d814330427a3668b48c347f1703c214748e)
1 import yaml
2 
3 from .namedelement import NamedElement
4 from .renderer import Renderer
5 
6 
7 class Property(NamedElement, Renderer):
8     LOCAL_ENUM_MAGIC = "<LOCAL_ENUM>"
9     NONLOCAL_ENUM_MAGIC = "<NONLOCAL_ENUM>"
10 
11     def __init__(self, **kwargs):
12         self.typeName = kwargs.pop("type", None)
13         self.cppTypeName = self.parse_cpp_type()
14         self.defaultValue = kwargs.pop("default", None)
15         self.flags = kwargs.pop("flags", [])
16         self.cpp_flags = self.or_cpp_flags(self.flags)
17         self.errors = kwargs.pop("errors", [])
18 
19         if self.defaultValue is not None:
20             if isinstance(self.defaultValue, bool):
21                 # Convert True/False to 'true'/'false'
22                 # because it will be rendered as C++ code
23                 self.defaultValue = "true" if self.defaultValue else "false"
24             elif (
25                 isinstance(self.defaultValue, str)
26                 and self.typeName.lower() == "string"
27             ):
28                 # Wrap string type default values with double-quotes
29                 self.defaultValue = '"' + self.defaultValue + '"'
30             elif (
31                 isinstance(self.defaultValue, str) and self.is_floating_point()
32             ):
33                 if self.defaultValue.lower() == "nan":
34                     self.defaultValue = (
35                         f"std::numeric_limits<{self.cppTypeName}>::quiet_NaN()"
36                     )
37                 elif self.defaultValue.lower() == "infinity":
38                     self.defaultValue = (
39                         f"std::numeric_limits<{self.cppTypeName}>::infinity()"
40                     )
41                 elif self.defaultValue.lower() == "-infinity":
42                     self.defaultValue = (
43                         f"-std::numeric_limits<{self.cppTypeName}>::infinity()"
44                     )
45                 elif self.defaultValue.lower() == "epsilon":
46                     self.defaultValue = (
47                         f"std::numeric_limits<{self.cppTypeName}>::epsilon()"
48                     )
49             elif isinstance(self.defaultValue, str) and self.is_integer():
50                 if self.defaultValue.lower() == "maxint":
51                     self.defaultValue = (
52                         f"std::numeric_limits<{self.cppTypeName}>::max()"
53                     )
54                 elif self.defaultValue.lower() == "minint":
55                     self.defaultValue = (
56                         f"std::numeric_limits<{self.cppTypeName}>::min()"
57                     )
58 
59         super(Property, self).__init__(**kwargs)
60 
61     def is_enum(self):
62         if not self.typeName:
63             return False
64         return "enum" == self.__type_tuple()[0]
65 
66     def is_integer(self):
67         return self.typeName in [
68             "byte",
69             "int16",
70             "uint16",
71             "int32",
72             "uint32",
73             "int64",
74             "uint64",
75             "size",
76             "ssize",
77         ]
78 
79     def is_floating_point(self):
80         return self.typeName in ["double"]
81 
82     """ Return a conversion of the cppTypeName valid as a function parameter.
83         Currently only 'enum' requires conversion.
84     """
85 
86     def cppTypeParam(self, interface, full=False, typename="common"):
87         return self.__cppTypeParam(interface, self.cppTypeName, full, typename)
88 
89     def default_value(self, interface):
90         if self.defaultValue is None:
91             return ""
92         value = str(self.defaultValue)
93         enum_prefix = ""
94         if self.is_enum():
95             p_type = self.cppTypeParam(interface)
96             enum_prefix = f"{p_type}::"
97         return f" = {enum_prefix}{value}"
98 
99     def __cppTypeParam(
100         self, interface, cppTypeName, full=False, typename="common"
101     ):
102         iface = NamedElement(name=interface).cppNamespacedClass()
103         r = cppTypeName
104 
105         # Fix up local enum placeholders.
106         if full:
107             r = r.replace(self.LOCAL_ENUM_MAGIC, iface)
108         else:
109             r = r.replace(self.LOCAL_ENUM_MAGIC + "::", "")
110 
111         # Fix up non-local enum placeholders.
112         r = r.replace(self.NONLOCAL_ENUM_MAGIC, typename)
113 
114         return r
115 
116     """ Determine the C++ header of an enumeration-type property.
117     """
118 
119     def enum_headers(self, interface=None):
120         typeTuple = self.__type_tuple()
121         return self.__enum_headers(typeTuple, interface)
122 
123     def __enum_headers(self, typeTuple, interface=None):
124         # Enums can be processed directly.
125         if "enum" == typeTuple[0]:
126             # Get enum type from typeTuple.
127             enumType = typeTuple[1][0][0]
128 
129             # Local enums don't need a header, unless interface is provided.
130             if "self." in enumType:
131                 if interface:
132                     enumType = enumType.replace("self.", interface + ".")
133                 else:
134                     return []
135 
136             enumType = ".".join(enumType.split(".")[0:-1])
137             return [NamedElement(name=enumType).headerFile()]
138 
139         # If the details part of the tuple has zero entries, no enums are
140         # present
141         if len(typeTuple[1]) == 0:
142             return []
143 
144         # Otherwise, the tuple-type might have enums embedded in it.  Handle
145         # them recursively.
146         r = []
147         for t in typeTuple[1]:
148             r.extend(self.__enum_headers(t, interface))
149         return r
150 
151     """ Convert the property type into a C++ type.
152     """
153 
154     def parse_cpp_type(self):
155         if not self.typeName:
156             return None
157 
158         typeTuple = self.__type_tuple()
159         return self.__parse_cpp_type__(typeTuple)
160 
161     """ Convert the 'typeName' into a tuple of ('type', [ details ]) where
162         'details' is a recursive type-tuple.
163     """
164 
165     def __type_tuple(self):
166         if not self.typeName:
167             return None
168 
169         """ typeNames are _almost_ valid YAML.  Insert a , before each [
170             and then wrap it in a [ ] and it becomes valid YAML.  (assuming
171             the user gave us a valid typename)
172         """
173         typeName = self.typeName
174         typeArray = yaml.safe_load("[" + ",[".join(typeName.split("[")) + "]")
175         return self.__preprocess_yaml_type_array(typeArray).pop(0)
176 
177     """ Take a list of dbus types from YAML and convert it to a recursive data
178         structure. Each entry of the original list gets converted into a tuple
179         consisting of the type name and a list with the params for this type,
180         e.g.
181             ['dict', ['string', 'dict', ['string', 'int64']]]
182         is converted to
183             [('dict', [('string', []), ('dict', [('string', []),
184              ('int64', [])]]]
185     """
186 
187     def __preprocess_yaml_type_array(self, typeArray):
188         result = []
189 
190         for i in range(len(typeArray)):
191             # Ignore lists because we merge them with the previous element
192             if type(typeArray[i]) is list:
193                 continue
194 
195             # If there is a next element and it is a list, merge it with the
196             # current element.
197             if i < len(typeArray) - 1 and type(typeArray[i + 1]) is list:
198                 result.append(
199                     (
200                         typeArray[i],
201                         self.__preprocess_yaml_type_array(typeArray[i + 1]),
202                     )
203                 )
204             else:
205                 result.append((typeArray[i], []))
206 
207         return result
208 
209     propertyMap = {
210         "byte": {"cppName": "uint8_t", "params": 0},
211         "boolean": {"cppName": "bool", "params": 0},
212         "int16": {"cppName": "int16_t", "params": 0},
213         "uint16": {"cppName": "uint16_t", "params": 0},
214         "int32": {"cppName": "int32_t", "params": 0},
215         "uint32": {"cppName": "uint32_t", "params": 0},
216         "int64": {
217             "cppName": "int64_t",
218             "params": 0,
219             "registryName": "number",
220         },
221         "uint64": {
222             "cppName": "uint64_t",
223             "params": 0,
224             "registryName": "number",
225         },
226         "size": {
227             "cppName": "size_t",
228             "params": 0,
229             "registryName": "number",
230         },
231         "ssize": {"cppName": "ssize_t", "params": 0},
232         "double": {
233             "cppName": "double",
234             "params": 0,
235             "registryName": "number",
236         },
237         "unixfd": {"cppName": "sdbusplus::message::unix_fd", "params": 0},
238         "string": {
239             "cppName": "std::string",
240             "params": 0,
241             "registryName": "string",
242         },
243         "object_path": {
244             "cppName": "sdbusplus::message::object_path",
245             "params": 0,
246             "registryName": "string",
247         },
248         "signature": {
249             "cppName": "sdbusplus::message::signature",
250             "params": 0,
251         },
252         "array": {"cppName": "std::vector", "params": 1},
253         "set": {"cppName": "std::set", "params": 1},
254         "struct": {"cppName": "std::tuple", "params": -1},
255         "variant": {"cppName": "std::variant", "params": -1},
256         "dict": {"cppName": "std::map", "params": 2},
257         "enum": {
258             "cppName": "enum",
259             "params": 1,
260             "registryName": "number",
261         },
262     }
263 
264     """ Get the registry type of the property. """
265 
266     def registry_type(self):
267         return Property.propertyMap[self.typeName]["registryName"]
268 
269     """ Take a list of dbus types and perform validity checking, such as:
270             [ variant [ dict [ int32, int32 ], double ] ]
271         This function then converts the type-list into a C++ type string.
272     """
273 
274     def __parse_cpp_type__(self, typeTuple):
275         if len(typeTuple) != 2:
276             raise RuntimeError("Invalid typeTuple %s" % typeTuple)
277 
278         first = typeTuple[0]
279         entry = Property.propertyMap[first]
280 
281         result = entry["cppName"]
282 
283         # Handle 0-entry parameter lists.
284         if entry["params"] == 0:
285             if len(typeTuple[1]) != 0:
286                 raise RuntimeError("Invalid typeTuple %s" % typeTuple)
287             else:
288                 return result
289 
290         # Get the parameter list
291         rest = typeTuple[1]
292 
293         # Confirm parameter count matches.
294         if (entry["params"] != -1) and (entry["params"] != len(rest)):
295             raise RuntimeError(
296                 "Invalid entry count for %s : %s" % (first, rest)
297             )
298 
299         # Switch enum for proper type.
300         if result == "enum":
301             result = rest[0][0]
302 
303             # self. means local type.
304             if result.startswith("self."):
305                 return result.replace("self.", self.LOCAL_ENUM_MAGIC + "::")
306 
307             # Insert place-holder for header-type namespace (ex. "common")
308             result = result.split(".")
309             result = "::".join(
310                 [
311                     "sdbusplus",
312                     self.NONLOCAL_ENUM_MAGIC,
313                     NamedElement(
314                         name=".".join(result[0:-1])
315                     ).cppNamespacedClass(),
316                     NamedElement(name=result[-1]).classname,
317                 ]
318             )
319             return result
320 
321         # Parse each parameter entry, if appropriate, and create C++ template
322         # syntax.
323         result += "<"
324         result += ", ".join([self.__parse_cpp_type__(e) for e in rest])
325         result += ">"
326 
327         return result
328 
329     def markdown(self, loader):
330         return self.render(
331             loader, "property.md.mako", property=self, post=str.strip
332         )
333 
334     def cpp_includes(self, interface):
335         return interface.error_includes(self.errors) + interface.enum_includes(
336             [self]
337         )
338 
339     def or_cpp_flags(self, flags):
340         """Return the corresponding ORed cpp flags."""
341         flags_dict = {
342             "const": "vtable::property_::const_",
343             "deprecated": "vtable::common_::deprecated",
344             "emits_change": "vtable::property_::emits_change",
345             "emits_invalidation": "vtable::property_::emits_invalidation",
346             "explicit": "vtable::property_::explicit_",
347             "hidden": "vtable::common_::hidden",
348             "readonly": False,
349             "unprivileged": "vtable::common_::unprivileged",
350         }
351 
352         cpp_flags = []
353         for flag in flags:
354             try:
355                 if flags_dict[flag]:
356                     cpp_flags.append(flags_dict[flag])
357             except KeyError:
358                 raise ValueError('Invalid flag "{}"'.format(flag))
359 
360         return " | ".join(cpp_flags)
361