blob: 7a43c9e802b88a3f8af755ff5389b04b8c329593 [file] [log] [blame]
#!/usr/bin/env python
# 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 enum
import logging
import re
from idl.generators import CodeGenerator, GeneratorStorage
from idl.matter_idl_types import Idl, Field, Attribute, Cluster, ClusterSide
from idl import matter_idl_types
from idl.generators.types import (ParseDataType, BasicString, BasicInteger, FundamentalType,
IdlType, IdlEnumType, IdlBitmapType, TypeLookupContext)
from typing import Union, List, Set
def camel_to_const(s):
return re.sub("([a-z])([A-Z])", lambda y: y.group(1) + "_" + y.group(2), s).upper()
def create_lookup_context(idl: Idl, cluster: Cluster) -> TypeLookupContext:
"""
A filter to mark a lookup context to be within a specific cluster.
This is used to specify how structure/enum/other names are looked up.
Generally one looks up within the specific cluster then if cluster does
not contain a definition, we loop at global namespacing.
"""
return TypeLookupContext(idl, cluster)
def get_field_info(definition: Field, cluster: Cluster, idl: Idl):
context = create_lookup_context(idl, cluster)
actual = ParseDataType(definition.data_type, context)
if type(actual) == IdlEnumType or type(actual) == IdlBitmapType:
actual = actual.base_type
if type(actual) == BasicString:
return 'OctetString', 'char', actual.max_length, \
'ZCL_%s_ATTRIBUTE_TYPE' % actual.idl_name.upper()
if type(actual) == BasicInteger:
name = actual.idl_name.upper()
ty = "int%d_t" % actual.power_of_two_bits
if not actual.is_signed:
ty = "u" + ty
return "", ty, actual.byte_count, "ZCL_%s_ATTRIBUTE_TYPE" % name
if type(actual) == FundamentalType:
if actual == FundamentalType.BOOL:
return "", "bool", 1, "ZCL_BOOLEAN_ATTRIBUTE_TYPE"
if actual == FundamentalType.FLOAT:
return "", "float", 4, "ZCL_SINGLE_ATTRIBUTE_TYPE"
if actual == FundamentalType.DOUBLE:
return "", "double", 8, "ZCL_DOUBLE_ATTRIBUTE_TYPE"
logging.warn('Unknown fundamental type: %r' % actual)
return None
if type(actual) == IdlType:
return '', actual.idl_name, 'sizeof(%s)' % actual.idl_name, \
'ZCL_STRUCT_ATTRIBUTE_TYPE'
logging.warn('Unknown type: %r' % actual)
return None
def get_raw_size_and_type(attr: Attribute, cluster: Cluster, idl: Idl):
container, cType, size, matterType = get_field_info(attr.definition, cluster, idl)
if attr.definition.is_list:
return 'ZCL_ARRAY_ATTRIBUTE_TYPE, {}'.format(size)
return '{}, {}'.format(matterType, size)
def get_field_type(definition: Field, cluster: Cluster, idl: Idl):
container, cType, size, matterType = get_field_info(definition, cluster, idl)
if container == 'OctetString':
return 'std::string'
if definition.is_list:
cType = 'std::vector<{}>'.format(cType)
if definition.is_nullable:
cType = '::chip::app::DataModel::Nullable<{}>'.format(cType)
if definition.is_nullable:
cType = '::chip::Optional<{}>'.format(cType)
return cType
def get_attr_type(attr: Attribute, cluster: Cluster, idl: Idl):
return get_field_type(attr.definition, cluster, idl)
def get_attr_init(attr: Attribute, cluster: Cluster, idl: Idl):
if attr.definition.name == 'clusterRevision':
return ', ZCL_' + camel_to_const(cluster.name) + '_CLUSTER_REVISION'
return ''
def get_attr_mask(attr: Attribute, cluster: Cluster, idl: Idl):
masks = []
if attr.is_writable:
masks.append('ATTRIBUTE_MASK_WRITABLE')
if masks:
return ' | '.join(masks)
return '0'
def get_dynamic_endpoint(idl: Idl):
for ep in idl.endpoints:
if ep.number == 2:
return ep
def is_dynamic_cluster(cluster: Cluster, idl: Idl):
ep = get_dynamic_endpoint(idl)
if not ep:
return True
for c in ep.server_clusters:
if cluster.name == c.name:
return True
return False
class BridgeGenerator(CodeGenerator):
"""
Generation of bridge cpp code for matter.
"""
def __init__(self, storage: GeneratorStorage, idl: Idl):
"""
Inintialization is specific for cpp generation and will add
filters as required by the cpp .jinja templates to function.
"""
super().__init__(storage, idl)
self.jinja_env.filters['getType'] = get_attr_type
self.jinja_env.filters['getRawSizeAndType'] = get_raw_size_and_type
self.jinja_env.filters['getField'] = get_field_type
self.jinja_env.filters['getMask'] = get_attr_mask
self.jinja_env.filters['getInit'] = get_attr_init
self.jinja_env.filters['dynamicCluster'] = is_dynamic_cluster
# constcase will transform ID to I_D which is not what we want
# instead make the requirement a transition from lower to upper
self.jinja_env.filters['cameltoconst'] = camel_to_const
def internal_render_all(self):
"""
Renders C++
"""
for cluster in self.idl.clusters:
if not is_dynamic_cluster(cluster, self.idl):
continue
if cluster.side != ClusterSide.SERVER:
output_file_name = "bridge/%sServer.h" % cluster.name
else:
output_file_name = "bridge/%s.h" % cluster.name
self.internal_render_one_output(
template_path="bridge/BridgeClustersCpp.jinja",
output_file_name=output_file_name,
vars={
'cluster': cluster,
'idl': self.idl,
}
)
self.internal_render_one_output(
template_path="bridge/BridgeClustersCommon.jinja",
output_file_name="bridge/BridgeClustersImpl.h",
vars={
'clusters': self.idl.clusters,
'idl': self.idl,
}
)
self.internal_render_one_output(
template_path="bridge/BridgeClustersGlobalStructs.jinja",
output_file_name="bridge/BridgeGlobalStructs.h",
vars={
'idl': self.idl,
'structs': self.idl.structs,
}
)