Commit 1522826f authored by Maxime Perrotin's avatar Maxime Perrotin

Partial support of nested states, with Sdl2010 syntax

parent 5141e1cf
...@@ -124,6 +124,54 @@ def _process(process): ...@@ -124,6 +124,54 @@ def _process(process):
t=var_type.ReferencedTypeName.replace('-','_'), t=var_type.ReferencedTypeName.replace('-','_'),
default=' := ' + dstr if def_value else '')) default=' := ' + dstr if def_value else ''))
# Flatten the nested states, add states to the process state list
# Requires renaming of nested states and setting chaining of transitions
def update_terminator(context, term, process):
'''Set next_id, identifying the next transition to run '''
if term.inputString.lower() in (st.statename.lower()
for st in context.composite_states):
if not term.via:
term.next_id = context.mapping \
[term.inputString.lower() + '_START']
else:
term.next_id = context.mapping[term.inputString.lower()
+ '_'
+ term.via.lower()
+ '_START']
def update_composite_state(state, process):
''' Rename inner states, recursively, and add inner transitions
to process, updating indexes, and update terminators
'''
trans_idx = len(process.transitions)
state.mapping = {state.statename + '_' + key:state.mapping.pop(key)
for key in state.mapping.keys()}
process.transitions.extend(state.transitions)
for key, value in state.mapping.viewitems():
# Update transition indices
if isinstance(value, int):
state.mapping[key] = value + trans_idx
else:
for inp in value:
inp.transition_id += trans_idx
process.mapping.update(state.mapping)
for inner in state.composite_states:
inner.statename = state.statename + '_' + inner.statename
update_composite_state(inner, process)
for each in state.terminators:
# Update state names in terminators and set next transition id
if each.kind == 'next_state':
each.inputString = state.statename + '_' + each.inputString
update_terminator(state, each, process)
for each in process.composite_states:
update_composite_state(each, process)
# Update terminators at process level
for each in process.terminators:
if each.kind == 'next_state':
update_terminator(process, each, process)
# Add the process states list to the process-level variables # Add the process states list to the process-level variables
states_decl = 'type states is (' states_decl = 'type states is ('
states_decl += ', '.join(process.mapping.iterkeys()) + ');' states_decl += ', '.join(process.mapping.iterkeys()) + ');'
...@@ -136,12 +184,13 @@ def _process(process): ...@@ -136,12 +184,13 @@ def _process(process):
.format(process_name)) .format(process_name))
# Add the declaration of the runTransition procedure # Add the declaration of the runTransition procedure
process_level_decl.append('procedure runTransition(trId: Integer);') process_level_decl.append('procedure runTransition(Id: Integer);')
# Generate the code of the start transition: # Generate the code of the start transition:
start_transition = ['begin'] start_transition = ['begin']
start_transition.append('runTransition(0);') start_transition.append('runTransition(0);')
mapping = {} mapping = {}
# Generate the code for the transitions in a mapping input-state # Generate the code for the transitions in a mapping input-state
input_signals = [sig['name'] for sig in process.input_signals] input_signals = [sig['name'] for sig in process.input_signals]
...@@ -150,7 +199,8 @@ def _process(process): ...@@ -150,7 +199,8 @@ def _process(process):
for input_signal in input_signals: for input_signal in input_signals:
mapping[input_signal] = {} mapping[input_signal] = {}
for state_name, input_symbols in process.mapping.viewitems(): for state_name, input_symbols in process.mapping.viewitems():
if state_name != 'START': if isinstance(input_symbols, list):
# Start symbols have no list of inputs
for i in input_symbols: for i in input_symbols:
if input_signal.lower() in (inp.lower() for if input_signal.lower() in (inp.lower() for
inp in i.inputlist): inp in i.inputlist):
...@@ -224,7 +274,7 @@ package {process_name} is'''.format(process_name=process_name, ...@@ -224,7 +274,7 @@ package {process_name} is'''.format(process_name=process_name,
taste_template.append('begin') taste_template.append('begin')
taste_template.append('case state is') taste_template.append('case state is')
for state in process.mapping.viewkeys(): for state in process.mapping.viewkeys():
if state == 'START': if state.endswith('START'):
continue continue
taste_template.append('when {state} =>'.format(state=state)) taste_template.append('when {state} =>'.format(state=state))
input_def = mapping[signal['name']].get(state) input_def = mapping[signal['name']].get(state)
...@@ -293,7 +343,9 @@ package {process_name} is'''.format(process_name=process_name, ...@@ -293,7 +343,9 @@ package {process_name} is'''.format(process_name=process_name,
'pragma import(C, RESET_{timer}, "{proc}_RI_reset_{timer}");' 'pragma import(C, RESET_{timer}, "{proc}_RI_reset_{timer}");'
.format(timer=timer, proc=process_name)) .format(timer=timer, proc=process_name))
taste_template.append('procedure runTransition(trId: Integer) is') taste_template.append('procedure runTransition(Id: Integer) is')
taste_template.append('trId : Integer := Id;')
# Generate the code for all transitions # Generate the code for all transitions
code_transitions = [] code_transitions = []
...@@ -327,6 +379,10 @@ package {process_name} is'''.format(process_name=process_name, ...@@ -327,6 +379,10 @@ package {process_name} is'''.format(process_name=process_name,
taste_template.extend(decl) taste_template.extend(decl)
taste_template.append('begin') taste_template.append('begin')
# Generate a loop that ends when a next state is reached
# (there can be chained transition when entering a nested state)
taste_template.append('while (trId /= -1) loop')
# Generate the switch-case on the transition id # Generate the switch-case on the transition id
taste_template.append('case trId is') taste_template.append('case trId is')
...@@ -342,10 +398,18 @@ package {process_name} is'''.format(process_name=process_name, ...@@ -342,10 +398,18 @@ package {process_name} is'''.format(process_name=process_name,
taste_template.append('null;') taste_template.append('null;')
taste_template.append('end case;') taste_template.append('end case;')
if code_labels:
# Due to nested states (chained transitions) jump over label code
# (NEXTSTATEs do not return from runTransition)
taste_template.append('goto next_transition;')
# Add the code for the floating labels # Add the code for the floating labels
taste_template.extend(code_labels) taste_template.extend(code_labels)
if code_labels:
taste_template.append('<<next_transition>>')
taste_template.append('null;')
taste_template.append('end loop;')
taste_template.append('end runTransition;') taste_template.append('end runTransition;')
taste_template.append('\n') taste_template.append('\n')
...@@ -1233,12 +1297,12 @@ def _transition(tr): ...@@ -1233,12 +1297,12 @@ def _transition(tr):
code.append('<<{label}>>'.format( code.append('<<{label}>>'.format(
label=tr.terminator.label.inputString)) label=tr.terminator.label.inputString))
if tr.terminator.kind == 'next_state': if tr.terminator.kind == 'next_state':
code.append('trId := ' + str(tr.terminator.next_id) + ';')
if tr.terminator.inputString.strip() != '-': if tr.terminator.inputString.strip() != '-':
# discard the dash state (remain in the same state) # discard the dash state (remain in the same state)
code.append('state := {nextState};'.format( if tr.terminator.next_id == -1:
code.append('state := {nextState};'.format(
nextState=tr.terminator.inputString)) nextState=tr.terminator.inputString))
# In any case, return to avoid code of floating labels
code.append('return;')
elif tr.terminator.kind == 'join': elif tr.terminator.kind == 'join':
code.append('goto {label};'.format( code.append('goto {label};'.format(
label=tr.terminator.inputString)) label=tr.terminator.inputString))
...@@ -1247,11 +1311,15 @@ def _transition(tr): ...@@ -1247,11 +1311,15 @@ def _transition(tr):
# TODO # TODO
elif tr.terminator.kind == 'return': elif tr.terminator.kind == 'return':
string = '' string = ''
if tr.terminator.return_expr: if tr.terminator.next_id == -1:
stmts, string, local = generate(tr.terminator.return_expr) if tr.terminator.return_expr:
code.extend(stmts) stmts, string, local = generate\
local_decl.extend(local) (tr.terminator.return_expr)
code.append('return{};'.format(' ' + string if string else '')) code.extend(stmts)
local_decl.extend(local)
code.append('return{};'.format(' ' + string if string else ''))
else:
code.append('trId := ' + str(tr.terminator.next_id) + ';')
if empty_transition: if empty_transition:
# If transition does not have any statement, generate an Ada 'null;' # If transition does not have any statement, generate an Ada 'null;'
code.append('null;') code.append('null;')
...@@ -1329,6 +1397,15 @@ def _inner_procedure(proc): ...@@ -1329,6 +1397,15 @@ def _inner_procedure(proc):
name=var_name, name=var_name,
sort=typename, sort=typename,
default=' := ' + dstr if def_value else '')) default=' := ' + dstr if def_value else ''))
# Look for labels in the diagram and transform them in floating labels
for idx in xrange(len(proc.content.floating_labels)):
for new_floating in find_labels(
proc.content.floating_labels[idx].transition):
proc.content.floating_labels.append(new_floating)
for new_floating in find_labels(proc.content.start.transition):
proc.content.floating_labels.append(new_floating)
tr_code, tr_decl = generate(proc.content.start.transition) tr_code, tr_decl = generate(proc.content.start.transition)
# Generate code for the floating labels # Generate code for the floating labels
code_labels = [] code_labels = []
......
...@@ -91,6 +91,10 @@ def _automaton(ast, scene): ...@@ -91,6 +91,10 @@ def _automaton(ast, scene):
if ast.start: if ast.start:
top_level_symbols.append(render(ast.start, scene, ast.states)) top_level_symbols.append(render(ast.start, scene, ast.states))
# Render named start symbols in nested states
for each in ast.named_start:
top_level_symbols.append(render(each, scene, ast.states))
# Render floating labels # Render floating labels
for label in ast.floating_labels: for label in ast.floating_labels:
top_level_symbols.append(render(label, scene, ast.states)) top_level_symbols.append(render(label, scene, ast.states))
...@@ -128,6 +132,9 @@ def _state(ast, scene, states, terminators, parent=None): ...@@ -128,6 +132,9 @@ def _state(ast, scene, states, terminators, parent=None):
for inp in ast.inputs: for inp in ast.inputs:
render(inp, scene=scene, parent=new_state, states=states) render(inp, scene=scene, parent=new_state, states=states)
new_state.nested_scene = ast.composite or ogAST.CompositeState()
return new_state return new_state
...@@ -161,6 +168,18 @@ def _start(ast, scene, states, parent=None): ...@@ -161,6 +168,18 @@ def _start(ast, scene, states, parent=None):
return start_symbol 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)
return start_symbol
@render.register(ogAST.Procedure_start) @render.register(ogAST.Procedure_start)
def _procedure_start(ast, scene, states, parent=None): def _procedure_start(ast, scene, states, parent=None):
''' Add the procedure start symbol to a scene ''' ''' Add the procedure start symbol to a scene '''
......
...@@ -561,6 +561,8 @@ class Symbol(QObject, QGraphicsPathItem, object): ...@@ -561,6 +561,8 @@ class Symbol(QObject, QGraphicsPathItem, object):
redbold = () redbold = ()
# Specify if the symbol can be drawn with anti-aliasing # Specify if the symbol can be drawn with anti-aliasing
_antialiasing = True _antialiasing = True
# Specify if the symbol text can be edited
editable = True
def __init__(self, parent=None): def __init__(self, parent=None):
''' '''
...@@ -1342,7 +1344,7 @@ class HorizontalSymbol(Symbol, object): ...@@ -1342,7 +1344,7 @@ class HorizontalSymbol(Symbol, object):
super(HorizontalSymbol, self).__init__(parent) super(HorizontalSymbol, self).__init__(parent)
self.minDistanceToSymbolAbove = 20 self.minDistanceToSymbolAbove = 20
self.connection = None self.connection = None
if text: if self.editable:
self.text = EditableText(parent=self, text=text, self.text = EditableText(parent=self, text=text,
hyperlink=hyperlink) hyperlink=hyperlink)
if parent: if parent:
...@@ -1506,7 +1508,10 @@ class VerticalSymbol(Symbol, object): ...@@ -1506,7 +1508,10 @@ class VerticalSymbol(Symbol, object):
x=None, y=None, hyperlink=None): x=None, y=None, hyperlink=None):
super(VerticalSymbol, self).__init__(parent) super(VerticalSymbol, self).__init__(parent)
self.connection = None self.connection = None
self.text = EditableText(parent=self, text=text, hyperlink=hyperlink) if self.editable:
self.text = EditableText(parent=self,
text=text,
hyperlink=hyperlink)
self.minDistanceToSymbolAbove = 15 self.minDistanceToSymbolAbove = 15
if parent: if parent:
local_pos = self.mapFromScene(0, y or 0) local_pos = self.mapFromScene(0, y or 0)
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
# Resource object code # Resource object code
# #
# Created: Fri Feb 28 10:41:17 2014 # Created: Fri Mar 7 19:56:41 2014
# by: The Resource Compiler for PySide (Qt v4.8.4) # by: The Resource Compiler for PySide (Qt v4.8.6)
# #
# WARNING! All changes made in this file will be lost! # WARNING! All changes made in this file will be lost!
...@@ -268,7 +268,7 @@ class Answer(object): ...@@ -268,7 +268,7 @@ class Answer(object):
''' AST Entry for a decision answer ''' ''' AST Entry for a decision answer '''
def __init__(self): def __init__(self):
''' One ANSWER of a DECISION ''' ''' One ANSWER of a DECISION '''
self.inputString = 'case' self.inputString = ''
self.line = None self.line = None
self.charPositionInLine = None self.charPositionInLine = None
self.pos_x = 0 self.pos_x = 0
...@@ -303,7 +303,7 @@ class Task(object): ...@@ -303,7 +303,7 @@ class Task(object):
''' AST Entry for TASKS ''' ''' AST Entry for TASKS '''
def __init__(self): def __init__(self):
''' Initialize TASK attributes (set of ASSIGN statements) ''' ''' Initialize TASK attributes (set of ASSIGN statements) '''
self.inputString = 'x := 1' self.inputString = ''
self.line = None self.line = None
self.charPositionInLine = None self.charPositionInLine = None
self.pos_x = 0 self.pos_x = 0
...@@ -342,7 +342,7 @@ class TaskForLoop(Task): ...@@ -342,7 +342,7 @@ class TaskForLoop(Task):
class Output(object): class Output(object):
''' AST Entry for OUTPUT statements ''' ''' AST Entry for OUTPUT statements '''
def __init__(self, defName='RI'): def __init__(self, defName=''):
''' Set of OUTPUT statements ''' ''' Set of OUTPUT statements '''
self.inputString = defName self.inputString = defName
self.pos_x = 0 self.pos_x = 0
...@@ -392,6 +392,8 @@ class Terminator(object): ...@@ -392,6 +392,8 @@ class Terminator(object):
self.return_expr = None self.return_expr = None
# via clause, used for entering nested state with an entry point # via clause, used for entering nested state with an entry point
self.via = None self.via = None
# some transitions can be chained, when entering/leaving nested states
self.next_id = -1
def __repr__(self): def __repr__(self):
''' Debug output for terminators ''' ''' Debug output for terminators '''
...@@ -406,7 +408,7 @@ class Label(object): ...@@ -406,7 +408,7 @@ class Label(object):
def __init__(self): def __init__(self):
''' Initialize the label attributes ''' ''' Initialize the label attributes '''
# inputString holds the label name # inputString holds the label name
self.inputString = 'Here' self.inputString = ''
self.pos_x = 0 self.pos_x = 0
self.pos_y = 0 self.pos_y = 0
self.width = 70 self.width = 70
...@@ -475,7 +477,7 @@ class Input(object): ...@@ -475,7 +477,7 @@ class Input(object):
def __init__(self): def __init__(self):
''' Initialize the Input attributes ''' ''' Initialize the Input attributes '''
# inputString is the user text, it can contain several inputs # inputString is the user text, it can contain several inputs
self.inputString = 'PI' self.inputString = ''
self.pos_x = 0 self.pos_x = 0
self.pos_y = 0 self.pos_y = 0
self.width = 70 self.width = 70
...@@ -526,7 +528,7 @@ class Start(object): ...@@ -526,7 +528,7 @@ class Start(object):
def __repr__(self): def __repr__(self):
''' Debug output for a START symbol ''' ''' Debug output for a START symbol '''
return 'START' return 'START {}'.format(self.inputString)
class Procedure_start(Start): class Procedure_start(Start):
...@@ -534,6 +536,11 @@ class Procedure_start(Start): ...@@ -534,6 +536,11 @@ class Procedure_start(Start):
pass pass
class CompositeState_start(Start):
''' Composite state start symbol - inherits from Start, can have a name '''
pass
class Comment(object): class Comment(object):
''' AST Entry for COMMENT symbols ''' ''' AST Entry for COMMENT symbols '''
def __init__(self): def __init__(self):
...@@ -575,6 +582,8 @@ class State(object): ...@@ -575,6 +582,8 @@ class State(object):
self.comment = None self.comment = None
# optional hyperlink # optional hyperlink
self.hyperlink = None self.hyperlink = None
# optional composite state content (type CompositeState)
self.composite = None
def __repr__(self): def __repr__(self):
''' Debug output for a STATE symbol ''' ''' Debug output for a STATE symbol '''
...@@ -588,7 +597,7 @@ class TextArea(object): ...@@ -588,7 +597,7 @@ class TextArea(object):
def __init__(self): def __init__(self):
''' Text area (raw content for rendering only) ''' ''' Text area (raw content for rendering only) '''
self.inputString = '-- Declare your variables\n\n' \ self.inputString = '-- Declare your variables\n\n' \
'-- Syntax: DCL <variable name> <type name>;' '-- Syntax: DCL <variable name> <type name>;\n\n'
# DCL variables in the text area {name: (sort, default_value), ...} # DCL variables in the text area {name: (sort, default_value), ...}
self.variables = {} self.variables = {}
self.line = None self.line = None
...@@ -617,13 +626,14 @@ class Automaton(object): ...@@ -617,13 +626,14 @@ class Automaton(object):
self.start = None self.start = None
self.floating_labels = [] self.floating_labels = []
self.states = [] self.states = []
self.named_start = []
class Procedure(object): class Procedure(object):
''' Internal procedure definition ''' ''' Internal procedure definition '''
def __init__(self): def __init__(self):
''' Procedure AST default value ''' ''' Procedure AST default value '''
self.inputString = 'Proc' self.inputString = ''
self.line = None self.line = None
self.charPositionInLine = None self.charPositionInLine = None
# Set default coordinates and width/height # Set default coordinates and width/height
...@@ -712,6 +722,9 @@ class Process(object): ...@@ -712,6 +722,9 @@ class Process(object):
# list of type Transition - use 'mapping' to map index to inputs/states # list of type Transition - use 'mapping' to map index to inputs/states
self.transitions = [] self.transitions = []
# list of type CompositeState
self.composite_states = []
# Set of symbols contained in the process (type Automaton) # Set of symbols contained in the process (type Automaton)
# (Includes inner procedures) # (Includes inner procedures)
self.content = Automaton(parent=self) self.content = Automaton(parent=self)
...@@ -720,6 +733,27 @@ class Process(object): ...@@ -720,6 +733,27 @@ class Process(object):
self.timers = [] self.timers = []
class CompositeState(Process):
'''
Composite states: the difference with Process is that they can have:
- several START elements, that correspond to state entry points,
- state exit points (with RETURN terminators)
- entry and exit procedures
'''
def __init__(self):
super(CompositeState, self).__init__()
self.statename = ''
self.state_entrypoints = []
self.state_exitpoints = []
# Special entry and exit procedures (named "entry" and "exit")
self.entry_procedure = None
self.exit_procedure = None
# Body can contain text areas, procedures, composite states,
# one nameless START, named START (one per entrypoint), states,
# and floating labels.
# XXX check what to do with local DCL and timers
class Block(object): class Block(object):
''' AST for a BLOCK entity ''' ''' AST for a BLOCK entity '''
def __init__(self): def __init__(self):
......
This diff is collapsed.
...@@ -53,7 +53,7 @@ from PySide import QtSvg ...@@ -53,7 +53,7 @@ from PySide import QtSvg
from genericSymbols import Symbol, Comment, EditableText, Cornergrabber from genericSymbols import Symbol, Comment, EditableText, Cornergrabber
from sdlSymbols import(Input, Output, Decision, DecisionAnswer, Task, from sdlSymbols import(Input, Output, Decision, DecisionAnswer, Task,
ProcedureCall, TextSymbol, State, Start, Join, Label, Procedure, ProcedureCall, TextSymbol, State, Start, Join, Label, Procedure,
ProcedureStart, ProcedureStop) ProcedureStart, ProcedureStop, StateStart)
# Icons and png files generated from the resource file: # Icons and png files generated from the resource file:
import icons # NOQA import icons # NOQA
...@@ -128,7 +128,9 @@ ACTIONS = { ...@@ -128,7 +128,9 @@ ACTIONS = {
'procedure': [ProcedureStart, Task, Decision, 'procedure': [ProcedureStart, Task, Decision,
DecisionAnswer, Output, ProcedureCall, TextSymbol, DecisionAnswer, Output, ProcedureCall, TextSymbol,
Comment, Label, Join, ProcedureStop], Comment, Label, Join, ProcedureStop],
'statechart': [] 'statechart': [],
'state': [StateStart, State, Input, Task, Decision, DecisionAnswer, Output,
ProcedureCall, TextSymbol, Comment, Label, Join, ProcedureStop]
} }
...@@ -256,7 +258,10 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -256,7 +258,10 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
scene_left = QtCore.Signal() scene_left = QtCore.Signal()
def __init__(self, context='process'): def __init__(self, context='process'):
''' Create an SDL Scene for a given context (process or procedure) ''' '''
Create an SDL Scene for a given context:
process, procedure or composite state
'''
super(SDL_Scene, self).__init__() super(SDL_Scene, self).__init__()
self.mode = 'idle' self.mode = 'idle'
self.context = context self.context = context
...@@ -273,7 +278,8 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -273,7 +278,8 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
# buttonSelected is used to set which symbol to draw # buttonSelected is used to set which symbol to draw
# on next scene click (see mousePressEvent) # on next scene click (see mousePressEvent)
self.button_selected = None self.button_selected = None
self.setBackgroundBrush(QtGui.QBrush(QtGui.QImage(':icons/texture.png'))) self.setBackgroundBrush(QtGui.QBrush(
QtGui.QImage(':icons/texture.png')))
self.messages_window = None self.messages_window = None
self.click_coordinates = None self.click_coordinates = None
self.process_name = 'opengeode' self.process_name = 'opengeode'
...@@ -922,9 +928,9 @@ class SDL_Scene(QtGui.QGraphicsScene, object): ...@@ -922,9 +928,9 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
# When creating a new decision, add two default answers # When creating a new decision, add two default answers
self.place_symbol(item_type=DecisionAnswer, parent=item) self.place_symbol(item_type=DecisionAnswer, parent=item)
self.place_symbol(item_type=DecisionAnswer, parent=item) self.place_symbol(item_type=DecisionAnswer, parent=item)
elif item_type == Procedure: elif item_type in (Procedure, State):
# Create a sub-scene for the procedure # Create a sub-scene for the procedure or nested state
subscene = SDL_Scene(context='procedure') subscene = SDL_Scene(context=item_type.__name__.lower())
subscene.messages_window = self.messages_window subscene.messages_window = self.messages_window
item.nested_scene = subscene item.nested_scene = subscene
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
<file>icons/procedurestop.png</file> <file>icons/procedurestop.png</file>
<file>icons/procedure.png</file> <file>icons/procedure.png</file>
<file>icons/start.png</file> <file>icons/start.png</file>
<file>icons/statestart.png</file>
<file>icons/state.png</file> <file>icons/state.png</file>