Renderer.py 11 KB
Newer Older
Maxime Perrotin's avatar
Maxime Perrotin committed
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#!/usr/bin/env python
# -*- coding: utf-8 -*-

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

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

    Copyright (c) 2012-2013 European Space Agency

    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 ogAST
import sdlSymbols
import genericSymbols
import logging
from singledispatch import singledispatch

LOG = logging.getLogger(__name__)

__all__ = ['render']

@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))


@render.register(ogAST.Process)
def _process(ast, scene):
    ''' Render the symbols inside a process or procedure '''
    # Set autocompletion lists for input, output, state, types, variables:
    try:
        sdlSymbols.TextSymbol.completion_list = {
                t.replace('-', '_') for t in ast.dataview}
    except (AttributeError, TypeError):
        LOG.debug('No dataview for filling types autocompletion list')
    sdlSymbols.State.completion_list = {
            state for state in ast.mapping if state != 'START'}
    sdlSymbols.Input.completion_list = {
            signal['name'] for signal in ast.input_signals}
    sdlSymbols.Output.completion_list = {
            signal['name'] for signal in ast.output_signals}
    sdlSymbols.Task.completion_list = set(ast.variables.keys())

    sdlSymbols.ProcedureCall.completion_list = {
            proc.inputString for proc in ast.procedures}

    return render(ast.content, scene)


@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))

94
95
96
97
    # 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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
    # Render floating labels
    for label in ast.floating_labels:
        top_level_symbols.append(render(label, scene, ast.states))

    # Render floating states
    for state in ast.states:
        # Create only floating states
        try:
            new_state = render(state, scene=scene, states=ast.states,
                               terminators=ast.parent.terminators)
        except TypeError:
            # Discard terminators (see _state function for explanation)
            pass
        else:
            top_level_symbols.append(new_state)
    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:
        if(term.kind == 'next_state' and
                term.pos_x == ast.pos_x and
                term.pos_y == ast.pos_y and
                term.inputString == ast.inputString):
            raise TypeError('This state is a terminator')
    new_state = sdlSymbols.State(parent=None, ast=ast)
    if new_state not in scene.items():
        scene.addItem(new_state)

    for inp in ast.inputs:
        render(inp, scene=scene, parent=new_state, states=states)
135
136
137

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

Maxime Perrotin's avatar
Maxime Perrotin committed
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
    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)
    scene.addItem(proc_symbol)
    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)
    scene.addItem(text)
    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)
    scene.addItem(start_symbol)
    if ast.transition:
        render(ast.transition,
                      scene=scene, parent=start_symbol, states=states)
168
169
170
171
172
173
174
175
176
177
178
179
    return start_symbol


@render.register(ogAST.CompositeState_start)
def _start(ast, scene, states, parent=None):
    ''' Add an editable start symbol to a scene (in composite states) '''
    _ = parent
    start_symbol = sdlSymbols.StateStart(ast)
    scene.addItem(start_symbol)
    if ast.transition:
        render(ast.transition,
                      scene=scene, parent=start_symbol, states=states)
Maxime Perrotin's avatar
Maxime Perrotin committed
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
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
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
    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)
    scene.addItem(start_symbol)
    if ast.transition:
        render(ast.transition,
                      scene=scene, parent=start_symbol, states=states)
    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():
        scene.addItem(lab)
    lab.setPos(ast.pos_x, ast.pos_y)
    if ast.transition:
        render(ast.transition,
                      scene=scene,
                      parent=lab,
                      states=states)
    return lab


@render.register(ogAST.Transition)
def _transition(ast, scene, parent, states):
    ''' Add a transition to a scene '''
    for action_symbol in ast.actions:
        # pylint: disable=E1111
        parent = render(action_symbol,
                               scene=scene, parent=parent, states=states)

    if ast.terminator:
        render(ast.terminator,
                      scene=scene, parent=parent, states=states)


@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)
def _output(ast, scene, parent, states):
    ''' 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:
        symbol.setPos(ast.pos_x, ast.pos_y)
    for branch in ast.answers:
        render(branch,
                      scene=scene, parent=symbol, states=states)
    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:
        symbol.setPos(ast.pos_x, ast.pos_y)
    if ast.transition:
        render(ast.transition,
                      scene=scene, parent=symbol, states=states)
    return symbol


@render.register(ogAST.Terminator)
def _terminator(ast, scene, parent, states):
    ''' Create a TERMINATOR symbol '''
    if ast.label:
        # pylint: disable=E1111
        parent = render(ast.label,
                               scene=scene, parent=parent, states=states)
    if ast.kind == 'next_state':
        LOG.debug('ADDING NEXT_STATE ' + ast.inputString)
        # Create a new state symbol
        symbol = sdlSymbols.State(parent=parent, ast=ast)
        # If the terminator is also a new state, render the inputs below
        LOG.debug('STATELIST:' + str([st.inputString for st in states]))
        for state_ast in states:
            if (state_ast.inputString == ast.inputString and
                    state_ast.pos_x == ast.pos_x and
                    state_ast.pos_y == ast.pos_y):
                LOG.debug('MERGING TERMINATOR "' + ast.inputString + '"')
                for input_ast in state_ast.inputs:
                    render(input_ast,
                                  scene=scene, parent=symbol, states=states)
    elif ast.kind == 'join':
        symbol = sdlSymbols.Join(parent, ast)
309
    elif ast.kind in ('return', 'stop'):
Maxime Perrotin's avatar
Maxime Perrotin committed
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
        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():
        scene.addItem(inp)
    if not parent:
        inp.setPos(ast.pos_x, ast.pos_y)
    if ast.transition:
        render(ast.transition,
               scene=scene,
               parent=inp,
               states=states)
    return inp