xref: /openbmc/sdbusplus/tools/sdbusplus/property.py (revision 8aea1d814330427a3668b48c347f1703c214748e)
1import yaml
2
3from .namedelement import NamedElement
4from .renderer import Renderer
5
6
7class 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