diff --git a/db/datatypes.go b/db/datatypes.go index 939d8cf4..21e2adfe 100644 --- a/db/datatypes.go +++ b/db/datatypes.go @@ -15,6 +15,7 @@ func (h *Host) indexDataTypeModels(cxt context.Context, parent *sectionInfo, clu h.indexBitmaps(cluster, parent) h.indexEnums(cluster, parent) h.indexStructs(cluster, parent) + h.indexTypeDefs(cluster, parent) return nil } @@ -55,6 +56,16 @@ func (h *Host) indexEnums(cluster *matter.Cluster, parent *sectionInfo) { } } } +func (h *Host) indexTypeDefs(cluster *matter.Cluster, parent *sectionInfo) { + for _, t := range cluster.TypeDefs { + row := newDBRow() + row.values[matter.TableColumnName] = t.Name + row.values[matter.TableColumnType] = t.Type.Name + row.values[matter.TableColumnDescription] = t.Description + ei := §ionInfo{id: h.nextID(typedefTable), parent: parent, values: row, children: make(map[string][]*sectionInfo)} + parent.children[typedefTable] = append(parent.children[typedefTable], ei) + } +} func (h *Host) readField(f *matter.Field, parent *sectionInfo, tableName string, entityType types.EntityType) { sr := newDBRow() @@ -93,7 +104,7 @@ func (h *Host) indexDataTypes(cxt context.Context, doc *spec.Doc, ds *sectionInf } for _, s := range parse.Skim[*spec.Section](dts.Elements()) { switch s.SecType { - case matter.SectionDataTypeBitmap, matter.SectionDataTypeEnum, matter.SectionDataTypeStruct: + case matter.SectionDataTypeBitmap, matter.SectionDataTypeEnum, matter.SectionDataTypeStruct, matter.SectionDataTypeDef: var t string switch s.SecType { case matter.SectionDataTypeBitmap: @@ -102,6 +113,8 @@ func (h *Host) indexDataTypes(cxt context.Context, doc *spec.Doc, ds *sectionInf t = "enum" case matter.SectionDataTypeStruct: t = "struct" + case matter.SectionDataTypeDef: + t = "typedef" } name := text.TrimCaseInsensitiveSuffix(s.Name, " Type") name = matter.StripDataTypeSuffixes(name) @@ -128,6 +141,9 @@ func (h *Host) indexDataTypes(cxt context.Context, doc *spec.Doc, ds *sectionInf ci.id = h.nextID(structTable) err = h.readTableSection(cxt, doc, ci, s, structField) ds.children[structTable] = append(ds.children[structTable], ci) + case matter.SectionDataTypeDef: + ci.id = h.nextID(typedefTable) + ds.children[typedefTable] = append(ds.children[typedefTable], ci) } if err != nil { return diff --git a/db/schema.go b/db/schema.go index 22ef2bdd..d9b20a51 100644 --- a/db/schema.go +++ b/db/schema.go @@ -22,6 +22,7 @@ var ( deviceTypeRevisionTable = "device_type_revision" deviceTypeConditionTable = "device_type_condition" deviceTypeClusterRequirementTable = "device_type_cluster_requirement" + typedefTable = "typedef" ) type tableSchemaDef struct { @@ -115,6 +116,14 @@ var tableSchema = map[string]tableSchemaDef{ matter.TableColumnConformance, }, }, + typedefTable: { + parent: clusterTable, + columns: []matter.TableColumn{ + matter.TableColumnName, + matter.TableColumnDescription, + matter.TableColumnType, + }, + }, attributeTable: { parent: clusterTable, columns: []matter.TableColumn{ diff --git a/errata/spec.go b/errata/spec.go index c97d7a94..cb993d90 100644 --- a/errata/spec.go +++ b/errata/spec.go @@ -37,9 +37,10 @@ const ( SpecPurposeCluster = 1 << (iota - 1) SpecPurposeDeviceType = 1 << (iota - 1) SpecPurposeCommandArguments = 1 << (iota - 1) + SpecPurposeDataTypesDef = 1 << (iota - 1) - SpecPurposeDataTypes SpecPurpose = SpecPurposeDataTypesBitmap | SpecPurposeDataTypesEnum | SpecPurposeDataTypesStruct - SpecPurposeAll SpecPurpose = SpecPurposeDataTypesBitmap | SpecPurposeDataTypesEnum | SpecPurposeDataTypesStruct | SpecPurposeCluster | SpecPurposeDeviceType | SpecPurposeCommandArguments + SpecPurposeDataTypes SpecPurpose = SpecPurposeDataTypesBitmap | SpecPurposeDataTypesEnum | SpecPurposeDataTypesStruct | SpecPurposeDataTypesDef + SpecPurposeAll SpecPurpose = SpecPurposeDataTypes | SpecPurposeCluster | SpecPurposeDeviceType | SpecPurposeCommandArguments ) var specPurposes = map[string]SpecPurpose{ diff --git a/matter/cluster.go b/matter/cluster.go index 9e67739a..318b6e3b 100644 --- a/matter/cluster.go +++ b/matter/cluster.go @@ -87,6 +87,7 @@ type Cluster struct { Features *Features `json:"features,omitempty"` AssociatedDataTypes + TypeDefs TypeDefSet `json:"typedefs,omitempty"` Attributes FieldSet `json:"attributes,omitempty"` Events EventSet `json:"events,omitempty"` Commands CommandSet `json:"commands,omitempty"` diff --git a/matter/sections.go b/matter/sections.go index e85a9afd..60cbe713 100644 --- a/matter/sections.go +++ b/matter/sections.go @@ -39,6 +39,7 @@ const ( SectionDerivedClusterNamespace SectionModeTags SectionGlobalElements + SectionDataTypeDef ) var TopLevelSectionOrders = map[DocType][]Section{ @@ -67,7 +68,7 @@ var TopLevelSectionOrders = map[DocType][]Section{ }, } -var DataTypeSectionOrder = []Section{SectionPrefix, SectionDataTypeBitmap, SectionDataTypeEnum, SectionDataTypeStruct} +var DataTypeSectionOrder = []Section{SectionPrefix, SectionDataTypeBitmap, SectionDataTypeEnum, SectionDataTypeStruct, SectionDataTypeDef} var sectionTypeStrings = map[Section]string{ SectionPrefix: "Prefix", @@ -85,6 +86,7 @@ var sectionTypeStrings = map[Section]string{ SectionDataTypeBitmap: "Bitmap", SectionDataTypeEnum: "Enum", SectionDataTypeStruct: "Struct", + SectionDataTypeDef: "TypeDef", SectionDeviceType: "DeviceType", SectionStatusCodes: "StatusCodes", SectionAttributes: "Attributes", @@ -130,6 +132,7 @@ var sectionTypeNames = map[Section]string{ SectionDataTypeBitmap: "Bitmap", SectionDataTypeEnum: "Enum", SectionDataTypeStruct: "Struct", + SectionDataTypeDef: "Type Definition", SectionDeviceType: "Device Type", SectionStatusCodes: "Status Codes", SectionAttributes: "Attributes", diff --git a/matter/spec/build.go b/matter/spec/build.go index 8ca0b3a6..02a40504 100644 --- a/matter/spec/build.go +++ b/matter/spec/build.go @@ -231,6 +231,16 @@ func addClusterToSpec(spec *Specification, d *Doc, m *matter.Cluster) { spec.DocRefs[en] = d spec.addEntityByName(en.Name, en, m) } + for _, en := range m.TypeDefs { + _, ok := spec.typeDefIndex[en.Name] + if ok { + slog.Debug("multiple structs with same name", "name", en.Name) + } else { + spec.typeDefIndex[en.Name] = en + } + spec.DocRefs[en] = d + spec.addEntityByName(en.Name, en, m) + } } func (sp *Builder) resolveDataTypeReferences(spec *Specification) { diff --git a/matter/spec/cluster.go b/matter/spec/cluster.go index a8647006..f5cb9d89 100644 --- a/matter/spec/cluster.go +++ b/matter/spec/cluster.go @@ -40,17 +40,20 @@ func (s *Section) toClusters(d *Doc, entityMap map[asciidoc.Attributable][]types var bitmaps matter.BitmapSet var enums matter.EnumSet var structs matter.StructSet + var typedefs matter.TypeDefSet for _, s := range elements { switch s.SecType { case matter.SectionDataTypes, matter.SectionStatusCodes: var bs matter.BitmapSet var es matter.EnumSet var ss matter.StructSet - bs, es, ss, err = s.toDataTypes(d, entityMap) + var ts matter.TypeDefSet + bs, es, ss, ts, err = s.toDataTypes(d, entityMap) if err == nil { bitmaps = append(bitmaps, bs...) enums = append(enums, es...) structs = append(structs, ss...) + typedefs = append(typedefs, ts...) } case matter.SectionFeatures: features, err = s.toFeatures(d, entityMap) @@ -90,6 +93,7 @@ func (s *Section) toClusters(d *Doc, entityMap map[asciidoc.Attributable][]types c.AddBitmaps(bitmaps...) c.AddEnums(enums...) c.AddStructs(structs...) + c.TypeDefs = append(c.TypeDefs, typedefs...) c.Features = features for _, s := range elements { @@ -137,6 +141,8 @@ func (s *Section) toClusters(d *Doc, entityMap map[asciidoc.Attributable][]types c.AddEnums(le) case *matter.Struct: c.AddStructs(le) + case *matter.TypeDef: + c.TypeDefs = append(c.TypeDefs, le) default: slog.Warn("unexpected loose entity", log.Element("path", d.Path, s.Base), "entity", le) } @@ -206,6 +212,12 @@ func assignCustomDataType(c *matter.Cluster, dt *types.DataType) { return } } + for _, t := range c.TypeDefs { + if name == t.Name { + dt.Entity = t + return + } + } slog.Debug("unable to find data type for field", slog.String("dataType", name), log.Type("source", dt.Source)) } diff --git a/matter/spec/datatypes.go b/matter/spec/datatypes.go index 826288d5..c93aefce 100644 --- a/matter/spec/datatypes.go +++ b/matter/spec/datatypes.go @@ -16,7 +16,7 @@ import ( "github.com/project-chip/alchemy/matter/types" ) -func (s *Section) toDataTypes(d *Doc, entityMap map[asciidoc.Attributable][]types.Entity) (bitmaps matter.BitmapSet, enums matter.EnumSet, structs matter.StructSet, err error) { +func (s *Section) toDataTypes(d *Doc, entityMap map[asciidoc.Attributable][]types.Entity) (bitmaps matter.BitmapSet, enums matter.EnumSet, structs matter.StructSet, typedefs matter.TypeDefSet, err error) { traverse(d, s, errata.SpecPurposeDataTypes, func(s *Section, parent parse.HasElements, index int) parse.SearchShould { switch s.SecType { @@ -47,6 +47,16 @@ func (s *Section) toDataTypes(d *Doc, entityMap map[asciidoc.Attributable][]type } else { structs = append(structs, me) } + case matter.SectionDataTypeDef: + var me *matter.TypeDef + me, err = s.toTypeDef(d, entityMap) + if err != nil { + slog.Warn("Error converting section to typedef", log.Element("path", d.Path, s.Base), slog.Any("error", err)) + err = nil + } else { + typedefs = append(typedefs, me) + entityMap[s.Base] = append(entityMap[s.Base], me) + } default: } return parse.SearchShouldContinue @@ -140,7 +150,7 @@ func (d *Doc) readFields(ti *TableInfo, entityType types.EntityType) (fields []* return } -var listDataTypeDefinitionPattern = regexp.MustCompile(`(?:list|List|DataTypeList)\[([^\]]+)\]`) +var listDataTypeDefinitionPattern = regexp.MustCompile(`(?:list|List|DataTypeList)\[([^]]+)]`) var asteriskPattern = regexp.MustCompile(`\^[0-9]+\^\s*$`) func (d *Doc) ReadRowDataType(row *asciidoc.TableRow, columnMap ColumnIndex, column matter.TableColumn) (*types.DataType, error) { diff --git a/matter/spec/global.go b/matter/spec/global.go index d322c056..6c4133ff 100644 --- a/matter/spec/global.go +++ b/matter/spec/global.go @@ -45,6 +45,15 @@ func addGlobalEntities(spec *Specification, doc *Doc) error { spec.structIndex[m.Name] = m } spec.addEntityByName(m.Name, m, nil) + case *matter.TypeDef: + slog.Debug("Found global typedef", "name", m.Name, "path", doc.Path) + _, ok := spec.typeDefIndex[m.Name] + if ok { + slog.Warn("multiple global typedefs with same name", "name", m.Name) + } else { + spec.typeDefIndex[m.Name] = m + } + spec.addEntityByName(m.Name, m, nil) case *matter.Command: _, ok := spec.commandIndex[m.Name] if ok { diff --git a/matter/spec/section.go b/matter/spec/section.go index 967fae3c..1805e603 100644 --- a/matter/spec/section.go +++ b/matter/spec/section.go @@ -156,7 +156,7 @@ func AssignSectionTypes(doc *Doc, top *Section) error { assignSectionType(doc, section, getSectionType(ps, section)) switch section.SecType { - case matter.SectionDataTypeBitmap, matter.SectionDataTypeEnum, matter.SectionDataTypeStruct: + case matter.SectionDataTypeBitmap, matter.SectionDataTypeEnum, matter.SectionDataTypeStruct, matter.SectionDataTypeDef: if section.Base.Level > 2 { slog.Debug("Unusual depth for section type", slog.String("name", section.Name), slog.String("type", section.SecType.String()), slog.String("path", doc.Path.String())) } @@ -177,6 +177,8 @@ func assignSectionType(doc *Doc, s *Section, sectionType matter.Section) { ignore = doc.errata.Spec.IgnoreSection(s.Name, errata.SpecPurposeDataTypesEnum) case matter.SectionDataTypeStruct: ignore = doc.errata.Spec.IgnoreSection(s.Name, errata.SpecPurposeDataTypesStruct) + case matter.SectionDataTypeDef: + ignore = doc.errata.Spec.IgnoreSection(s.Name, errata.SpecPurposeDataTypesDef) case matter.SectionCluster: ignore = doc.errata.Spec.IgnoreSection(s.Name, errata.SpecPurposeCluster) case matter.SectionDeviceType: @@ -360,6 +362,8 @@ func deriveSectionType(section *Section, parent *Section) matter.Section { return matter.SectionDataTypeBitmap } else if dataType.BaseType == types.BaseDataTypeCustom { return matter.SectionDataTypeStruct + } else if dataType.BaseType.IsSimple() { + return matter.SectionDataTypeDef } } slog.Debug("unknown section type", "path", section.Doc.Path, "name", name) @@ -453,7 +457,7 @@ func (s *Section) toGlobalObjects(d *Doc, entityMap map[asciidoc.Attributable][] return entities, nil } -var dataTypeDefinitionPattern = regexp.MustCompile(`is\s+derived\s+from\s+(?:<>)?`) +var dataTypeDefinitionPattern = regexp.MustCompile(`is\s+derived\s+from\s+(?:<>)?`) func (s *Section) GetDataType() *types.DataType { var dts string @@ -527,6 +531,15 @@ func findLooseEntities(doc *Doc, section *Section, entityMap map[asciidoc.Attrib } else { entities = append(entities, s) } + case matter.SectionDataTypeDef: + var t *matter.TypeDef + t, err = section.toTypeDef(doc, entityMap) + if err != nil { + slog.Warn("Error converting loose section to typedef", log.Element("path", doc.Path, section.Base), slog.Any("error", err)) + err = nil + } else { + entities = append(entities, t) + } case matter.SectionGlobalElements: var ges []types.Entity ges, err = section.toGlobalElements(doc, entityMap) diff --git a/matter/spec/spec.go b/matter/spec/spec.go index c020c20c..c9982647 100644 --- a/matter/spec/spec.go +++ b/matter/spec/spec.go @@ -27,6 +27,7 @@ type Specification struct { bitmapIndex map[string]*matter.Bitmap enumIndex map[string]*matter.Enum structIndex map[string]*matter.Struct + typeDefIndex map[string]*matter.TypeDef commandIndex map[string]*matter.Command eventIndex map[string]*matter.Event @@ -48,6 +49,7 @@ func newSpec() *Specification { bitmapIndex: make(map[string]*matter.Bitmap), enumIndex: make(map[string]*matter.Enum), structIndex: make(map[string]*matter.Struct), + typeDefIndex: make(map[string]*matter.TypeDef), commandIndex: make(map[string]*matter.Command), eventIndex: make(map[string]*matter.Event), diff --git a/matter/spec/typedef.go b/matter/spec/typedef.go new file mode 100644 index 00000000..01b2fbe6 --- /dev/null +++ b/matter/spec/typedef.go @@ -0,0 +1,25 @@ +package spec + +import ( + "fmt" + + "github.com/project-chip/alchemy/asciidoc" + "github.com/project-chip/alchemy/internal/text" + "github.com/project-chip/alchemy/matter" + "github.com/project-chip/alchemy/matter/types" +) + +func (s *Section) toTypeDef(d *Doc, entityMap map[asciidoc.Attributable][]types.Entity) (ms *matter.TypeDef, err error) { + name := text.TrimCaseInsensitiveSuffix(s.Name, " Type") + ms = matter.NewTypeDef(s.Base) + ms.Name = name + + dt := s.GetDataType() + if (dt == nil) || !dt.BaseType.IsSimple() { + return nil, fmt.Errorf("unknown typedef data type: %s", dt.Name) + } + ms.Type = dt + entityMap[s.Base] = append(entityMap[s.Base], ms) + ms.Name = CanonicalName(ms.Name) + return +} diff --git a/matter/typedef.go b/matter/typedef.go new file mode 100644 index 00000000..961d7d75 --- /dev/null +++ b/matter/typedef.go @@ -0,0 +1,46 @@ +package matter + +import ( + "github.com/project-chip/alchemy/asciidoc" + "github.com/project-chip/alchemy/matter/types" +) + +type TypeDef struct { + entity + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Type *types.DataType `json:"type,omitempty"` +} + +func NewTypeDef(source asciidoc.Element) *TypeDef { + return &TypeDef{ + entity: entity{source: source}, + } +} + +func (*TypeDef) EntityType() types.EntityType { + return types.EntityTypeDef +} + +func (s *TypeDef) Clone() *TypeDef { + ns := &TypeDef{Name: s.Name, Description: s.Description, Type: s.Type} + return ns +} + +func (s *TypeDef) Inherit(parent *TypeDef) { + if len(s.Description) == 0 { + s.Description = parent.Description + } + s.Type = s.Type +} + +type TypeDefSet []*TypeDef + +func (ss TypeDefSet) Identifier(name string) (types.Entity, bool) { + for _, e := range ss { + if e.Name == name { + return e, true + } + } + return nil, false +} diff --git a/matter/types/base.go b/matter/types/base.go index 8946c94b..b83bb912 100644 --- a/matter/types/base.go +++ b/matter/types/base.go @@ -89,7 +89,6 @@ const ( BaseDataTypeTag BaseDataTypeMessageID - BaseDataTypeDeviceType ) type HasBaseDataType interface { @@ -121,6 +120,18 @@ func (bdt BaseDataType) String() string { return BaseDataTypeName(bdt) } +func (bdt BaseDataType) IsSimple() bool { + switch bdt { + case BaseDataTypeUInt8, BaseDataTypeUInt16, BaseDataTypeUInt24, BaseDataTypeUInt32, + BaseDataTypeUInt40, BaseDataTypeUInt48, BaseDataTypeUInt56, BaseDataTypeUInt64, + BaseDataTypeInt8, BaseDataTypeInt16, BaseDataTypeInt24, BaseDataTypeInt32, + BaseDataTypeInt40, BaseDataTypeInt48, BaseDataTypeInt56, BaseDataTypeInt64, + BaseDataTypeString: + return true + } + return false +} + func BaseDataTypeName(baseDataType BaseDataType) string { switch baseDataType { case BaseDataTypeUnknown: diff --git a/matter/types/entity.go b/matter/types/entity.go index 4af056f5..faf616dc 100644 --- a/matter/types/entity.go +++ b/matter/types/entity.go @@ -27,6 +27,7 @@ const ( EntityTypeCondition EntityTypeStructField EntityTypeElementRequirement + EntityTypeDef EntityTypeNamespace ) @@ -58,6 +59,7 @@ var ( EntityTypeCondition: "condition", EntityTypeStructField: "structField", EntityTypeElementRequirement: "elementRequirement", + EntityTypeDef: "typeDef", EntityTypeNamespace: "namespace", } ) diff --git a/zap/datatype.go b/zap/datatype.go index 882dd663..84b892b5 100644 --- a/zap/datatype.go +++ b/zap/datatype.go @@ -423,14 +423,6 @@ func ToBaseDataType(s string) types.BaseDataType { } return types.BaseDataTypeUnknown } - -func ConvertZapToDataTypeName(s string) string { - if z, ok := zapToMatterMap[strings.ToLower(s)]; ok { - return z - } - return s -} - func maxOver255Bytes(fs matter.FieldSet, f *matter.Field) bool { if f.Constraint == nil { return false @@ -464,6 +456,10 @@ func FieldToZapDataType(fs matter.FieldSet, f *matter.Field) string { if f.Type.IsArray() { return DataTypeName(f.Type.EntryType) } + if t, isTypeDef := f.Type.Entity.(*matter.TypeDef); isTypeDef { + return DataTypeName(t.Type) + } + return DataTypeName(f.Type) } diff --git a/zap/generate/cluster.go b/zap/generate/cluster.go index 628c5710..8e2c963b 100644 --- a/zap/generate/cluster.go +++ b/zap/generate/cluster.go @@ -123,8 +123,10 @@ func (tg *TemplateGenerator) populateCluster(configurator *zap.Configurator, cle commands[c] = []*matter.Number{} } - xml.SetOrCreateSimpleElement(cle, "domain", matter.DomainNames[configurator.Doc.Domain]) - clusterName := cluster.Name + de := xml.SetOrCreateSimpleElement(cle, "domain", "") + de.CreateAttr("name", matter.DomainNames[configurator.Doc.Domain]) + de.SetText("") + clusterName := cluster.Name if errata.ClusterName != "" { clusterName = errata.ClusterName } diff --git a/zap/generate/configurator.go b/zap/generate/configurator.go index 46a40da1..a075c40c 100644 --- a/zap/generate/configurator.go +++ b/zap/generate/configurator.go @@ -48,6 +48,8 @@ func (tg *TemplateGenerator) renderZapTemplate(configurator *zap.Configurator, x if ce == nil { ce = x.CreateElement("configurator") } + ce.CreateAttr("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") + ce.CreateAttr("xsi:noNamespaceSchemaLocation", "../../zcl.xsd") de := ce.SelectElement("domain") if de == nil {