| # Copyright (c) 2022 Project CHIP Authors |
| # |
| # 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. |
| |
| import logging |
| import enum |
| |
| from idl.matter_idl_types import DataType |
| from idl import matter_idl_types # to explicitly say 'Enum' |
| |
| from typing import Union, List, Optional |
| from dataclasses import dataclass |
| |
| |
| def ToPowerOfTwo(bits: int) -> int: |
| """ |
| Given a number, find the next power of two that is >= to the given value. |
| |
| Can be used to figure out a variable size given non-standard bit sizes in |
| matter: eg. a int24 can be stored in an int32, so ToPortOfTwo(24) == 32. |
| |
| """ |
| # probably bit manipulation can be faster, but this should be ok as well |
| result = 1 |
| while result < bits: |
| result = result * 2 |
| return result |
| |
| |
| @dataclass |
| class BasicInteger: |
| """ |
| Represents something that is stored as a basic integer. |
| """ |
| idl_name: str |
| byte_count: int # NOTE: may NOT be a power of 2 for odd sized integers |
| is_signed: bool |
| |
| @property |
| def bits(self): |
| return self.byte_count * 8 |
| |
| @property |
| def power_of_two_bits(self): |
| return ToPowerOfTwo(self.bits) |
| |
| |
| @dataclass |
| class BasicString: |
| """ |
| Represents either a string or a binary string (blob). |
| """ |
| idl_name: str |
| is_binary: bool |
| max_length: Union[int, None] = None |
| |
| |
| class FundamentalType(enum.Enum): |
| """ |
| Native types, generally available across C++/ObjC/Java/python/other. |
| """ |
| BOOL = enum.auto() |
| FLOAT = enum.auto() |
| DOUBLE = enum.auto() |
| |
| @property |
| def idl_name(self): |
| if self == FundamentalType.BOOL: |
| return "bool" |
| elif self == FundamentalType.FLOAT: |
| return "single" |
| elif self == FundamentalType.DOUBLE: |
| return "double" |
| else: |
| raise Error("Type not handled: %r" % self) |
| |
| @property |
| def byte_count(self): |
| if self == FundamentalType.BOOL: |
| return 1 |
| elif self == FundamentalType.FLOAT: |
| return 4 |
| elif self == FundamentalType.DOUBLE: |
| return 8 |
| else: |
| raise Error("Type not handled: %r" % self) |
| |
| @property |
| def bits(self): |
| return self.byte_count * 8 |
| |
| |
| @dataclass |
| class IdlEnumType: |
| """ |
| An enumeration type. Enumerations are constants with an underlying |
| base type that is an interger. |
| """ |
| idl_name: str |
| base_type: BasicInteger |
| |
| @property |
| def byte_count(self): |
| return base_type.byte_count() |
| |
| @property |
| def bits(self): |
| return base_type.bits() |
| |
| |
| @dataclass |
| class IdlBitmapType: |
| """ |
| Bitmaps mark that each bit (or a subset of said bits) have a meaning. |
| |
| Examples include "feature maps" where bits represent feature available or not. |
| """ |
| idl_name: str |
| base_type: BasicInteger |
| |
| @property |
| def byte_count(self): |
| return base_type.byte_count() |
| |
| @property |
| def bits(self): |
| return base_type.bits() |
| |
| |
| class IdlItemType(enum.Enum): |
| UNKNOWN = enum.auto() |
| STRUCT = enum.auto() |
| |
| |
| @dataclass |
| class IdlType: |
| """ |
| A type defined within the IDL. |
| |
| IDLs would generally only define structures as all other types are |
| described in other things like enums/bitmaps/basic types etc. |
| |
| However since IDL parsing is not yet codegen just syntactically, we allow |
| the option to have a type that is marked 'unknown' (likely invalid/never |
| defined). |
| """ |
| idl_name: str |
| item_type: IdlItemType |
| |
| @property |
| def is_struct(self) -> bool: |
| return self.item_type == IdlItemType.STRUCT |
| |
| |
| # Data types, held by ZAP in chip-types.xml and generally by the spec. |
| __CHIP_SIZED_TYPES__ = { |
| "bitmap16": BasicInteger(idl_name="bitmap16", byte_count=2, is_signed=False), |
| "bitmap24": BasicInteger(idl_name="bitmap24", byte_count=3, is_signed=False), |
| "bitmap32": BasicInteger(idl_name="bitmap32", byte_count=4, is_signed=False), |
| "bitmap64": BasicInteger(idl_name="bitmap64", byte_count=8, is_signed=False), |
| "bitmap8": BasicInteger(idl_name="bitmap8", byte_count=1, is_signed=False), |
| "enum16": BasicInteger(idl_name="enum16", byte_count=2, is_signed=False), |
| "enum32": BasicInteger(idl_name="enum32", byte_count=4, is_signed=False), |
| "enum8": BasicInteger(idl_name="enum8", byte_count=1, is_signed=False), |
| "int16s": BasicInteger(idl_name="int16s", byte_count=2, is_signed=True), |
| "int16u": BasicInteger(idl_name="int16u", byte_count=2, is_signed=False), |
| "int24s": BasicInteger(idl_name="int24s", byte_count=3, is_signed=True), |
| "int24u": BasicInteger(idl_name="int24u", byte_count=3, is_signed=False), |
| "int32s": BasicInteger(idl_name="int32s", byte_count=4, is_signed=True), |
| "int32u": BasicInteger(idl_name="int32u", byte_count=4, is_signed=False), |
| "int40s": BasicInteger(idl_name="int40s", byte_count=5, is_signed=True), |
| "int40u": BasicInteger(idl_name="int40u", byte_count=5, is_signed=False), |
| "int48s": BasicInteger(idl_name="int48s", byte_count=6, is_signed=True), |
| "int48u": BasicInteger(idl_name="int48u", byte_count=6, is_signed=False), |
| "int56s": BasicInteger(idl_name="int56s", byte_count=7, is_signed=True), |
| "int56u": BasicInteger(idl_name="int56u", byte_count=7, is_signed=False), |
| "int64s": BasicInteger(idl_name="int64s", byte_count=8, is_signed=True), |
| "int64u": BasicInteger(idl_name="int64u", byte_count=8, is_signed=False), |
| "int8s": BasicInteger(idl_name="int8s", byte_count=1, is_signed=True), |
| "int8u": BasicInteger(idl_name="int8u", byte_count=1, is_signed=False), |
| # Derived types |
| # Specification describes them in section '7.18.2. Derived Data Types' |
| "action_id": BasicInteger(idl_name="action_id", byte_count=1, is_signed=False), |
| "attrib_id": BasicInteger(idl_name="attrib_id", byte_count=4, is_signed=False), |
| "cluster_id": BasicInteger(idl_name="cluster_id", byte_count=4, is_signed=False), |
| "command_id": BasicInteger(idl_name="command_id", byte_count=4, is_signed=False), |
| "data_ver": BasicInteger(idl_name="data_ver", byte_count=4, is_signed=False), |
| "date": BasicInteger(idl_name="date", byte_count=4, is_signed=False), |
| "devtype_id": BasicInteger(idl_name="devtype_id", byte_count=4, is_signed=False), |
| "endpoint_no": BasicInteger(idl_name="endpoint_no", byte_count=2, is_signed=False), |
| "epoch_s": BasicInteger(idl_name="epoch_s", byte_count=4, is_signed=False), |
| "epoch_us": BasicInteger(idl_name="epoch_us", byte_count=8, is_signed=False), |
| "event_id": BasicInteger(idl_name="event_id", byte_count=4, is_signed=False), |
| "event_no": BasicInteger(idl_name="event_no", byte_count=8, is_signed=False), |
| "fabric_id": BasicInteger(idl_name="fabric_id", byte_count=8, is_signed=False), |
| "fabric_idx": BasicInteger(idl_name="fabric_idx", byte_count=1, is_signed=False), |
| "field_id": BasicInteger(idl_name="field_id", byte_count=4, is_signed=False), |
| "group_id": BasicInteger(idl_name="group_id", byte_count=2, is_signed=False), |
| "node_id": BasicInteger(idl_name="node_id", byte_count=8, is_signed=False), |
| "percent": BasicInteger(idl_name="percent", byte_count=1, is_signed=False), |
| "percent100ths": BasicInteger(idl_name="percent100ths", byte_count=2, is_signed=False), |
| "status": BasicInteger(idl_name="status", byte_count=2, is_signed=False), |
| "systime_us": BasicInteger(idl_name="systime_us", byte_count=8, is_signed=False), |
| "tod": BasicInteger(idl_name="tod", byte_count=4, is_signed=False), |
| "trans_id": BasicInteger(idl_name="trans_id", byte_count=4, is_signed=False), |
| "vendor_id": BasicInteger(idl_name="vendor_id", byte_count=2, is_signed=False), |
| } |
| |
| |
| class TypeLookupContext: |
| """ |
| Handles type lookups within a scope. |
| |
| Generally when looking for a struct/enum, the lookup will be first done |
| at a cluster level, then at a global level. |
| |
| Example: |
| |
| ================ test.matter ============== |
| enum A {} |
| |
| server cluster X { |
| struct A {} |
| struct B {} |
| } |
| |
| server cluster Y { |
| enum C {} |
| } |
| =========================================== |
| |
| When considering a lookup context of global (i.e. cluster is not set) |
| "A" is defined as an enum (::A) |
| "B" is undefined |
| "C" is undefined |
| |
| When considering a lookup context of cluster X |
| "A" is defined as a struct (X::A) |
| "B" is defined as a struct (X::B) |
| "C" is undefined |
| |
| When considering a lookup context of cluster Y |
| "A" is defined as an enum (::A) |
| "B" is undefined |
| "C" is defined as an enum (Y::C) |
| |
| """ |
| |
| def __init__(self, idl: matter_idl_types.Idl, cluster: Optional[matter_idl_types.Cluster]): |
| self.idl = idl |
| self.cluster = cluster |
| |
| def find_enum(self, name) -> Optional[matter_idl_types.Enum]: |
| """ |
| Find the first enumeration matching the given name for the given |
| lookup rules (searches cluster first, then global). |
| """ |
| for e in self.all_enums: |
| if e.name == name: |
| return e |
| |
| return None |
| |
| def find_struct(self, name) -> Optional[matter_idl_types.Struct]: |
| for s in self.all_structs: |
| if s.name == name: |
| return s |
| |
| return None |
| |
| def find_bitmap(self, name) -> Optional[matter_idl_types.Bitmap]: |
| for s in self.all_bitmaps: |
| if s.name == name: |
| return s |
| |
| return None |
| |
| @property |
| def all_enums(self): |
| """ |
| All enumerations, ordered by lookup priority. |
| |
| If an enum A is defined both in the cluster and globally, this WILL |
| return both instances, however it will return the cluster version first. |
| """ |
| if self.cluster: |
| for e in self.cluster.enums: |
| yield e |
| for e in self.idl.enums: |
| yield e |
| |
| @property |
| def all_bitmaps(self): |
| """ |
| All bitmaps defined within this lookup context. |
| |
| bitmaps are only defined at cluster level. If lookup context does not |
| include a cluster, the bitmal list will be empty. |
| """ |
| if self.cluster: |
| for b in self.cluster.bitmaps: |
| yield b |
| |
| @property |
| def all_structs(self): |
| """All structs, ordered by lookup prioroty. |
| |
| If a struct A is defined both in the cluster and globally, this WILL |
| return both instances, however it will return the cluster version first. |
| """ |
| if self.cluster: |
| for e in self.cluster.structs: |
| yield e |
| for e in self.idl.structs: |
| yield e |
| |
| def is_enum_type(self, name: str): |
| """ |
| Determine if the given type name is an enumeration. |
| |
| Handles both standard names (like enum8) as well as enumerations defined |
| within the current lookup context. |
| """ |
| if name.lower() in ["enum8", "enum16", "enum32"]: |
| return True |
| return any(map(lambda e: e.name == name, self.all_enums)) |
| |
| def is_struct_type(self, name: str): |
| """ |
| Determine if the given type name is type that is known to be a struct |
| """ |
| return any(map(lambda s: s.name == name, self.all_structs)) |
| |
| def is_bitmap_type(self, name: str): |
| """ |
| Determine if the given type name is type that is known to be a bitmap. |
| |
| Handles both standard/zcl names (like bitmap32) and types defined within |
| the current lookup context. |
| """ |
| if name.lower() in ["bitmap8", "bitmap16", "bitmap24", "bitmap32", "bitmap64"]: |
| return True |
| |
| return any(map(lambda s: s.name == name, self.all_bitmaps)) |
| |
| |
| def ParseDataType(data_type: DataType, lookup: TypeLookupContext) -> Union[BasicInteger, BasicString, FundamentalType, IdlType]: |
| """ |
| Given a AST data type and a lookup context, match it to a type that can be later |
| be used for generation. |
| |
| AST parsing is textual, so it does not understand what "foo" means. This method |
| looks up what "foo" actually means: includes basic types (e.g. bool), |
| zcl types (like enums or bitmaps) and does lookups to find structs/enums/bitmaps/etc |
| that are defined in the given lookup context. |
| """ |
| |
| lowercase_name = data_type.name.lower() |
| |
| if lowercase_name == 'boolean': |
| return FundamentalType.BOOL |
| if lowercase_name == 'single': |
| return FundamentalType.FLOAT |
| elif lowercase_name == 'double': |
| return FundamentalType.DOUBLE |
| elif lowercase_name in ['char_string', 'long_char_string']: |
| return BasicString(idl_name=lowercase_name, is_binary=False, max_length=data_type.max_length) |
| elif lowercase_name in ['octet_string', 'long_octet_string']: |
| return BasicString(idl_name=lowercase_name, is_binary=True, max_length=data_type.max_length) |
| elif lowercase_name in ['enum8', 'enum16', 'enum32']: |
| return IdlEnumType(idl_name=lowercase_name, base_type=__CHIP_SIZED_TYPES__[lowercase_name]) |
| elif lowercase_name in ['bitmap8', 'bitmap16', 'bitmap24', 'bitmap32']: |
| return IdlEnumType(idl_name=lowercase_name, base_type=__CHIP_SIZED_TYPES__[lowercase_name]) |
| |
| int_type = __CHIP_SIZED_TYPES__.get(lowercase_name, None) |
| if int_type is not None: |
| return int_type |
| |
| # All fast checks done, now check against known data types |
| e = lookup.find_enum(data_type.name) |
| if e: |
| # Valid enum found. it MUST be based on a valid data type |
| return IdlEnumType(idl_name=data_type.name, base_type=__CHIP_SIZED_TYPES__[e.base_type.lower()]) |
| |
| b = lookup.find_bitmap(data_type.name) |
| if b: |
| # Valid enum found. it MUST be based on a valid data type |
| return IdlBitmapType(idl_name=data_type.name, base_type=__CHIP_SIZED_TYPES__[b.base_type.lower()]) |
| |
| result = IdlType(idl_name=data_type.name, item_type=IdlItemType.UNKNOWN) |
| if lookup.find_struct(data_type.name): |
| result.item_type = IdlItemType.STRUCT |
| else: |
| logging.warn( |
| "Data type %s is NOT known, but treating it as a generic IDL type." % data_type) |
| |
| return result |