Commit de3bd218 authored by Maxime Perrotin's avatar Maxime Perrotin
Browse files

Create symbol to render continuous symbols

parent 4341e770
This note explains the steps to be followed in order to implement SDL features
that are not yet supported by the tool.
Use case: add support for "Continous signals"
Use case: add support for "Continuous signals"
The following steps are:
......@@ -92,7 +92,7 @@ class State(object):
''' AST Entry for STATE symbols '''
def __init__(self, defName=''):
# (...) Add the following lines:
# list of ContinousSignal (provided clauses below a state)
# list of ContinuousSignal (provided clauses below a state)
self.continuous_signals = []
Then create the new class, using the same structure as another class of a
......@@ -145,10 +145,107 @@ an AST containing the new construct.
There are still a number of updates to perform to complete the support of the
new feature:
- Update the on-the-fly parser for syntax checks in the graphical editor (ogParser.py)
- Create a graphical symbol for the new feature (in sdlSymbols.py)
- Update the renderer to draw the symbol (Renderer.py)
- Update the on-the-fly parser for syntax checks in the graphical editor (ogParser.py)
- Create an icon and add the new symbol to the palette
- Update the statechart renderer (Statechart.py)
- Update the backend that saves the model to phrase representation (Pr.py)
- Update other backends that can be impacted, such as code generators (AdaGenerator.py, etc.)
5) Syntax checker in ogParser.py
When a graphical symbol is edited, a call to "ogParser.parseSingleElement"
is made to check that there are no syntax errors.
There is an assertion at the begining of this function to prevent trying to
parse unsupported construct and raise a exception which would be more
difficult to debug.
We need to add our new "continuous_signal" symbol here:
assert(elem in ('input_part', 'output', 'decision', 'alternative_part',
'terminator_statement', 'label', 'task', 'procedure_call', 'end',
'text_area', 'state', 'start', 'procedure', 'floating_label',
'connect_part', 'process_definition', 'proc_start', 'state_start',
'signalroute', 'stop_if', 'continuous_signal'))
6) Create a new symbol in sdlSymbols.py
sdlSymbols.py module contains the SDL-specific symbol ; they all inherit from
a higher level symbol class, which provides the standard behaviour of the
symbol, e.g. when it is moved, etc.
The symbol-specific class must contain the following information:
- the name of the symbol as understood by the syntax checker
- the shape of the symbol
- the behaviour of the autocompletion feature
- the list of symbols that can be connected to this symbol
- and possibly tuning of some paramters
At the top-part of the module, the new class needs to be added to the __all__
global:
__all__ = ['Input', 'Output', 'State', 'Task', 'ProcedureCall', 'Label',
'Decision', 'DecisionAnswer', 'Join', 'Start', 'TextSymbol',
'Procedure', 'ProcedureStart', 'ProcedureStop', 'ASN1Viewer',
'StateStart', 'Process', 'ContinuousSignal']
Then the class is defined... extract:
# pylint: disable=R0904
class ContinuousSignal(HorizontalSymbol):
''' " Provided" part below a state - not a enabling condition '''
common_name = 'continuous_signal' # <==== Same name as in ogParser.py
def set_shape(self, width, height):
''' Define the shape '''
path = QPainterPath()
path.moveTo(15, 0)
path.lineTo(0, height / 2.0)
path.lineTo(15, height)
path.moveTo(width - 15, 0)
path.lineTo(width, height / 2.0)
path.lineTo(width - 15, height)
self.setPath(path)
super(ContinuousSignal, self).set_shape(width, height)
7) Update the renderer to see some first result: Renderer.py
Since the parser is ready and the symbol is defined, it is now possible to
update the graphical renderer and see if the symbol is properly displayed.
The renderer is a backend that uses a visitor design pattern (using the python
singledispatch mechanism). It makes it easy to extend.
Rendering means taking an ogAST class and creating an sdlSymbols class.
There is a single "render" function that is dispatched depending on the class
name. You have to provide a function (name does not matter) and register it
using a singledispatch decorator:
@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
And then you must call the render function from the State renderer, since
the continuous signals are generated below states, just like Inputs:
@render.register(ogAST.State)
def _state(ast, scene, states, terminators, parent=None):
(...)
for exit in chain(ast.inputs, ast.connects, ast.continuous_signals):
render(exit, scene=scene, parent=new_state, states=states)
(...)
......@@ -183,7 +183,7 @@ def _state(ast, scene, states, terminators, parent=None):
if new_state not in scene.items():
add_to_scene(new_state, scene)
for exit in chain(ast.inputs, ast.connects):
for exit in chain(ast.inputs, ast.connects, ast.continuous_signals):
render(exit, scene=scene, parent=new_state, states=states)
new_state.nested_scene = ast.composite or ogAST.CompositeState()
......@@ -344,7 +344,9 @@ def _terminator(ast, scene, parent, states):
state_ast.pos_y == ast.pos_y):
symbol.nested_scene = state_ast.composite or \
ogAST.CompositeState()
for each in chain(state_ast.inputs, state_ast.connects):
for each in chain(state_ast.inputs,
state_ast.connects,
state_ast.continuous_signals):
render(each, scene=scene, parent=symbol, states=states)
break
symbol.nested_scene = ast.composite or ogAST.CompositeState()
......@@ -374,6 +376,22 @@ def _input(ast, scene, parent, states):
return inp
@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
@render.register(ogAST.Connect)
def _connect(ast, scene, parent, states):
''' Add connect symbol from the AST to the scene '''
......
......@@ -699,7 +699,7 @@ class State(object):
self.inputs = []
# list of type Connect (connection below a nested state)
self.connects = []
# list of ContinousSignal (provided clauses below a state)
# list of ContinuousSignal (provided clauses below a state)
self.continuous_signals = []
# optional comment symbol
self.comment = None
......
......@@ -4303,7 +4303,7 @@ def parseSingleElement(elem='', string='', context=None):
'terminator_statement', 'label', 'task', 'procedure_call', 'end',
'text_area', 'state', 'start', 'procedure', 'floating_label',
'connect_part', 'process_definition', 'proc_start', 'state_start',
'signalroute', 'stop_if'))
'signalroute', 'stop_if', 'continuous_signal'))
# Create a dummy context, needed to place context data
if elem == 'proc_start':
elem = 'start'
......
......@@ -20,7 +20,7 @@
__all__ = ['Input', 'Output', 'State', 'Task', 'ProcedureCall', 'Label',
'Decision', 'DecisionAnswer', 'Join', 'Start', 'TextSymbol',
'Procedure', 'ProcedureStart', 'ProcedureStop', 'ASN1Viewer',
'StateStart', 'Process']
'StateStart', 'Process', 'ContinuousSignal']
#import traceback
import logging
......@@ -137,7 +137,7 @@ class Input(HorizontalSymbol):
''' SDL INPUT Symbol '''
_unique_followers = ['Comment']
_insertable_followers = ['Task', 'ProcedureCall', 'Output', 'Decision',
'Input', 'Label', 'Connect']
'Input', 'Label', 'Connect', 'ContinuousSignal']
_terminal_followers = ['Join', 'State', 'ProcedureStop']
common_name = 'input_part'
......@@ -167,8 +167,9 @@ class Input(HorizontalSymbol):
def insert_symbol(self, parent, x, y):
''' Insert Input symbol - propagate branch Entry point '''
# Make sure that parent is a state, not a sibling input
item_parent = (parent if not isinstance(parent, Input)
# Make sure that parent is a state, not a sibling symbol
item_parent = (parent if not isinstance(parent, (Input,
ContinuousSignal))
else parent.parentItem())
self.branch_entrypoint = item_parent.branch_entrypoint
super(Input, self).insert_symbol(item_parent, x, y)
......@@ -883,7 +884,7 @@ class ASN1Viewer(TextSymbol):
class State(VerticalSymbol):
''' SDL STATE Symbol '''
_unique_followers = ['Comment']
_insertable_followers = ['Input', 'Connect']
_insertable_followers = ['Input', 'Connect', 'ContinuousSignal']
arrow_head = 'simple'
common_name = 'terminator_statement'
needs_parent = False
......@@ -1185,3 +1186,59 @@ class StateStart(Start):
''' Update nested state entry points '''
CONTEXT.state_entrypoints = \
set(CONTEXT.state_entrypoints) | set(unicode(self))
# pylint: disable=R0904
class ContinuousSignal(HorizontalSymbol):
''' " Provided" part below a state - not a enabling condition '''
_unique_followers = ['Comment']
_insertable_followers = ['Task', 'ProcedureCall', 'Output', 'Decision',
'Input', 'Label', 'Connect', 'ContinuousSignal']
_terminal_followers = ['Join', 'State', 'ProcedureStop']
common_name = 'continuous_signal'
# Define reserved keywords for the syntax highlighter
blackbold = SDL_BLACKBOLD
redbold = SDL_REDBOLD
def __init__(self, parent=None, ast=None):
''' Create the Provided symbol - use no background color '''
ast = ast or ogAST.ContinuousSignal()
self.ast = ast
self.branch_entrypoint = None
if not ast.pos_y and parent:
# Make sure the item is placed below its parent
ast.pos_y = parent.y() + parent.boundingRect().height() + 10
super(ContinuousSignal, self).__init__(parent, text=ast.inputString,
x=ast.pos_x or 0, y=ast.pos_y or 0, hyperlink=ast.hyperlink)
self.set_shape(ast.width, ast.height)
self.terminal_symbol = False
self.parser = ogParser
if ast.comment:
Comment(parent=self, ast=ast.comment)
def insert_symbol(self, parent, x, y):
''' Insert symbol - propagate branch Entry point '''
# Make sure that parent is a state, not a sibling symbol
item_parent = (parent if not isinstance(parent, (Input,
ContinuousSignal))
else parent.parentItem())
self.branch_entrypoint = item_parent.branch_entrypoint
super(ContinuousSignal, self).insert_symbol(item_parent, x, y)
def set_shape(self, width, height):
''' Define the shape '''
path = QPainterPath()
path.moveTo(15, 0)
path.lineTo(0, height / 2.0)
path.lineTo(15, height)
path.moveTo(width - 15, 0)
path.lineTo(width, height / 2.0)
path.lineTo(width - 15, height)
self.setPath(path)
super(ContinuousSignal, self).set_shape(width, height)
@property
def completion_list(self):
''' Set auto-completion list '''
return variables_autocompletion(self, None)
......@@ -27,7 +27,7 @@ signal telemetry(tm_type);
NEXTSTATE wait_for_tc;
/* CIF STATE (159, 110), (111, 35) */
STATE wait_for_tc;
/* CIF INPUT (143, 165), (144, 35) */
/* CIF PROVIDED (143, 165), (144, 35) */
provided got_tc=true and present(tc!content)=change_mode;
/* CIF PROCEDURECALL (123, 215), (184, 35) */
CALL writeln('Change Mode');
......@@ -43,7 +43,7 @@ signal telemetry(tm_type);
NEXTSTATE wait_for_tc;
/* CIF STATE (126, 106), (106, 35) */
STATE wait_for_tc;
/* CIF INPUT (107, 161), (144, 35) */
/* CIF PROVIDED (107, 161), (144, 35) */
INPUT telecommand(tc);
/* CIF TASK (117, 211), (123, 35) */
TASK got_tc := true;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment