qgenada_A_mapper.py 14.5 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#
# (C) Semantix Information Technologies.
#
# Copyright 2014-2015 IB Krates <info@krates.ee>
#       QGenc code generator integration
#
# Semantix Information Technologies is licensing the code of the
# Data Modelling Tools (DMT) in the following dual-license mode:
#
# Commercial Developer License:
#       The DMT Commercial Developer License is the suggested version
# to use for the development of proprietary and/or commercial software.
# This version is for developers/companies who do not want to comply
# with the terms of the GNU Lesser General Public License version 2.1.
#
# GNU LGPL v. 2.1:
#       This version of DMT is the one to use for the development of
# applications, when you are willing to comply with the terms of the
# GNU Lesser General Public License version 2.1.
#
# Note that in both cases, there are no charges (royalties) for the
# generated code.
#
'''This contains the implementation of model level mapping
of ASN.1 constructs to C. It is used as a backend of Semantix's
code generator A.'''

import os
import sys
import re
31

32
import distutils.spawn as spawn
33
from typing import List
34

35
36
37
from ..commonPy import asnParser
from ..commonPy.utility import panic, inform
from ..commonPy.asnAST import AsnBool, AsnInt, AsnReal, AsnString, AsnEnumerated, AsnSequence, AsnSet, AsnChoice, AsnMetaMember, AsnSequenceOf, AsnSetOf
38
from ..commonPy.createInternalTypes import ScanChildren
39
from ..commonPy.cleanupNodes import SetOfBadTypenames
40
41
42
43
44

# The file written to
g_outputFile = None

# A map of the ASN.1 types defined so far
45
g_definedTypes = set()  # type: Set[asnParser.Typename]
46
47
48
49
50
51
52

g_octetStrings = 0

g_bHasStartupRunOnce = False


def Version():
53
    print("Code generator: " + "$Id: qgenada_A_mapper.py $")  # pragma: no cover
54
55
56
57
58
59
60
61
62


def CleanNameAsSimulinkWants(name):
    return re.sub(r'[^a-zA-Z0-9_]', '_', name)


# Especially for the C mapper, since we need to pass the complete ASN.1 files list to ASN1SCC,
# the second param is not asnFile, it is asnFiles

63
def OnStartup(unused_modelingLanguage: str, asnFiles: List[str], outputDir: str, unused_badTypes: SetOfBadTypenames) -> None:  # pylint: disable=invalid-sequence-index
64
    # print "Use ASN1SCC to generate the structures for '%s'" % asnFile
65
66
67
    asn1SccPath = spawn.find_executable('asn1.exe')
    if not asn1SccPath:
        panic("ASN1SCC seems to be missing from your system (asn1.exe not found in PATH).\n")  # pragma: no cover
68
69
    os.system(
        ("mono " if sys.argv[0].endswith('.py') and sys.platform.startswith('linux') else "") +
70
71
        "\"{}\" -wordSize 8 -typePrefix asn1Scc -c -uPER -o \"".format(asn1SccPath) +
        outputDir + "\" \"" + "\" \"".join(asnFiles) + "\"")
72
73
74
75
76
77
78
79
80
81
82
83
84
85
    os.system("rm -f \"" + outputDir + "\"/*.adb")

    global g_bHasStartupRunOnce
    if g_bHasStartupRunOnce:
        # Don't rerun, it has already done all the work
        # for all the ASN.1 files used
        return  # pragma: no cover
    else:
        g_bHasStartupRunOnce = True
    # outputFilename = modelingLanguage + "_" + os.path.basename(asnFile).replace(".","_") + ".m"
    # Changed at Maxime's request, 27/1/2011
    outputFilename = "Simulink_DataView_asn.m"
    inform("QGenAda_A_mapper: Creating file '%s'...", outputFilename)
    global g_outputFile
86
    outputDir += "../"
87
    g_outputFile = open(outputDir + outputFilename, 'w')
88
    g_definedTypes.clear()
89
90
    global g_octetStrings
    g_octetStrings = 0
91
    CreateDeclarationsForAllTypes(asnParser.g_names, asnParser.g_leafTypeDict)
92
93


94
def OnBasic(unused_nodeTypename, unused_node, unused_leafTypeDict):
95
    pass  # pragma: nocover
96
97


98
def OnSequence(unused_nodeTypename, unused_node, unused_leafTypeDict):
99
    pass  # pragma: nocover
100
101


102
def OnSet(unused_nodeTypename, unused_node, unused_leafTypeDict):
103
104
105
    pass  # pragma: nocover


106
def OnEnumerated(unused_nodeTypename, unused_node, unused_leafTypeDict):
107
    pass  # pragma: nocover
108
109


110
def OnSequenceOf(unused_nodeTypename, unused_node, unused_leafTypeDict):
111
    pass  # pragma: nocover
112
113


114
def OnSetOf(unused_nodeTypename, unused_node, unused_leafTypeDict):
115
116
117
    pass  # pragma: nocover


118
def OnChoice(unused_nodeTypename, unused_node, unused_leafTypeDict):
119
    pass  # pragma: nocover
120
121


122
def OnShutdown(unused_badTypes):
123
124
125
126
    pass


def MapInteger(node):
127
    if node._range[0] >= 0 and node._range[1] <= 255:
128
        return "uint8"
129
    elif node._range[0] >= -128 and node._range[1] <= 127:
130
        return "int8"
131
    elif node._range[0] >= 0 and node._range[1] <= 65535:
132
        return "uint16"
133
    elif node._range[0] >= -32768 and node._range[1] <= 32767:
134
        return "int16"
135
    elif node._range[0] >= 0:
136
137
138
139
140
141
142
        return "uint32"
    else:
        return "int32"


def CreateAlias(nodeTypename, mappedType, description):
    # Requirements have changed: Simulink has an issue with AliasType...
Thanassis Tsiodras's avatar
Thanassis Tsiodras committed
143
144
145
    g_outputFile.write("%s = Simulink.AliasType;\n" % CleanNameAsSimulinkWants(nodeTypename))
    g_outputFile.write("%s.BaseType = '%s';\n" % (CleanNameAsSimulinkWants(nodeTypename), mappedType))
    g_outputFile.write("%s.Description = '%s';\n\n" % (CleanNameAsSimulinkWants(nodeTypename), description))
146
147
148
149
    return


def DeclareCollection(node, name, internal):
150
    for i in range(0, node._range[-1]):
151
152
153
154
155
156
        g_outputFile.write('%s_member_%02d=Simulink.BusElement;\n' % (name, i))
        # Andreas(ESA) wants them to be called 'element_%02d'
        g_outputFile.write("%s_member_%02d.name='element_%02d';\n" % (name, i, i))
        g_outputFile.write("%s_member_%02d.DataType='%s';\n" % (name, i, internal))
        g_outputFile.write("%s_member_%02d.dimensions=1;\n\n" % (name, i))
    g_outputFile.write('%s_member_length=Simulink.BusElement;\n' % name)
157
    g_outputFile.write("%s_member_length.name='length';\n" % name)
158
159
160
161
162
    g_outputFile.write("%s_member_length.DataType='int32';\n" % name)
    g_outputFile.write("%s_member_length.dimensions=1;\n\n" % name)
    g_outputFile.write('%s=Simulink.Bus;\n' % name)
    g_outputFile.write("%s.Elements = " % name)
    g_outputFile.write('[')
163
    for i in range(0, node._range[-1]):
164
165
166
167
168
169
170
171
        g_outputFile.write("%s_member_%02d " % (name, i))
    g_outputFile.write('%s_member_length' % name)
    g_outputFile.write(']')
    g_outputFile.write(";\n\n")


def DeclareSimpleCollection(node, name, internal):
    g_outputFile.write('%s_member_data=Simulink.BusElement;\n' % name)
Thanassis Tsiodras's avatar
Thanassis Tsiodras committed
172
    g_outputFile.write("%s_member_data.name='element_data';\n" % name)
173
174
175
176
    g_outputFile.write("%s_member_data.DataType='%s';\n" % (name, internal))
    g_outputFile.write("%s_member_data.dimensions=%d;\n\n" % (name, node._range[-1]))

    bNeedLength = False
177
    if len(node._range) > 1 and node._range[0] != node._range[1]:
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
        bNeedLength = True

    if bNeedLength:
        g_outputFile.write('%s_member_length=Simulink.BusElement;\n' % name)
        g_outputFile.write("%s_member_length.name='length';\n" % name)
        g_outputFile.write("%s_member_length.DataType='int32';\n" % name)
        g_outputFile.write("%s_member_length.dimensions=1;\n\n" % name)

    g_outputFile.write('%s=Simulink.Bus;\n' % name)
    g_outputFile.write("%s.Elements = " % name)
    g_outputFile.write('[')
    g_outputFile.write("%s_member_data " % name)
    if bNeedLength:
        g_outputFile.write('%s_member_length' % name)
    g_outputFile.write(']')
    g_outputFile.write(";\n\n")


def CreateDeclarationForType(nodeTypename, names, leafTypeDict):
    if nodeTypename in g_definedTypes:
        return
199
200
    g_definedTypes.add(nodeTypename)
    results = []  # type: List[str]
201
202
203
204
205
206
207
208
209
210
211
212
213
214
    ScanChildren(nodeTypename, names[nodeTypename], names, results, isRoot=True, createInnerNodesInNames=True)
    inform("Prerequisites of %s", nodeTypename)
    for prereqNodeTypename in results:
        if prereqNodeTypename != '':
            inform("\t%s", prereqNodeTypename)
            CreateDeclarationForType(prereqNodeTypename, names, leafTypeDict)
    node = names[nodeTypename]
    if isinstance(node, AsnBool):
        CreateAlias(nodeTypename, "boolean", 'A simple BOOLEAN')
    elif isinstance(node, AsnInt):
        CreateAlias(nodeTypename, MapInteger(node), "range is %s" % str(node._range))
    elif isinstance(node, AsnReal):
        CreateAlias(nodeTypename, "double", "range is %s" % str(node._range))
    elif isinstance(node, AsnString):
215
        if not node._range:
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
            panic("QGenAda_A_mapper: string (in %s) must have a SIZE constraint!\n" % node.Location())  # pragma: no cover
        name = CleanNameAsSimulinkWants(nodeTypename)
        DeclareSimpleCollection(node, name, "uint8")
    elif isinstance(node, AsnEnumerated):
        g_outputFile.write("%% Values for %s:\n" % nodeTypename)
        for member in node._members:
            # member[0] is the enumerant name
            # member[1] is the integer value used (or None)
            if member[1] is not None:
                g_outputFile.write("%s_value_%s = %s;\n" % (CleanNameAsSimulinkWants(nodeTypename), CleanNameAsSimulinkWants(member[0]), member[1]))
            else:  # pragma: no cover
                panic("QGenAda_A_mapper: must have values for enumerants (%s)" % node.Location())  # pragma: no cover
        CreateAlias(nodeTypename, "int32", "values of ENUMERATED %s" % nodeTypename)
        g_outputFile.write("\n")
    elif isinstance(node, AsnSequence) or isinstance(node, AsnSet) or isinstance(node, AsnChoice):
        elemNo = 0
        if isinstance(node, AsnChoice):
            elemNo += 1
            name = "%s_elem%02d" % (CleanNameAsSimulinkWants(nodeTypename), elemNo)
            g_outputFile.write(name + "=Simulink.BusElement;\n")
            g_outputFile.write(name + ".name='choiceIdx';\n")
            g_outputFile.write(name + ".DataType='uint8';\n")
            g_outputFile.write(name + ".dimensions=1;\n\n")
        for child in node._members:
            elemNo += 1
            name = "%s_elem%02d" % (CleanNameAsSimulinkWants(nodeTypename), elemNo)
            g_outputFile.write(name + "=Simulink.BusElement;\n")
            g_outputFile.write(name + ".name='%s';\n" % CleanNameAsSimulinkWants(child[0]))

            # Since AliasType doesn't work well in the Matlab/Simulink typesystem,
            # we have to change the simple fields to their native types.
            # Trace all the AsnMetaMember chain to find out the leaf type...
            childNode = child[1]
            while isinstance(childNode, AsnMetaMember):
                childNode = names[childNode._containedType]

            # Now that you have the leaf type, use the native types wherever possible.
            # BUT! For octet strings, we have pre-declared (see Dependencies definitions)
            # octet_string_%d types, but ONLY for native AsnString, not for typedef'd ones...
            if isinstance(childNode, AsnBool):
                mappedType = 'boolean'
            elif isinstance(childNode, AsnInt):
                mappedType = MapInteger(childNode)
            elif isinstance(childNode, AsnReal):
                mappedType = 'double'
            elif isinstance(childNode, AsnString):
                if isinstance(child[1], AsnMetaMember):
                    mappedType = CleanNameAsSimulinkWants(child[1]._containedType)
                else:                                  # pragma: no cover
                    mappedType = child[1]._pseudoname  # pragma: no cover
            elif isinstance(childNode, AsnEnumerated):
                mappedType = 'int32'
            # These would not be possible: AsnSequence, AsnChoice, AsnSequenceOf,
            # since the parsing stage ends with automated pseudonames (through
            # AsnMetaMembers) for them. However, we resolve AsnMetaMembers in the
            # "while" above, so if we do meet one of them (now that the resolving
            # has been done), we must have started from an AsnMetaMember...
            # so we will use the containedType_t for reference.
            elif isinstance(childNode, (AsnSequence, AsnSequenceOf, AsnSet, AsnSetOf, AsnChoice)):

276
                # mappedType = CleanNameAsSimulinkWants(child[1]._containedType + "_t") XYZ
277
278
279
280
281
282
283
284
285
                mappedType = CleanNameAsSimulinkWants(child[1]._containedType)
            else:  # pragma: no cover
                panic("QGenAda_A_mapper: Unexpected category of child (%s)" % str(child[1]))  # pragma: no cover
            g_outputFile.write(name + ".DataType='%s';\n" % mappedType)
            # Used to be -1 for strings and metaMembers, but requirements have changed (again :-)
            g_outputFile.write(name + ".dimensions=1;\n\n")

        g_outputFile.write("%s = Simulink.Bus;\n" % CleanNameAsSimulinkWants(nodeTypename))
        g_outputFile.write("%s.Elements = " % CleanNameAsSimulinkWants(nodeTypename))
286
        if elemNo > 1:
287
            g_outputFile.write('[')
288
        for i in range(0, elemNo):
289
            g_outputFile.write("%s_elem%02d " % (CleanNameAsSimulinkWants(nodeTypename), i + 1))
290
        if elemNo > 1:
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
            g_outputFile.write(']')
        g_outputFile.write(";\n\n")
    elif isinstance(node, AsnSequenceOf) or isinstance(node, AsnSetOf):
        name = CleanNameAsSimulinkWants(nodeTypename)
        contained = node._containedType
        assert isinstance(contained, str)
        containedNode = contained
        while isinstance(containedNode, str):
            containedNode = names[containedNode]

        if isinstance(containedNode, AsnBool):
            DeclareSimpleCollection(node, name, 'boolean')
        elif isinstance(containedNode, AsnInt):
            DeclareSimpleCollection(node, name, MapInteger(containedNode))
        elif isinstance(containedNode, AsnReal):
            DeclareSimpleCollection(node, name, 'double')
        elif isinstance(containedNode, AsnString):
            DeclareCollection(node, name, CleanNameAsSimulinkWants(contained))
        elif isinstance(containedNode, AsnEnumerated):
            DeclareSimpleCollection(node, name, 'int32')
        # These are not possible: AsnSequence, AsnChoice, AsnSequenceOf, because we introduce
        # AsnMetaMember for them at the end of the parser. If we meet them here, it is
        # because resolving has been done (see while above), so we started from a string...
        elif isinstance(containedNode, (AsnSequence, AsnSequenceOf, AsnSet, AsnSetOf, AsnChoice)):

            DeclareCollection(node, name, CleanNameAsSimulinkWants(contained))
        else:  # pragma: no cover
            panic("QGenAda_A_mapper: Unexpected category of contained type (%s,%s)" % (node.Location(), str(contained)))  # pragma: no cover

    else:  # pragma: no cover
        panic("Unexpected ASN.1 type... Send this grammar to Semantix")  # pragma: no cover


def CreateDeclarationsForAllTypes(names, leafTypeDict):
325
    for nodeTypename in list(names.keys()):
326
        CreateDeclarationForType(nodeTypename, names, leafTypeDict)