Renderer.py 14.8 KB
Newer Older
Maxime Perrotin's avatar
Maxime Perrotin committed
1
#!/usr/bin/env python3
Maxime Perrotin's avatar
Maxime Perrotin committed
2
3
4
5
6
7
8
# -*- coding: utf-8 -*-

"""
    OpenGEODE - A tiny, free SDL Editor for TASTE

    SDL is the Specification and Description Language (Z100 standard from ITU)

9
    Copyright (c) 2012-2020 European Space Agency
Maxime Perrotin's avatar
Maxime Perrotin committed
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

    Designed and implemented by Maxime Perrotin

    Contact: maxime.perrotin@esa.int

    This module is responsible for transforming AST elements to actual symbols

    It is separated from the main SDL_Scene class as the rendering can
    be done on any scene (e.g. clipboard).

    There is a single rendering function for all SDL construct, and a dispatch
    machanism (using the Python3-backported feature called singledispatch).

    Rendering can be done for single elements (returns the symbol) or for
    complete diagrams.

    This rendering capability is separated from the AST definition (ogAST.py)
    so that the AST module is kept independent from any graphical backend and
    is not related to Pyside.

    When rendering a (set of) symbol(s), update text autocompletion list(s).
"""

import logging
Maxime Perrotin's avatar
Maxime Perrotin committed
34
from itertools import chain
Maxime Perrotin's avatar
Maxime Perrotin committed
35
36
37
38
39
from functools import singledispatch

from .ogParser import type_name
from . import ogAST, sdlSymbols, genericSymbols

Maxime Perrotin's avatar
Maxime Perrotin committed
40
41
42

LOG = logging.getLogger(__name__)

Maxime Perrotin's avatar
Maxime Perrotin committed
43
44
45
46
47
48
49
50
__all__ = ['render', 'add_to_scene']


def add_to_scene(item, scene):
    ''' Add item to a scene after verifying that the scene allows it '''
    if type(item) in scene.allowed_symbols:
        scene.addItem(item)
    else:
51
        #print type(item), scene.allowed_symbols, scene.context
Maxime Perrotin's avatar
Maxime Perrotin committed
52
        raise TypeError('This symbol does not fit the current scene')
Maxime Perrotin's avatar
Maxime Perrotin committed
53

Maxime Perrotin's avatar
Maxime Perrotin committed
54

Maxime Perrotin's avatar
Maxime Perrotin committed
55
56
57
58
59
60
61
62
63
64
@singledispatch
def render(ast, scene, parent, states, terminators=None):
    ''' Render a transition action symbol on the scene '''
    _, _, _, _ = scene, parent, states, terminators
    # Default behaviour is to raise an exception, if there is no
    # rendering function for a given symbol. Otherwise the dispatch
    # mechanism forwards the call to a registered function (see below)
    raise TypeError('[Renderer] Unsupported symbol in branch: ' + repr(ast))


65
66
67
68
69
70
@render.register(ogAST.Block)
def _block(ast, scene):
    ''' Render a block, containing a set of process symbols '''
    top_level = []
    for each in ast.processes:
        top_level.append(render(each, scene))
71
72
        if each.instance_of_ref:
            top_level.append(render(each.instance_of_ref, scene))
73
74
75
    for each in ast.parent.text_areas:
        # Sytem level may contain text areas with signal definitions, etc.
        top_level.append(render(each, scene))
76
77
78
    if not ast.parent.text_areas:
        # If signals are declared outside from a textbox, create one
        signals = ["signal {si[name]}{param};\n".format(si=sig,
79
80
           param=('(' + sig['type'].ReferencedTypeName.replace('-', '_') + ')')
                 if 'type' in sig else '')
81
             for sig in ast.parent.signals]
82
        procedures = ["procedure {proc.inputString};\n{optfpar}external;\n"
83
                      .format(proc=proc,
84
85
86
                              optfpar="fpar\n    " + u",\n    ".join
                              ([u"{direc} {fp[name]} {asn1}"
                                .format(fp=fpar,
87
88
89
                                        direc="in"
                                           if fpar['direction']=='in'
                                           else 'in/out',
Maxime Perrotin's avatar
Maxime Perrotin committed
90
                                        asn1=getattr(fpar['type'],
91
                                           'ReferencedTypeName', 'TYPE_ERROR')
Maxime Perrotin's avatar
Maxime Perrotin committed
92
                                           .replace('-', '_'))
93
94
                                for fpar in proc.fpar]) + ';\n'
                                    if proc.fpar else '')
95
96
97
98
99
100
101
102
                        for proc in ast.parent.procedures]
        if signals or procedures:
            text_area = ogAST.TextArea()
            text_area.inputString = "{}\n\n{}".format('\n'.join(signals),
                                                      '\n'.join(procedures))
            text_area.pos_x = scene.itemsBoundingRect().width()
            text_area.pos_y = scene.itemsBoundingRect().y() + 10
            top_level.append(render(text_area, scene))
103
104
105
    return top_level


Maxime Perrotin's avatar
Maxime Perrotin committed
106
@render.register(ogAST.Process)
Maxime Perrotin's avatar
Maxime Perrotin committed
107
def _process(ast, scene, **_):
108
    ''' Render a Process symbol (in a BLOCK diagram) '''
109
110
111
112
    if ast.process_type:
        symbol = sdlSymbols.ProcessType(ast, ast)
    else:
        symbol = sdlSymbols.Process(ast, ast)
Maxime Perrotin's avatar
Maxime Perrotin committed
113
    add_to_scene(symbol, scene)
114
    return symbol
Maxime Perrotin's avatar
Maxime Perrotin committed
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134


@render.register(ogAST.Automaton)
def _automaton(ast, scene):
    ''' Render graphical elements of a process or procedure '''
    top_level_symbols = []
    # Render text areas (DCL declarations, etc.)
    for text in ast.textAreas:
        top_level_symbols.append(render(text, scene))

    # Render procedures symbols
    top_level_symbols.extend(
            [render(proc, scene)
                            for proc in ast.inner_procedures
                            if not proc.external])

    # Render the start symbol
    if ast.start:
        top_level_symbols.append(render(ast.start, scene, ast.states))

135
136
137
138
    # Render named start symbols in nested states
    for each in ast.named_start:
        top_level_symbols.append(render(each, scene, ast.states))

Maxime Perrotin's avatar
Maxime Perrotin committed
139
140
141
142
143
    # Render floating labels
    for label in ast.floating_labels:
        top_level_symbols.append(render(label, scene, ast.states))

    # Render floating states
Maxime Perrotin's avatar
Maxime Perrotin committed
144
    nested_states = []
Maxime Perrotin's avatar
Maxime Perrotin committed
145
146
147
148
149
    for state in ast.states:
        # Create only floating states
        try:
            new_state = render(state, scene=scene, states=ast.states,
                               terminators=ast.parent.terminators)
150
            if new_state.nested_scene:
Maxime Perrotin's avatar
Maxime Perrotin committed
151
                if str(new_state).lower() in nested_states:
152
153
                    new_state.nested_scene = None
                else:
Maxime Perrotin's avatar
Maxime Perrotin committed
154
                    nested_states.append(str(new_state).lower())
Maxime Perrotin's avatar
Maxime Perrotin committed
155
156
157
158
159
        except TypeError:
            # Discard terminators (see _state function for explanation)
            pass
        else:
            top_level_symbols.append(new_state)
160
161

    # If the source .pr contained FPAR outside a textbox, create one
162
    if ast.parent.fpar and not any(x.fpar for x in ast.textAreas):
163
164
165
166
167
168
169
170
171
        text_area = ogAST.TextArea()
        fpars = ('{} {}'.format(fp['name'],
                                type_name(fp['type']).replace('-', '_'))
                    for fp in ast.parent.fpar)
        text_area.inputString = ("-- Formal parameters\n"
                                "fpar {};".format(',\n          '.join(fpars)))
        text_area.pos_x = scene.itemsBoundingRect().x() - 200
        text_area.pos_y = scene.itemsBoundingRect().y()
        top_level_symbols.append(render(text_area, scene))
Maxime Perrotin's avatar
Maxime Perrotin committed
172
173
174
175
176
177
178
179
180
181
182
    return top_level_symbols


@render.register(ogAST.State)
def _state(ast, scene, states, terminators, parent=None):
    ''' Render a floating state and its inputs '''
    _ = parent
    # Discard the state if it is a terminator too as it is not a floating
    # state in that case: it will be rendered together with all its (possible)
    # INPUT children in the render_terminator function.
    for term in terminators:
183
        state_label = ast.via or ast.inputString
Maxime Perrotin's avatar
Maxime Perrotin committed
184
185
186
        if(term.kind == 'next_state' and
                term.pos_x == ast.pos_x and
                term.pos_y == ast.pos_y and
187
                term.inputString == state_label):
Maxime Perrotin's avatar
Maxime Perrotin committed
188
189
190
            raise TypeError('This state is a terminator')
    new_state = sdlSymbols.State(parent=None, ast=ast)
    if new_state not in scene.items():
Maxime Perrotin's avatar
Maxime Perrotin committed
191
        add_to_scene(new_state, scene)
Maxime Perrotin's avatar
Maxime Perrotin committed
192

193
    for exit in chain(ast.inputs, ast.connects, ast.continuous_signals):
Maxime Perrotin's avatar
Maxime Perrotin committed
194
        render(exit, scene=scene, parent=new_state, states=states)
195
196
197

    new_state.nested_scene = ast.composite or ogAST.CompositeState()

Maxime Perrotin's avatar
Maxime Perrotin committed
198
199
200
201
202
203
204
205
    return new_state


@render.register(ogAST.Procedure)
def _procedure(ast, scene, parent=None, states=None):
    ''' Add a procedure symbol to the scene '''
    _, _ = parent, states
    proc_symbol = sdlSymbols.Procedure(ast, ast)
Maxime Perrotin's avatar
Maxime Perrotin committed
206
    add_to_scene(proc_symbol, scene)
Maxime Perrotin's avatar
Maxime Perrotin committed
207
208
209
210
211
212
213
214
    return proc_symbol


@render.register(ogAST.TextArea)
def _text_area(ast, scene, parent=None, states=None):
    ''' Render a text area from the AST '''
    _, _ = parent, states
    text = sdlSymbols.TextSymbol(ast)
Maxime Perrotin's avatar
Maxime Perrotin committed
215
    add_to_scene(text, scene)
Maxime Perrotin's avatar
Maxime Perrotin committed
216
217
218
219
220
221
222
223
    return text


@render.register(ogAST.Start)
def _start(ast, scene, states, parent=None):
    ''' Add the start symbol to a scene '''
    _ = parent
    start_symbol = sdlSymbols.Start(ast)
Maxime Perrotin's avatar
Maxime Perrotin committed
224
    add_to_scene(start_symbol, scene)
Maxime Perrotin's avatar
Maxime Perrotin committed
225
    if ast.transition:
Maxime Perrotin's avatar
Maxime Perrotin committed
226
        render(ast.transition, scene=scene, parent=start_symbol, states=states)
227
228
229
230
    return start_symbol


@render.register(ogAST.CompositeState_start)
Maxime Perrotin's avatar
Maxime Perrotin committed
231
def _composite_start(ast, scene, states, parent=None):
232
233
234
    ''' Add an editable start symbol to a scene (in composite states) '''
    _ = parent
    start_symbol = sdlSymbols.StateStart(ast)
Maxime Perrotin's avatar
Maxime Perrotin committed
235
    add_to_scene(start_symbol, scene)
236
237
238
    if ast.transition:
        render(ast.transition,
                      scene=scene, parent=start_symbol, states=states)
Maxime Perrotin's avatar
Maxime Perrotin committed
239
240
241
242
243
244
245
246
    return start_symbol


@render.register(ogAST.Procedure_start)
def _procedure_start(ast, scene, states, parent=None):
    ''' Add the procedure start symbol to a scene '''
    _ = parent
    start_symbol = sdlSymbols.ProcedureStart(ast)
Maxime Perrotin's avatar
Maxime Perrotin committed
247
    add_to_scene(start_symbol, scene)
Maxime Perrotin's avatar
Maxime Perrotin committed
248
    if ast.transition:
Maxime Perrotin's avatar
Maxime Perrotin committed
249
        render(ast.transition, scene=scene, parent=start_symbol, states=states)
Maxime Perrotin's avatar
Maxime Perrotin committed
250
251
252
253
254
255
256
257
258
    return start_symbol


@render.register(ogAST.Floating_label)
def _floating_label(ast, scene, states, parent=None):
    ''' Add a Floating label from the AST to the scene '''
    _ = parent
    lab = sdlSymbols.Label(parent=None, ast=ast)
    if lab not in scene.items():
Maxime Perrotin's avatar
Maxime Perrotin committed
259
        add_to_scene(lab, scene)
260
    lab.pos_x, lab.pos_y = ast.pos_x, ast.pos_y
Maxime Perrotin's avatar
Maxime Perrotin committed
261
    if ast.transition:
Maxime Perrotin's avatar
Maxime Perrotin committed
262
        render(ast.transition, scene=scene, parent=lab, states=states)
Maxime Perrotin's avatar
Maxime Perrotin committed
263
264
265
266
267
268
    return lab


@render.register(ogAST.Transition)
def _transition(ast, scene, parent, states):
    ''' Add a transition to a scene '''
Maxime Perrotin's avatar
Maxime Perrotin committed
269
    for each in ast.actions:
Maxime Perrotin's avatar
Maxime Perrotin committed
270
        # pylint: disable=E1111
Maxime Perrotin's avatar
Maxime Perrotin committed
271
        parent = render(each, scene=scene, parent=parent, states=states)
Maxime Perrotin's avatar
Maxime Perrotin committed
272
273

    if ast.terminator:
Maxime Perrotin's avatar
Maxime Perrotin committed
274
        render(ast.terminator, scene=scene, parent=parent, states=states)
Maxime Perrotin's avatar
Maxime Perrotin committed
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298


@render.register(ogAST.Comment)
def _comment(ast, scene, parent, states=None):
    ''' Create a COMMENT symbol - note: relative positionning is lost '''
    _, _ = scene, states
    return genericSymbols.Comment(parent, ast=ast)


@render.register(ogAST.Task)
def _task(ast, scene, parent, states):
    ''' Create a TASK symbol '''
    _, _ = scene, states
    return sdlSymbols.Task(parent, ast=ast)


@render.register(ogAST.Output)
def _output(ast, scene, parent, states):
    ''' Create an OUTPUT or PROCEDURE CALL symbol '''
    _, _ = scene, states
    return sdlSymbols.Output(parent, ast=ast)


@render.register(ogAST.ProcedureCall)
Maxime Perrotin's avatar
Maxime Perrotin committed
299
def _procedure_call(ast, scene, parent, states):
Maxime Perrotin's avatar
Maxime Perrotin committed
300
301
302
303
304
305
306
307
308
309
310
    ''' Create an OUTPUT or PROCEDURE CALL symbol '''
    _, _ = scene, states
    return sdlSymbols.ProcedureCall(parent, ast=ast)


@render.register(ogAST.Decision)
def _decision(ast, scene, parent, states):
    ''' Create a DECISION symbol and all its answers '''
    symbol = sdlSymbols.Decision(parent, ast=ast)
    # Place the symbol at absolute coordinates
    if not parent:
311
        symbol.pos_x, symbol.pos_y = ast.pos_x, ast.pos_y
Maxime Perrotin's avatar
Maxime Perrotin committed
312
    for branch in ast.answers:
313
314
        render(branch, scene=scene, parent=symbol, states=states)
    symbol.updateConnectionPointPosition()
Maxime Perrotin's avatar
Maxime Perrotin committed
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
    return symbol


@render.register(ogAST.Label)
def _label(ast, scene, parent=None, states=None):
    ''' Create a LABEL symbol '''
    _, _ = scene, states
    return sdlSymbols.Label(parent, ast=ast)


@render.register(ogAST.Answer)
def _answer(ast, scene, parent, states):
    ''' Create an ANSWER symbol and build its following transition '''
    symbol = sdlSymbols.DecisionAnswer(parent, ast=ast)
    # Place the symbol at absolute coordinates so that if
    # the branch has NEXTSTATEs symbols, they are properly placed
    if not parent:
332
        symbol.pos_x, symbol.pos_y = ast.pos_x, ast.pos_y
Maxime Perrotin's avatar
Maxime Perrotin committed
333
    if ast.transition:
334
        render(ast.transition, scene=scene, parent=symbol, states=states)
Maxime Perrotin's avatar
Maxime Perrotin committed
335
336
337
338
339
340
341
342
    return symbol


@render.register(ogAST.Terminator)
def _terminator(ast, scene, parent, states):
    ''' Create a TERMINATOR symbol '''
    if ast.label:
        # pylint: disable=E1111
Maxime Perrotin's avatar
Maxime Perrotin committed
343
        parent = render(ast.label, scene=scene, parent=parent, states=states)
Maxime Perrotin's avatar
Maxime Perrotin committed
344
345
346
347
348
    if ast.kind == 'next_state':
        # Create a new state symbol
        symbol = sdlSymbols.State(parent=parent, ast=ast)
        # If the terminator is also a new state, render the inputs below
        for state_ast in states:
349
350
            state_label = state_ast.via or state_ast.inputString
            if (state_label == ast.inputString and
Maxime Perrotin's avatar
Maxime Perrotin committed
351
352
                    state_ast.pos_x == ast.pos_x and
                    state_ast.pos_y == ast.pos_y):
Maxime Perrotin's avatar
Maxime Perrotin committed
353
354
                symbol.nested_scene = state_ast.composite or \
                                      ogAST.CompositeState()
355
356
357
                for each in chain(state_ast.inputs,
                                  state_ast.connects,
                                  state_ast.continuous_signals):
Maxime Perrotin's avatar
Maxime Perrotin committed
358
359
                    render(each, scene=scene, parent=symbol, states=states)
                break
Maxime Perrotin's avatar
Maxime Perrotin committed
360
        symbol.nested_scene = ast.composite or ogAST.CompositeState()
Maxime Perrotin's avatar
Maxime Perrotin committed
361
362
    elif ast.kind == 'join':
        symbol = sdlSymbols.Join(parent, ast)
363
    elif ast.kind in ('return', 'stop'):
Maxime Perrotin's avatar
Maxime Perrotin committed
364
365
366
367
368
369
370
371
372
373
374
375
        symbol = sdlSymbols.ProcedureStop(parent, ast)
    else:
        raise TypeError('Unsupported terminator: ' + repr(ast))
    return symbol


@render.register(ogAST.Input)
def _input(ast, scene, parent, states):
    ''' Add input from the AST to the scene '''
    # Note: PROVIDED clause is not supported
    inp = sdlSymbols.Input(parent, ast=ast)
    if inp not in scene.items():
Maxime Perrotin's avatar
Maxime Perrotin committed
376
        add_to_scene(inp, scene)
Maxime Perrotin's avatar
Maxime Perrotin committed
377
    if not parent:
378
        inp.pos_x, inp.pos_y = ast.pos_x, ast.pos_y
Maxime Perrotin's avatar
Maxime Perrotin committed
379
380
381
382
383
384
    if ast.transition:
        render(ast.transition,
               scene=scene,
               parent=inp,
               states=states)
    return inp
Maxime Perrotin's avatar
Maxime Perrotin committed
385

Maxime Perrotin's avatar
Maxime Perrotin committed
386

387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
@render.register(ogAST.ContinuousSignal)
def _continuous_signal(ast, scene, parent, states):
    ''' Add continuous signal to the scene '''
    cont = sdlSymbols.ContinuousSignal(parent, ast=ast)
    if cont not in scene.items():
        add_to_scene(cont, scene)
    if not parent:
        cont.pos_x, cont.pos_y = ast.pos_x, ast.pos_y
    if ast.transition:
        render(ast.transition,
               scene=scene,
               parent=cont,
               states=states)
    return cont


Maxime Perrotin's avatar
Maxime Perrotin committed
403
@render.register(ogAST.Connect)
Maxime Perrotin's avatar
Maxime Perrotin committed
404
def _connect(ast, scene, parent, states):
Maxime Perrotin's avatar
Maxime Perrotin committed
405
406
407
    ''' Add connect symbol from the AST to the scene '''
    conn = sdlSymbols.Connect(parent, ast=ast)
    if conn not in scene.items():
Maxime Perrotin's avatar
Maxime Perrotin committed
408
        add_to_scene(conn, scene)
Maxime Perrotin's avatar
Maxime Perrotin committed
409
    if not parent:
410
        conn.pos_x, conn.pos_y = ast.pos_x, ast.pos_y
Maxime Perrotin's avatar
Maxime Perrotin committed
411
412
413
414
415
416
    if ast.transition:
        render(ast.transition,
               scene=scene,
               parent=conn,
               states=states)
    return conn