qgenada_A_mapper.py 15.1 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, Union
34

35
36
from ..commonPy import asnParser
from ..commonPy.utility import panic, inform
37
from ..commonPy.createInternalTypes import ScanChildren
38
from ..commonPy.cleanupNodes import SetOfBadTypenames
39
40
41
42
43
from ..commonPy.asnAST import (
    AsnBasicNode, AsnSequenceOrSet, AsnSequenceOrSetOf, AsnBool, AsnInt,
    AsnReal, AsnString, AsnEnumerated, AsnSequence, AsnSet, AsnChoice,
    AsnMetaMember, AsnSequenceOf, AsnSetOf, AsnNode)
from ..commonPy.asnParser import AST_Leaftypes, AST_Lookup
44
45
46
47
48

# The file written to
g_outputFile = None

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

g_octetStrings = 0

g_bHasStartupRunOnce = False


56
def Version() -> None:
57
    print("Code generator: " + "$Id: qgenada_A_mapper.py $")  # pragma: no cover
58
59


60
def CleanNameAsSimulinkWants(name: str) -> str:
61
62
63
64
65
66
    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

67
def OnStartup(unused_modelingLanguage: str, asnFiles: List[str], outputDir: str, unused_badTypes: SetOfBadTypenames) -> None:  # pylint: disable=invalid-sequence-index
68
    # print "Use ASN1SCC to generate the structures for '%s'" % asnFile
69
70
71
    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
72
73
    os.system(
        ("mono " if sys.argv[0].endswith('.py') and sys.platform.startswith('linux') else "") +
74
75
        "\"{}\" -wordSize 8 -typePrefix asn1Scc -c -uPER -o \"".format(asn1SccPath) +
        outputDir + "\" \"" + "\" \"".join(asnFiles) + "\"")
76
77
78
79
80
81
82
83
84
85
86
87
88
89
    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
90
    outputDir += "../"
91
    g_outputFile = open(outputDir + outputFilename, 'w')
92
    g_definedTypes.clear()
93
94
    global g_octetStrings
    g_octetStrings = 0
95
    CreateDeclarationsForAllTypes(asnParser.g_names, asnParser.g_leafTypeDict)
96
97


98
def OnBasic(unused_nodeTypename: str, unused_node: AsnBasicNode, unused_leafTypeDict: AST_Leaftypes) -> None:
99
    pass  # pragma: nocover
100
101


102
def OnSequence(unused_nodeTypename: str, unused_node: AsnSequenceOrSet, unused_leafTypeDict: AST_Leaftypes) -> None:
103
    pass  # pragma: nocover
104
105


106
def OnSet(unused_nodeTypename: str, unused_node: AsnSequenceOrSet, unused_leafTypeDict: AST_Leaftypes) -> None:
107
108
109
    pass  # pragma: nocover


110
def OnEnumerated(unused_nodeTypename: str, unused_node: AsnEnumerated, unused_leafTypeDict: AST_Leaftypes) -> None:
111
    pass  # pragma: nocover
112
113


114
def OnSequenceOf(unused_nodeTypename: str, unused_node: AsnSequenceOrSetOf, unused_leafTypeDict: AST_Leaftypes) -> None:
115
    pass  # pragma: nocover
116
117


118
def OnSetOf(unused_nodeTypename: str, unused_node: AsnSequenceOrSetOf, unused_leafTypeDict: AST_Leaftypes) -> None:
119
120
121
    pass  # pragma: nocover


122
def OnChoice(unused_nodeTypename: str, unused_node: AsnChoice, unused_leafTypeDict: AST_Leaftypes) -> None:
123
    pass  # pragma: nocover
124
125


126
def OnShutdown(unused_badTypes: SetOfBadTypenames) -> None:
127
128
129
    pass


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


145
def CreateAlias(nodeTypename: str, mappedType: str, description: str) -> None:
146
    # Requirements have changed: Simulink has an issue with AliasType...
Thanassis Tsiodras's avatar
Thanassis Tsiodras committed
147
148
149
    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))
150
151


152
def DeclareCollection(node: AsnSequenceOrSetOf, name: str, internal: str) -> None:
153
    for i in range(0, node._range[-1]):
154
155
156
157
158
159
        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)
160
    g_outputFile.write("%s_member_length.name='length';\n" % name)
161
162
163
164
165
    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('[')
166
    for i in range(0, node._range[-1]):
167
168
169
170
171
172
        g_outputFile.write("%s_member_%02d " % (name, i))
    g_outputFile.write('%s_member_length' % name)
    g_outputFile.write(']')
    g_outputFile.write(";\n\n")


173
def DeclareSimpleCollection(node: Union[AsnString, AsnSequenceOf, AsnSetOf], name: str, internal: str) -> None:
174
    g_outputFile.write('%s_member_data=Simulink.BusElement;\n' % name)
Thanassis Tsiodras's avatar
Thanassis Tsiodras committed
175
    g_outputFile.write("%s_member_data.name='element_data';\n" % name)
176
177
178
179
    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
180
    if len(node._range) > 1 and node._range[0] != node._range[1]:
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")


199
def CreateDeclarationForType(nodeTypename: str, names: AST_Lookup, leafTypeDict: AST_Leaftypes) -> None:
200
201
    if nodeTypename in g_definedTypes:
        return
202
203
    g_definedTypes.add(nodeTypename)
    results = []  # type: List[str]
204
205
206
207
208
209
210
211
212
213
214
215
216
217
    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):
218
        if not node._range:
219
220
221
222
223
224
225
226
227
228
229
230
231
232
            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")
233
    elif isinstance(node, (AsnSequence, AsnSet, AsnChoice)):
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
276
277
278
        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)):

279
                # mappedType = CleanNameAsSimulinkWants(child[1]._containedType + "_t") XYZ
280
281
282
283
284
285
286
287
288
                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))
289
        if elemNo > 1:
290
            g_outputFile.write('[')
291
        for i in range(0, elemNo):
292
            g_outputFile.write("%s_elem%02d " % (CleanNameAsSimulinkWants(nodeTypename), i + 1))
293
        if elemNo > 1:
294
295
            g_outputFile.write(']')
        g_outputFile.write(";\n\n")
296
    elif isinstance(node, (AsnSequenceOf, AsnSetOf)):
297
298
299
        name = CleanNameAsSimulinkWants(nodeTypename)
        contained = node._containedType
        assert isinstance(contained, str)
300
        containedNode = contained  # type: Union[AsnNode, str]
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
        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


327
def CreateDeclarationsForAllTypes(names: AST_Lookup, leafTypeDict: AST_Leaftypes) -> None:
328
    for nodeTypename in list(names.keys()):
329
        CreateDeclarationForType(nodeTypename, names, leafTypeDict)