Pr.py 14.2 KB
Newer Older
1
2
3
4
5
6
7
8
9
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
    OpenGEODE - A tiny SDL Editor for TASTE

    This module generates textual SDL code (PR format)
    by parsing the graphical symbols.

10
    Copyright (c) 2012-2016 European Space Agency
11
12
13
14
15
16
17
18

    Designed and implemented by Maxime Perrotin

    Contact: maxime.perrotin@esa.int
"""


import logging
Maxime Perrotin's avatar
Maxime Perrotin committed
19
20
from collections import deque
from itertools import chain
21
22
from singledispatch import singledispatch

23
import genericSymbols, sdlSymbols, Connectors
24
25
26

LOG = logging.getLogger(__name__)

Maxime Perrotin's avatar
Maxime Perrotin committed
27
28
29
30
31
32
33
34
35
36
37
38
__all__ = ['parse_scene', 'generate']


class Indent(deque):
    ''' Extension of the deque class to support automatic indenting '''
    indent = 0

    def append(self, string):
        ''' Redefinition of the append to insert the indent pattern '''
        super(Indent, self).append('    ' * Indent.indent + string)


39
40
41
def parse_scene(scene, full_model=False):
    ''' Return the PR string for a complete scene
        Optionally, also generate the SYSTEM structure, with channels, etc. '''
42
    pr_data = Indent()
43
44
45
    if full_model:
        # Generate a complete SDL system - to have everything in a single file
        # (1) get system name
46
47
        # (2) get signal directions from the connection of the process to env
        # (3) generate all the text
48
49
        processes = list(scene.processes)
        system_name = unicode(processes[0]) if processes else u'OpenGEODE'
50
51
52
        pr_data.append('SYSTEM {};'.format(system_name))
        Indent.indent += 1
        channels, routes = Indent(), Indent()
53
        for each in scene.texts:
54
            # Parse text areas to retrieve signal names USELESS
55
           pr = generate(each)
56
           pr_data.extend(pr)
57
58
59
60
        if processes:
            to_env = processes[0].connection.out_sig
            from_env = processes[0].connection.in_sig
            if to_env or from_env:
61
62
63
64
                channels.append('CHANNEL c')
                Indent.indent += 1

                routes.append('SIGNALROUTE r')
65
66
67
68
                if from_env:
                    from_txt = 'FROM ENV TO {} WITH {};'\
                               .format(system_name, from_env)
                    channels.append(from_txt)
69
                    Indent.indent += 1
70
                    routes.append(from_txt)
71
                    Indent.indent -= 1
72
73
74
75
                if to_env:
                    to_txt = 'FROM {} TO ENV WITH {};'\
                              .format(system_name, to_env)
                    channels.append(to_txt)
76
                    Indent.indent += 1
77
                    routes.append(to_txt)
78
                    Indent.indent -= 1
Maxime Perrotin's avatar
Maxime Perrotin committed
79
80
81
82
                Indent.indent -= 1
                channels.append('ENDCHANNEL;')
                Indent.indent += 1
                routes.append('CONNECT c AND r;')
83
            Indent.indent -= 1
84
85

        pr_data.extend(channels)
86
        pr_data.append('BLOCK {};'.format(system_name))
87
        Indent.indent += 1
88
        pr_data.extend(routes)
89
90
        for each in processes:
            pr_data.extend(generate(each))
91
        Indent.indent -= 1
92
        pr_data.append('ENDBLOCK;')
93
        Indent.indent -= 1
94
95
96
97
        pr_data.append('ENDSYSTEM;')

    else:
        for each in scene.processes:
98
            #pr_data.extend(generate(each))
Maxime Perrotin's avatar
Maxime Perrotin committed
99
100
101
102
            # Only one process is supported - return now because
            # the text areas must not be parsed - some may have been
            # generated automatically to display the list of signals
            # and external procedures when interface was generated by TASTE
103
            return list(generate(each))
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122

        for each in chain(scene.texts, scene.procs, scene.start):
            pr_data.extend(generate(each))
        for each in scene.floating_labels:
            pr_data.extend(generate(each))
        composite = set(scene.composite_states.keys())
        for each in scene.states:
            if each.is_composite():
                # Ignore via clause:
                statename = unicode(each).split()[0].lower()
                try:
                    composite.remove(statename)
                    sub_state = generate(each, composite=True, nextstate=False)
                    if sub_state:
                        sub_state.reverse()
                        pr_data.extendleft(sub_state)
                except KeyError:
                    pass
            pr_data.extend(generate(each, nextstate=False))
Maxime Perrotin's avatar
Maxime Perrotin committed
123
    return list(pr_data)
124
125
126
127
128
129
130
131
132
133
134
135
136
137


def cif_coord(name, symbol):
    ''' PR string for the CIF coordinates/size of a symbol '''
    return u'/* CIF {symb} ({x}, {y}), ({w}, {h}) */'.format(
            symb=name,
            x=int(symbol.scenePos().x()), y=int(symbol.scenePos().y()),
            w=int(symbol.boundingRect().width()),
            h=int(symbol.boundingRect().height()))


def hyperlink(symbol):
    ''' PR string for the optional hyperlink associated to a symbol '''
    return u"/* CIF Keep Specific Geode HyperLink '{}' */".format(
Maxime Perrotin's avatar
Maxime Perrotin committed
138
                                                         symbol.text.hyperlink)
139
140
141
142


def common(name, symbol):
    ''' PR string format that is shared by most symbols '''
Maxime Perrotin's avatar
Maxime Perrotin committed
143
144
145
    result = Indent()
    result.append(cif_coord(name, symbol))
    if symbol.text.hyperlink:
146
147
148
149
150
151
152
153
154
155
        result.append(hyperlink(symbol))
    result.append(u'{} {}{}'.format(name, unicode(symbol.text), ';'
                                if not symbol.comment else ''))
    if symbol.comment:
        result.extend(generate(symbol.comment))
    return result


def recursive_aligned(symbol):
    ''' Get the branch following symbol '''
Maxime Perrotin's avatar
Maxime Perrotin committed
156
157
    result = Indent()
    Indent.indent += 1
158
159
160
161
    next_symbol = symbol.next_aligned_symbol()
    while next_symbol:
        result.extend(generate(next_symbol))
        next_symbol = next_symbol.next_aligned_symbol()
Maxime Perrotin's avatar
Maxime Perrotin committed
162
    Indent.indent -= 1
163
164
165
166
    return result


@singledispatch
Maxime Perrotin's avatar
Maxime Perrotin committed
167
def generate(symbol, *args, **kwargs):
168
169
    ''' Generate text for a symbol, recursively or not - return a list of
        strings '''
Maxime Perrotin's avatar
Maxime Perrotin committed
170
171
172
    _ = symbol
    raise NotImplementedError('Unsupported AST construct: {}'
                              .format(type(symbol)))
Maxime Perrotin's avatar
Maxime Perrotin committed
173
    return Indent()
174
175
176


@generate.register(genericSymbols.Comment)
Maxime Perrotin's avatar
Maxime Perrotin committed
177
def _comment(symbol, **kwargs):
178
    ''' Optional comment linked to a symbol '''
Maxime Perrotin's avatar
Maxime Perrotin committed
179
180
181
    result = Indent()
    result.append(cif_coord('COMMENT', symbol))
    if symbol.text.hyperlink:
182
        result.append(hyperlink(symbol))
Maxime Perrotin's avatar
Maxime Perrotin committed
183
    result.append(u'COMMENT \'{}\';'.format(unicode(symbol.text)))
184
185
186
187
    return result


@generate.register(sdlSymbols.Input)
Maxime Perrotin's avatar
Maxime Perrotin committed
188
def _input(symbol, recursive=True, **kwargs):
189
190
191
192
193
194
195
    ''' Input symbol or branch if recursive is set '''
    result = common('INPUT', symbol)
    if recursive:
        result.extend(recursive_aligned(symbol))
    return result


196
197
198
199
200
201
202
203
204
@generate.register(sdlSymbols.ContinuousSignal)
def _continuous_signal(symbol, recursive=True, **kwargs):
    ''' "Provided" symbol or branch if recursive is set '''
    result = common('PROVIDED', symbol)
    if recursive:
        result.extend(recursive_aligned(symbol))
    return result


205
@generate.register(sdlSymbols.Connect)
Maxime Perrotin's avatar
Maxime Perrotin committed
206
def _connect(symbol, recursive=True, **kwargs):
207
208
209
210
211
212
213
214
    ''' Connect symbol or branch if recursive is set '''
    result = common('CONNECT', symbol)
    if recursive:
        result.extend(recursive_aligned(symbol))
    return result


@generate.register(sdlSymbols.Output)
Maxime Perrotin's avatar
Maxime Perrotin committed
215
def _output(symbol, **kwargs):
216
217
218
219
220
    ''' Output symbol '''
    return common('OUTPUT', symbol)


@generate.register(sdlSymbols.Decision)
Maxime Perrotin's avatar
Maxime Perrotin committed
221
def _decision(symbol, recursive=True, **kwargs):
222
223
224
225
    ''' Decision symbol or branch if recursive is set '''
    result = common('DECISION', symbol)
    if recursive:
        else_branch = None
Maxime Perrotin's avatar
Maxime Perrotin committed
226
        Indent.indent += 1
227
228
229
230
231
232
233
        for answer in symbol.branches():
            if unicode(answer).lower().strip() == 'else':
                else_branch = generate(answer)
            else:
                result.extend(generate(answer))
        if else_branch:
            result.extend(else_branch)
Maxime Perrotin's avatar
Maxime Perrotin committed
234
235
236
        Indent.indent -= 1
    result.append(u'ENDDECISION;')
    return result
237
238
239


@generate.register(sdlSymbols.DecisionAnswer)
Maxime Perrotin's avatar
Maxime Perrotin committed
240
def _decisionanswer(symbol, recursive=True, **kwargs):
241
    ''' Decision Answer symbol or branch if recursive is set '''
Maxime Perrotin's avatar
Maxime Perrotin committed
242
243
    result = Indent()
    result.append(cif_coord('ANSWER', symbol))
244
245
246
    ans = unicode(symbol)
    if ans.lower().strip() != u'else':
        ans = u'({})'.format(ans)
Maxime Perrotin's avatar
Maxime Perrotin committed
247
    if symbol.text.hyperlink:
248
        result.append(hyperlink(symbol))
Maxime Perrotin's avatar
Maxime Perrotin committed
249
    result.append(u'{}:'.format(ans))
250
251
252
253
254
255
    if recursive:
        result.extend(recursive_aligned(symbol))
    return result


@generate.register(sdlSymbols.Join)
Maxime Perrotin's avatar
Maxime Perrotin committed
256
def _join(symbol, **kwargs):
257
258
259
260
261
    ''' Join symbol '''
    return common('JOIN', symbol)


@generate.register(sdlSymbols.ProcedureStop)
Maxime Perrotin's avatar
Maxime Perrotin committed
262
def _procedurestop(symbol, **kwargs):
263
264
265
266
267
    ''' Procedure Stop symbol '''
    return common('RETURN', symbol)


@generate.register(sdlSymbols.Task)
Maxime Perrotin's avatar
Maxime Perrotin committed
268
def _task(symbol, **kwargs):
269
270
271
272
273
    ''' Task symbol '''
    return common('TASK', symbol)


@generate.register(sdlSymbols.ProcedureCall)
Maxime Perrotin's avatar
Maxime Perrotin committed
274
def _procedurecall(symbol, **kwargs):
275
    ''' Procedure call symbol '''
Maxime Perrotin's avatar
Maxime Perrotin committed
276
277
278
    result = Indent()
    result.append(cif_coord('PROCEDURECALL', symbol))
    if symbol.text.hyperlink:
279
280
281
        result.append(hyperlink(symbol))
    result.append(u'CALL {}{}'.format(unicode(symbol.text), ';'
                                      if not symbol.comment else ''))
Maxime Perrotin's avatar
Maxime Perrotin committed
282
283
    if symbol.comment:
        result.extend(generate(symbol.comment))
284
285
286
287
    return result


@generate.register(sdlSymbols.TextSymbol)
Maxime Perrotin's avatar
Maxime Perrotin committed
288
def _textsymbol(symbol, **kwargs):
289
    ''' Text Area symbol '''
Maxime Perrotin's avatar
Maxime Perrotin committed
290
291
292
    result = Indent()
    result.append(cif_coord('TEXT', symbol))
    if symbol.text.hyperlink:
293
294
        result.append(hyperlink(symbol))
    result.append(unicode(symbol.text))
Maxime Perrotin's avatar
Maxime Perrotin committed
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
    result.append(u'/* CIF ENDTEXT */')
    return result


@generate.register(sdlSymbols.Label)
def _label(symbol, recursive=True, **kwargs):
    ''' Label symbol or branch if recursive is set '''
    result = Indent()
    result.append(cif_coord('LABEL', symbol))
    if symbol.text.hyperlink:
        result.append(hyperlink(symbol))
    if symbol.common_name == 'floating_label':
        result.append(u'CONNECTION {}:'.format(unicode(symbol)))
        if recursive:
            result.extend(recursive_aligned(symbol))
        result.append(u'/* CIF End Label */')
        result.append(u'ENDCONNECTION;')
    else:
        result.append(u'{}:'.format(unicode(symbol)))
314
315
316
317
    return result


@generate.register(sdlSymbols.State)
318
319
def _state(symbol, recursive=True, nextstate=True, composite=False, cpy=False,
           **kwargs):
Maxime Perrotin's avatar
Maxime Perrotin committed
320
    ''' State/Nextstate symbol or branch if recursive is set '''
321
    if nextstate and symbol.hasParent:
Maxime Perrotin's avatar
Maxime Perrotin committed
322
        result = common('NEXTSTATE', symbol)
323
    elif not composite and symbol.hasParent and not cpy \
Maxime Perrotin's avatar
Maxime Perrotin committed
324
325
326
327
328
329
330
331
332
333
            and not [each for each in symbol.childSymbols()
            if not isinstance(each, genericSymbols.Comment)]:
        # If nextstate has no child, don't generate anything
        result = []
    elif not composite:
        result = common('STATE', symbol)
        if recursive:
            Indent.indent += 1
            # Generate code for INPUT and CONNECT symbols
            for each in (symb for symb in symbol.childSymbols()
334
335
                         if isinstance(symb, (sdlSymbols.Input,
                                              sdlSymbols.ContinuousSignal))):
Maxime Perrotin's avatar
Maxime Perrotin committed
336
337
338
339
340
341
                result.extend(generate(each))
            Indent.indent -= 1
        result.append(u'ENDSTATE;')
    else:
        # Generate code for a nested state
        result = Indent()
342
        agg = ' AGGREGATION' if symbol.nested_scene.is_aggregation() else ''#if not list(symbol.nested_scene.start) else ''
343
        result.append('STATE{} {};'.format(agg, unicode(symbol).split()[0]))
Maxime Perrotin's avatar
Maxime Perrotin committed
344
        result.append('SUBSTRUCTURE')
Maxime Perrotin's avatar
Maxime Perrotin committed
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
        Indent.indent += 1
        entry_points, exit_points = [], []
        for each in symbol.nested_scene.start:
            if unicode(each):
                entry_points.append(unicode(each))
        for each in symbol.nested_scene.returns:
            if unicode(each) != u'no_name':
                exit_points.append(unicode(each))
        if entry_points:
            result.append(u'in ({});'.format(','.join(entry_points)))
        if exit_points:
            result.append(u'out ({});'.format(','.join(exit_points)))
        Indent.indent += 1
        result.extend(parse_scene(symbol.nested_scene))
        Indent.indent -= 1
        Indent.indent -= 1
        result.append(u'ENDSUBSTRUCTURE;')
    return result
363
364
365


@generate.register(sdlSymbols.Process)
Maxime Perrotin's avatar
Maxime Perrotin committed
366
367
368
369
370
371
372
373
374
def _process(symbol, recursive=True, **kwargs):
    ''' Process symbol and inner content if recursive is set '''
    result = common('PROCESS', symbol)
    if recursive and symbol.nested_scene:
        Indent.indent += 1
        result.extend(parse_scene(symbol.nested_scene))
        Indent.indent -= 1
    result.append(u'ENDPROCESS {};'.format(unicode(symbol)))
    return result
375
376
377


@generate.register(sdlSymbols.Procedure)
Maxime Perrotin's avatar
Maxime Perrotin committed
378
def _procedure(symbol, recursive=True, **kwargs):
379
    ''' Procedure symbol or branch if recursive is set '''
Maxime Perrotin's avatar
Maxime Perrotin committed
380
381
382
383
384
385
386
    result = common('PROCEDURE', symbol)
    if recursive and symbol.nested_scene:
        Indent.indent += 1
        result.extend(parse_scene(symbol.nested_scene))
        Indent.indent -= 1
    result.append(u'ENDPROCEDURE;'.format(unicode(symbol)))
    return result
387
388
389


@generate.register(sdlSymbols.Start)
Maxime Perrotin's avatar
Maxime Perrotin committed
390
def _start(symbol, recursive=True, **kwargs):
391
    ''' START symbol or branch if recursive is set '''
Maxime Perrotin's avatar
Maxime Perrotin committed
392
393
394
395
396
397
398
399
400
401
402
    result = Indent()
    result.append(cif_coord('START', symbol))
    result.append(u'START{via}{comment}'
                  .format(via=(' ' + unicode(symbol) + ' ')
                          if unicode(symbol).replace('START', '') else '',
                          comment=';' if not symbol.comment else ''))
    if symbol.comment:
        result.extend(generate(symbol.comment))
    if recursive:
        result.extend(recursive_aligned(symbol))
    return result
403
404
405
406
407
408
409


@generate.register(Connectors.Signalroute)
def _channel(symbol, recursive=True, **kwargs):
    ''' Signalroute at block level '''
    result = Indent()
    result.append('SIGNALROUTE c')
410
    Indent.indent += 1
411
    if symbol.out_sig:
412
        result.append('FROM {} TO ENV WITH {};'.format(unicode(symbol.parent),
413
414
                                                       symbol.out_sig))
    if symbol.in_sig:
415
        result.append('FROM ENV TO {} WITH {};'.format(unicode(symbol.parent),
416
                                                       symbol.in_sig))
417
    Indent.indent -= 1
418
419
420
    return result