#!/usr/bin/env python3 # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # # Copyright 2026 (c) o6 Automation GmbH (Author: Julius Pfrommer) import re import copy from collections import OrderedDict import argparse from nodeset_compiler.type_parser import * ############################### # Parse the Command Line Input# ############################### parser = argparse.ArgumentParser() parser.add_argument('-c', '--type-csv', metavar="", type=argparse.FileType('r'), dest="type_csv", action='append', default=[], help='csv file with type descriptions') parser.add_argument('-x', '--xml', metavar="", type=argparse.FileType('rb'), action='append', dest="type_xml", default=[], help='NodeSet XML file.') parser.add_argument('--namespaceMap', metavar="", type=str, dest="namespace_map", action='append', default=["0:http://opcfoundation.org/UA/"], help='Mapping of namespace uri to the resulting namespace index in the server. Default only contains Namespace 0: "0:http://opcfoundation.org/UA/". ' 'Parameter can be used multiple times to define multiple mappings. ' 'Pinning the index of every namespace that contributes a type makes the generated type array const. ' 'Purely imported namespaces need not be pinned. An incomplete or inconsistent mapping is an error.') parser.add_argument('-s', '--selected-types', metavar="", type=argparse.FileType('r'), dest="selected_types", action='append', default=[], help='file with list of types (among those parsed) to be generated. If not given, all types are generated') parser.add_argument('--no-builtin', action='store_true', dest="no_builtin", help='Do not generate builtin types') parser.add_argument('--opaque-map', metavar="", type=argparse.FileType('r'), dest="opaque_map", action='append', default=[], help='JSON file with opaque type mapping: { \'typename\': { \'ns\': 0, \'id\': 7, \'name\': \'UInt32\' }, ... }') parser.add_argument('--internal', action='store_true', dest="internal", help='Given bsd are internal types which do not have any .csv file') parser.add_argument('--gen-doc', action='store_true', dest="gen_doc", help='Generate a .rst documentation version of the type definition') parser.add_argument('-t', '--type-bsd', metavar="", type=argparse.FileType('r'), dest="type_bsd", action='append', default=[], help='bsd file with type definitions') parser.add_argument('-i', '--import', metavar="", type=str, dest="import_bsd", action='append', default=[], help='combination of TYPE_ARRAY#filepath.bsd with type definitions which should be loaded but not exported/generated') parser.add_argument('--export-macro', metavar="", type=str, dest="export_macro", default="", help='macro to use in front of extern declarations (default: UA_EXPORT)') parser.add_argument('outfile', metavar='', help='output file w/o extension') ################### # Code Generation # ################### # Namespace 0, always at index 0 in a server OPC_UA_NAMESPACE = "http://opcfoundation.org/UA/" # Some types can be memcpy'd off the binary stream. That's especially important # for arrays. But we need to check if they contain padding and whether the # endianness is correct. This dict gives the C-statement that must be true for the # type to be overlayable. Parsed types are added to the list if they apply. # # Boolean is not overlayable 1-byte type. We get "undefined behavior" errors # during fuzzing if we don't force the value to either exactly true or false. builtin_overlayable = {"SByte": "true", "Byte": "true", "Int16": "UA_BINARY_OVERLAYABLE_INTEGER", "UInt16": "UA_BINARY_OVERLAYABLE_INTEGER", "Int32": "UA_BINARY_OVERLAYABLE_INTEGER", "UInt32": "UA_BINARY_OVERLAYABLE_INTEGER", "Int64": "UA_BINARY_OVERLAYABLE_INTEGER", "UInt64": "UA_BINARY_OVERLAYABLE_INTEGER", "Float": "UA_BINARY_OVERLAYABLE_FLOAT", "Double": "UA_BINARY_OVERLAYABLE_FLOAT", "StatusCode": "UA_BINARY_OVERLAYABLE_INTEGER", "Guid": "(UA_BINARY_OVERLAYABLE_INTEGER && " + "offsetof(UA_Guid, data2) == sizeof(UA_UInt32) && " + "offsetof(UA_Guid, data3) == (sizeof(UA_UInt16) + sizeof(UA_UInt32)) && " + "offsetof(UA_Guid, data4) == (2*sizeof(UA_UInt32)))"} whitelistFuncAttrWarnUnusedResult = [] # for instances [ "String", "ByteString", "LocalizedText" ] # Escape C strings: def makeCLiteral(value): return re.sub(r'(? 1 else "" return "0", nodeId def _types_definition_equal(t1, t2): """Compare two Type objects by structural definition (ignoring nodeId/outname). Used to detect cross-namespace same-name types that are ABI-compatible vs those that would produce a silent memory-layout mismatch.""" if type(t1) is not type(t2): return False if isinstance(t1, EnumerationType): return (t1.elements == t2.elements and t1.isOptionSet == t2.isOptionSet and t1.lengthInBits == t2.lengthInBits) if isinstance(t1, OpaqueType): return t1.base_type == t2.base_type if isinstance(t1, StructType): if len(t1.members) != len(t2.members): return False for m1, m2 in zip(t1.members, t2.members): if (m1.name != m2.name or m1.member_type.name != m2.member_type.name or m1.is_array != m2.is_array or m1.is_optional != m2.is_optional): return False return True return False class CGenerator: def __init__(self, parser, inname, outfile, is_internal_types, gen_doc, namespaceMap, export_macro=""): self.parser = parser self.inname = inname self.outfile = outfile self.is_internal_types = is_internal_types self.gen_doc = gen_doc self.filtered_types = None self.namespaceMap = namespaceMap self.export_macro = export_macro if export_macro else "UA_EXPORT" self.fh = None self.fc = None self.fd = None self.fe = None def _contributing_namespaces(self): """The namespaces that actually contribute types to the generated array. iter_types drops types that come from imported bsd files but keeps the (then empty) namespace entry, so filtered_types can list namespaces that own no type in this array. Those do not constrain the baked namespace indices.""" return [ns for ns, types in self.filtered_types.items() if len(types) > 0] def _validate_namespace_map(self): """Sanity-check a user-provided --namespaceMap. Pinning is all-or-nothing: with an unpinned namespace the array cannot be const, and the nodeset compiler then rewrites the namespace index of every entry when the nodeset is loaded -- silently discarding the pinned indices. Rather than generate such a half-honored array, fail here so the mistake surfaces at build time.""" if self.namespaceMap == {OPC_UA_NAMESPACE: 0}: return # Nothing pinned, only the implicit namespace 0 entry byIndex = {} for ns in sorted(self.namespaceMap): idx = self.namespaceMap[ns] if not 0 <= idx <= 65535: raise RuntimeError( "--namespaceMap index {} for {} is outside the UInt16 " "range (0..65535).".format(idx, ns)) if idx in byIndex: raise RuntimeError( "--namespaceMap pins index {} to more than one namespace:\n" " {}\n {}".format(idx, byIndex[idx], ns)) byIndex[idx] = ns unknown = [ns for ns in self.namespaceMap if ns != OPC_UA_NAMESPACE and ns not in self.filtered_types] if unknown: raise RuntimeError( "--namespaceMap contains namespaces that do not appear in the " "generated type array:\n" + "".join(" {}\n".format(ns) for ns in sorted(unknown)) + "Namespaces of this type array:\n" + "".join(" {}\n".format(ns) for ns in sorted(self.filtered_types))) missing = [ns for ns in self._contributing_namespaces() if ns not in self.namespaceMap] if missing: raise RuntimeError( "--namespaceMap is incomplete. Pinning is all-or-nothing: as " "long as one namespace is unpinned the type array cannot be " "const, and loading the nodeset overwrites the namespace index " "of every entry -- including the pinned ones. Either pin all " "namespaces that contribute types or drop --namespaceMap.\n" "Not pinned:\n" + "".join(" {}\n".format(ns) for ns in sorted(missing))) def _can_be_const(self): """The type array can be declared const (and reside in read-only memory) when the namespace index of every contained type is fixed at generation time: either the type belongs to namespace 0 or its namespace index was pinned via --namespaceMap. Only namespaces that contribute a type are relevant. Without pinning, the namespace indices get rewritten in-place when the nodeset is loaded into a server, so the array must remain mutable.""" return all(ns in self.namespaceMap for ns in self._contributing_namespaces()) @staticmethod def get_type_index(datatype): if isinstance(datatype, BuiltinType): return makeCIdentifier("UA_TYPES_" + datatype.name.upper()) if isinstance(datatype, EnumerationType): return datatype.strTypeIndex if datatype.name is not None: return "UA_" + makeCIdentifier(datatype.outname.upper() + "_" + datatype.name.upper()) return makeCIdentifier(datatype.outname.upper()) @staticmethod def get_type_kind(datatype): if isinstance(datatype, BuiltinType): return "UA_DATATYPEKIND_" + datatype.name.upper() if isinstance(datatype, EnumerationType): return datatype.strTypeKind if isinstance(datatype, OpaqueType): return "UA_DATATYPEKIND_" + datatype.base_type.upper() if isinstance(datatype, StructType): for m in datatype.members: if m.is_optional: return "UA_DATATYPEKIND_OPTSTRUCT" if datatype.is_union: return "UA_DATATYPEKIND_UNION" return "UA_DATATYPEKIND_STRUCTURE" raise RuntimeError("Unknown type") @staticmethod def get_struct_overlayable(struct): if not struct.pointerfree == "false": return "false" before = None overlayable = "" for m in struct.members: if m.is_array or not m.member_type.pointerfree: return "false" overlayable += "\n\t\t && " + m.member_type.overlayable if before: overlayable += "\n\t\t && offsetof(UA_%s, %s) == (offsetof(UA_%s, %s) + sizeof(UA_%s))" % \ (makeCIdentifier(struct.name), makeCIdentifier(m.name), makeCIdentifier(struct.name), makeCIdentifier(before.name), makeCIdentifier(before.member_type.name)) before = m return overlayable def get_type_overlayable(self, datatype): if isinstance(datatype, BuiltinType) or isinstance(datatype, OpaqueType): return builtin_overlayable[datatype.name] if datatype.name in builtin_overlayable else "false" if isinstance(datatype, EnumerationType): return "UA_BINARY_OVERLAYABLE_INTEGER" if isinstance(datatype, StructType): return self.get_struct_overlayable(datatype) raise RuntimeError("Unknown datatype") def _baked_nodeid(self, datatype, nodeIdStr): """Split a NodeId string of a type definition into the namespace index baked into the generated array and the bare id. Pinned namespaces (--namespaceMap) get the pinned index, which is what allows the array to be const. Otherwise the index of an explicit ns= prefix is kept and defaults to 0; the nodeset compiler rewrites it when the nodeset is loaded. Null NodeIds (e.g. a missing encoding id) stay in namespace 0.""" nsPrefix, bareId = splitNodeidNs(nodeIdStr) if not bareId: return "0", bareId pinned = self.namespaceMap.get(datatype.namespaceUri) if pinned is not None: return str(pinned), bareId return nsPrefix, bareId def print_datatype(self, datatype): nsIdx, bareNodeId = self._baked_nodeid(datatype, datatype.nodeId) typeid = "{{{}, {}}}".format(nsIdx, getNodeidTypeAndId(bareNodeId)) binNs, bareBinId = self._baked_nodeid(datatype, datatype.binaryEncodingId) binaryEncodingId = "{{{}, {}}}".format(binNs, getNodeidTypeAndId(bareBinId)) xmlNs, bareXmlId = self._baked_nodeid(datatype, datatype.xmlEncodingId) xmlEncodingId = "{{{}, {}}}".format(xmlNs, getNodeidTypeAndId(bareXmlId)) idName = makeCIdentifier(datatype.name) pointerfree = "true" if datatype.pointerfree else "false" # TODO: OptionSet is omitted because the type description is not generated as UA_DATATYPEKIND_ENUM isEnum = isinstance(datatype, EnumerationType) and not datatype.isOptionSet return "{\n" + \ " UA_TYPENAME(\"%s\") /* .typeName */\n" % idName + \ " " + typeid + ", /* .typeId */\n" + \ " " + binaryEncodingId + ", /* .binaryEncodingId */\n" + \ " " + xmlEncodingId + ", /* .xmlEncodingId */\n" + \ " sizeof(UA_" + idName + "), /* .memSize */\n" + \ " " + self.get_type_kind(datatype) + ", /* .typeKind */\n" + \ " " + pointerfree + ", /* .pointerFree */\n" + \ " " + self.get_type_overlayable(datatype) + ", /* .overlayable */\n" + \ " " + str(len(datatype.elements) if isEnum else len(datatype.members)) + ", /* .membersSize */\n" + \ " %s_members" % idName + " /* .members */\n" + \ "}" @staticmethod def print_members(datatype): idName = makeCIdentifier(datatype.name) # TODO: OptionSet is omitted because the type description is not generated as UA_DATATYPEKIND_ENUM isEnum = isinstance(datatype, EnumerationType) and not datatype.isOptionSet if (not isEnum and len(datatype.members) == 0) or (isEnum and len(datatype.elements) == 0): return "#define %s_members NULL" % (idName) isUnion = isinstance(datatype, StructType) and datatype.is_union members = "static const UA_DataTypeMember {}_members[{}] = {{".format(idName, len(datatype.elements) if isEnum else len(datatype.members)) before = None size = len(datatype.elements) if isEnum else len(datatype.members) if isEnum: # Print all enumerators as UA_DataTypeMember for i, (name, value) in enumerate(datatype.elements.items()): m = "\n{\n" m += " UA_TYPENAME(\"%s\") /* .memberName */\n" % name m += " (const UA_DataType *)(uintptr_t)({}), /* .memberType */\n".format(value) m += " 0, /* .padding */\n" m += " false, /* .isArray */\n" m += " false /* .isOptional */\n}" # Don't add a trailing comma for the last entry if i != size - 1: m += "," members += m else: # Print all structure fields as UA_DataTypeMember for i, member in enumerate(datatype.members): # Build the type name for the current field if not member.member_type.members and isinstance(member.member_type, StructType): type_name = "ExtensionObject" else: type_name = member.member_type.name # Build the type name for the previous field (used to calculate padding) if before: if not before.member_type.members and isinstance(before.member_type, StructType): type_name_before = "ExtensionObject" else: type_name_before = before.member_type.name # Build a valid identifier as member name with capital first letter member_name = makeCIdentifier(member.name) member_name_capital = member_name if len(member_name) > 0: member_name_capital = member_name[0].upper() + member_name[1:] m = "\n{\n" m += " UA_TYPENAME(\"%s\") /* .memberName */\n" % member_name_capital m += " &UA_{}[UA_{}_{}], /* .memberType */\n".format( member.member_type.outname.upper(), member.member_type.outname.upper(), makeCIdentifier(type_name.upper())) m += " " # Print code to calculate type specific padding for the member if not before and not isUnion: m += "0," elif isUnion: m += "offsetof(UA_{}, fields.{}),".format(idName, member_name) else: if member.is_array: m += "offsetof(UA_{}, {}Size)".format(idName, member_name) else: m += "offsetof(UA_{}, {})".format(idName, member_name) m += " - offsetof(UA_{}, {})".format(idName, makeCIdentifier(before.name)) if before.is_array or before.is_optional: m += " - sizeof(void *)," else: m += " - sizeof(UA_%s)," % makeCIdentifier(type_name_before) m += " /* .padding */\n" m += (" true" if member.is_array else " false") + ", /* .isArray */\n" m += (" true" if member.is_optional else " false") + " /* .isOptional */\n}" # Don't add a trailing comma for the last entry if i != size - 1: m += "," members += m before = member return members + "};" @staticmethod def print_datatype_ptr(datatype): return "&UA_" + datatype.outname.upper() + "[UA_" + makeCIdentifier( datatype.outname.upper() + "_" + datatype.name.upper()) + "]" def print_functions(self, datatype): idName = makeCIdentifier(datatype.name) funcs = "UA_INLINABLE( void\nUA_{}_init(UA_{} *p), {{\n memset(p, 0, sizeof(UA_{}));\n}})\n\n".format(idName, idName, idName) funcs += "UA_INLINABLE( UA_{} *\nUA_{}_new(void), {{\n return (UA_{}*)UA_new({});\n}})\n\n".format(idName, idName, idName, CGenerator.print_datatype_ptr(datatype)) if datatype.pointerfree == "true": funcs += "UA_INLINABLE( UA_StatusCode\nUA_{}_copy(const UA_{} *src, UA_{} *dst), {{\n *dst = *src;\n return UA_STATUSCODE_GOOD;\n}})\n\n".format(idName, idName, idName) funcs += "UA_INLINABLE( void\nUA_{}_clear(UA_{} *p), {{\n memset(p, 0, sizeof(UA_{}));\n}})\n".format(idName, idName, idName) else: for entry in whitelistFuncAttrWarnUnusedResult: if idName == entry: funcs += "UA_INTERNAL_FUNC_ATTR_WARN_UNUSED_RESULT " break funcs += "UA_INLINABLE( UA_StatusCode\nUA_{}_copy(const UA_{} *src, UA_{} *dst), {{\n return UA_copy(src, dst, {});\n}})\n\n".format(idName, idName, idName, self.print_datatype_ptr(datatype)) funcs += "UA_INLINABLE( void\nUA_{}_clear(UA_{} *p), {{\n UA_clear(p, {});\n}})\n\n".format(idName, idName, self.print_datatype_ptr(datatype)) funcs += "UA_INLINABLE( void\nUA_{}_delete(UA_{} *p), {{\n UA_delete(p, {});\n}})\n\n".format(idName, idName, self.print_datatype_ptr(datatype)) funcs += "UA_INLINABLE( UA_Boolean\nUA_{}_equal(const UA_{} *p1, const UA_{} *p2), {{\n return (UA_order(p1, p2, {}) == UA_ORDER_EQ);\n}})\n".format( idName, idName, idName, self.print_datatype_ptr(datatype)) return funcs @staticmethod def print_enum_typedef(enum, gen_doc=False): values = enum.elements.items() if enum.isOptionSet: elements = map(lambda kv: "#define " + makeCIdentifier("UA_" + enum.name.upper() + "_" + kv[0].upper()) + " " + kv[1], values) return "typedef " + enum.strDataType + " " + makeCIdentifier("UA_" + enum.name) + ";\n\n" + "\n".join(elements) else: elements = [makeCIdentifier("UA_" + enum.name.upper() + "_" + kv[0].upper()) + " = " + kv[1] for kv in values] if not gen_doc: elements.append(f"__UA_{makeCIdentifier(enum.name.upper())}_FORCE32BIT = 0x7fffffff") out = [] out.append("typedef enum {") for i,e in enumerate(elements): out.append(" " + e) if i < len(elements)-1: out[-1] += "," out.append(f"}} UA_{makeCIdentifier(enum.name)};") if not gen_doc: out.append(f"\nUA_STATIC_ASSERT(sizeof(UA_{makeCIdentifier(enum.name)}) == sizeof(UA_Int32), enum_must_be_32bit);") return "\n".join(out) @staticmethod def print_struct_typedef(struct): #generate enum option for union returnstr = "" if struct.is_union: #test = type("MyEnumOptionSet", (EnumOptionSet, object), {"foo": lambda self: "foo"}) obj = type('MyEnumOptionSet', (object,), {'isOptionSet': False, 'elements': OrderedDict(), 'name': struct.name+"Switch"}) obj.elements['None'] = str(0) count = 1 for member in struct.members: obj.elements[member.name] = str(count) count += 1 returnstr += CGenerator.print_enum_typedef(obj) returnstr += "\n\n" if len(struct.members) == 0: raise Exception("Structs with no members are filtered out. Why not here?") if struct.is_recursive: returnstr += "typedef struct UA_{} UA_{};\n".format(makeCIdentifier(struct.name), makeCIdentifier(struct.name)) returnstr += "struct UA_%s {\n" % makeCIdentifier(struct.name) else: returnstr += "typedef struct {\n" if struct.is_union: returnstr += " UA_%sSwitch switchField;\n" % struct.name returnstr += " union {\n" for member in struct.members: if not member.member_type.members and isinstance(member.member_type, StructType): type_name = "ExtensionObject" else: type_name = member.member_type.name if member.is_array: if struct.is_union: returnstr += " struct {\n " returnstr += " size_t %sSize;\n" % makeCIdentifier(member.name) if struct.is_union: returnstr += " " returnstr += " UA_{} *{};\n".format( makeCIdentifier(type_name), makeCIdentifier(member.name)) if struct.is_union: returnstr += " } " + makeCIdentifier(member.name) + ";\n" elif struct.is_union: returnstr += " UA_{} {};\n".format( makeCIdentifier(type_name), makeCIdentifier(member.name)) elif member.is_optional: returnstr += " UA_{} *{};\n".format( makeCIdentifier(type_name), makeCIdentifier(member.name)) else: returnstr += " UA_{} {};\n".format( makeCIdentifier(type_name), makeCIdentifier(member.name)) if struct.is_union: returnstr += " } fields;\n" if struct.is_recursive: return returnstr + "};" return returnstr + "} UA_%s;" % makeCIdentifier(struct.name) @staticmethod def print_datatype_typedef(datatype, gen_doc=False): if isinstance(datatype, EnumerationType): return CGenerator.print_enum_typedef(datatype, gen_doc) if isinstance(datatype, OpaqueType): return "typedef UA_" + datatype.base_type + " UA_%s;" % datatype.name if isinstance(datatype, StructType): return CGenerator.print_struct_typedef(datatype) raise RuntimeError("Type does not have an associated typedef") def write_definitions(self): self.fh = open(self.outfile + "_generated.h", 'w') self.fc = open(self.outfile + "_generated.c", 'w') self.filtered_types = self.iter_types(self.parser.types) self._validate_namespace_map() self.print_header() self.print_description_array() self.fh.close() self.fc.close() if self.gen_doc: self.fd = open(self.outfile + "_generated.rst", 'w') self.print_doc() self.fd.close() def printh(self, string): print(string, end='\n', file=self.fh) def printc(self, string): print(string, end='\n', file=self.fc) def printd(self, string): print(string, end='\n', file=self.fd) def iter_types(self, v): # Make a copy. We cannot delete from the map that is iterated over at # the same time. l = copy.deepcopy(v) # Keep only selected types? if len(self.parser.selected_types) > 0: for ns in v: for t in v[ns]: if t not in self.parser.selected_types: if ns in l and t in l[ns]: del l[ns][t] # Remove builtins? if self.parser.no_builtin: for ns in v: for t in v[ns]: if isinstance(v[ns][t], BuiltinType): if ns in l and t in l[ns]: del l[ns][t] # Remove types that are from other (imported) bsd files. # Track type names from imported namespaces so we can detect # cross-namespace name collisions (same C type name, different # OPC UA namespace). Those types must still appear in the type # array (they have their own NodeId), but we must not re-emit # the C typedef / inline helpers because the C symbol already # exists from the included dependency header. # IMPORTANT: the definitions must be identical; differing definitions # would cause a silent ABI mismatch since the same C struct is used for # both type array entries. self.cross_ns_duplicate_types = set() # Build name→Type map from existing (imported) types for comparison. # When the same name appears in multiple imported namespaces prefer the # non-builtin definition. existing_type_map = {} for ns in self.parser.existing_types: for t in self.parser.existing_types[ns]: et = self.parser.existing_types[ns][t] if t not in existing_type_map or isinstance(existing_type_map[t], BuiltinType): existing_type_map[t] = et if ns in l and t in l[ns]: del l[ns][t] for ns in list(l.keys()): for t in list(l.get(ns, {}).keys()): if t in existing_type_map: clash = existing_type_map[t] if not isinstance(clash, BuiltinType) and \ not _types_definition_equal(clash, l[ns][t]): raise RuntimeError( f"Type '{t}' in namespace '{ns}' has the same name " f"as an imported type but a different definition. " f"Cross-namespace duplicate type names are only " f"allowed when definitions are identical.\n" f" Imported from: {clash.namespaceUri}\n" f" Current: {ns}" ) self.cross_ns_duplicate_types.add(t) # Remove structs with no members for ns in v: for t in v[ns]: if isinstance(v[ns][t], StructType) and len(v[ns][t].members) == 0: if ns in l and t in l[ns]: del l[ns][t] return l def print_header(self): additionalHeaders = "" for arr in self.parser.existing_types_array: if arr == "UA_TYPES": continue # remove ua_ prefix if exists typeFile = arr.lower() typeFile = typeFile[typeFile.startswith("ua_") and len("ua_"):] additionalHeaders += """#include "%s_generated.h"\n""" % typeFile self.printh('''/********************************** * Autogenerated -- do not modify * **********************************/ #include #ifndef ''' + self.parser.outname.upper() + '''_GENERATED_H_ #define ''' + self.parser.outname.upper() + '''_GENERATED_H_ ''' + (additionalHeaders) + ''' _UA_BEGIN_DECLS ''') self.printh('''/** * Every type is assigned an index in an array containing the type descriptions. * These descriptions are used during type handling (copying, deletion, * binary encoding, ...). */''') totalCount = 0 for ns in self.filtered_types: totalCount += len(self.filtered_types[ns]) self.printh("#define UA_" + self.parser.outname.upper() + "_COUNT %s" % (str(totalCount))) if totalCount > 0: outUpper = self.parser.outname.upper() const_q = "const " if self._can_be_const() else "" if self._can_be_const(): self.printh(""" /* All namespace indices in the type array are fixed at generation time * (--namespaceMap). The array is const and can reside in read-only memory. * The server must assign exactly the baked indices to these namespace URIs * at runtime. The nodeset initializer verifies the URI/index pairs below. */""") self.printh("#define UA_" + outUpper + "_IS_CONST 1") # Preserve the URI/index association for nodeset initialization. # Array order and the nodeset output filename do not identify # which namespace a type belongs to. entries = [ '{{{}, UA_STRING_STATIC("{}")}}'.format( self.namespaceMap[ns], makeCLiteral(ns)) for ns in self._contributing_namespaces()] self.printh("#define UA_" + outUpper + "_NAMESPACE_MAP {" + ", ".join(entries) + "}") self.printh( "extern " + self.export_macro + " " + const_q + "UA_DataType UA_" + outUpper + "[UA_" + outUpper + "_COUNT];") typeIndex = 0 for ns in self.filtered_types: for t_name in self.filtered_types[ns]: t = self.filtered_types[ns][t_name] is_cross_ns_dup = t_name in self.cross_ns_duplicate_types if t.description == "": self.printh("\n/* " + t.name + " */") else: self.printh("\n/* " + t.name + ": " + t.description + " */") # For cross-namespace duplicates the C typedef and inline # helpers already exist from the imported dependency header. # We only emit the index constant here; the type array # entry is emitted in the .c file as usual. if not is_cross_ns_dup: if not isinstance(t, BuiltinType): self.printh(self.print_datatype_typedef(t) + "\n") self.printh("#define UA_" + makeCIdentifier(self.parser.outname.upper() + "_" + t.name.upper()) + " " + str(typeIndex)) typeIndex += 1 self.printh("") if not is_cross_ns_dup: self.printh(self.print_functions(t)) else: self.printh("#define UA_" + self.parser.outname.upper() + " NULL") self.printh(''' _UA_END_DECLS #endif /* %s_GENERATED_H_ */\n''' % self.parser.outname.upper()) def print_doc(self): for ns in self.filtered_types: for _, t_name in enumerate(self.filtered_types[ns]): t = self.filtered_types[ns][t_name] if isinstance(t, BuiltinType): continue self.printd(t.name) self.printd("^" * len(t.name)) self.printd(t.description) self.printd(".. code-block:: c\n") lines = self.print_datatype_typedef(t, True) for l in lines.splitlines(): self.printd(" " + l) self.printd("") def print_description_array(self): self.printc('''/********************************** * Autogenerated -- do not modify * **********************************/ #include "''' + self.parser.outname + '''_generated.h"''') totalCount = 0 for ns in self.filtered_types: totalCount += len(self.filtered_types[ns]) for _, t_name in enumerate(self.filtered_types[ns]): t = self.filtered_types[ns][t_name] self.printc("") self.printc("/* " + t.name + " */") self.printc(CGenerator.print_members(t)) if totalCount > 0: outUpper = self.parser.outname.upper() const_q = "const " if self._can_be_const() else "" self.printc( "{}UA_DataType UA_{}[UA_{}_COUNT] = {{".format(const_q, outUpper, outUpper)) for ns in self.filtered_types: for _, t_name in enumerate(self.filtered_types[ns]): t = self.filtered_types[ns][t_name] self.printc("/* " + t.name + " */") self.printc(self.print_datatype(t) + ",") self.printc("};\n") ########################################### # Execute with the command line arguments # ########################################### args = parser.parse_args() outname = args.outfile.split("/")[-1] inname = ', '.join(list(map(lambda x: x.name.split("/")[-1], args.type_bsd))) namespaceMap = {OPC_UA_NAMESPACE: 0} for m in args.namespace_map: [idx, ns] = m.split(':', 1) idx = int(idx) if ns in namespaceMap and namespaceMap[ns] != idx: parser.error("--namespaceMap pins {} to conflicting indices {} and {}." .format(ns, namespaceMap[ns], idx)) namespaceMap[ns] = idx parser = CSVBSDTypeParser(args.opaque_map, args.selected_types, args.no_builtin, outname, args.import_bsd, args.type_bsd, args.type_csv, args.type_xml, namespaceMap) parser.create_types() generator = CGenerator(parser, inname, args.outfile, args.internal, args.gen_doc, namespaceMap, args.export_macro) generator.write_definitions()