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):
t=var_type.ReferencedTypeName.replace('-','_'),
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
states_decl = 'type states is ('
states_decl += ', '.join(process.mapping.iterkeys()) + ');'
......@@ -136,12 +184,13 @@ def _process(process):
.format(process_name))
# 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:
start_transition = ['begin']
start_transition.append('runTransition(0);')
mapping = {}
# Generate the code for the transitions in a mapping input-state
input_signals = [sig['name'] for sig in process.input_signals]
......@@ -150,7 +199,8 @@ def _process(process):
for input_signal in input_signals:
mapping[input_signal] = {}
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:
if input_signal.lower() in (inp.lower() for
inp in i.inputlist):
......@@ -224,7 +274,7 @@ package {process_name} is'''.format(process_name=process_name,
taste_template.append('begin')
taste_template.append('case state is')
for state in process.mapping.viewkeys():
if state == 'START':
if state.endswith('START'):
continue
taste_template.append('when {state} =>'.format(state=state))
input_def = mapping[signal['name']].get(state)
......@@ -293,7 +343,9 @@ package {process_name} is'''.format(process_name=process_name,
'pragma import(C, RESET_{timer}, "{proc}_RI_reset_{timer}");'
.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
code_transitions = []
......@@ -327,6 +379,10 @@ package {process_name} is'''.format(process_name=process_name,
taste_template.extend(decl)
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
taste_template.append('case trId is')
......@@ -342,10 +398,18 @@ package {process_name} is'''.format(process_name=process_name,
taste_template.append('null;')
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
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('\n')
......@@ -1233,12 +1297,12 @@ def _transition(tr):
code.append('<<{label}>>'.format(
label=tr.terminator.label.inputString))
if tr.terminator.kind == 'next_state':
code.append('trId := ' + str(tr.terminator.next_id) + ';')
if tr.terminator.inputString.strip() != '-':
# 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))
# In any case, return to avoid code of floating labels
code.append('return;')
elif tr.terminator.kind == 'join':
code.append('goto {label};'.format(
label=tr.terminator.inputString))
......@@ -1247,11 +1311,15 @@ def _transition(tr):
# TODO
elif tr.terminator.kind == 'return':
string = ''
if tr.terminator.return_expr:
stmts, string, local = generate(tr.terminator.return_expr)
code.extend(stmts)
local_decl.extend(local)
code.append('return{};'.format(' ' + string if string else ''))
if tr.terminator.next_id == -1:
if tr.terminator.return_expr:
stmts, string, local = generate\
(tr.terminator.return_expr)
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 transition does not have any statement, generate an Ada 'null;'
code.append('null;')
......@@ -1329,6 +1397,15 @@ def _inner_procedure(proc):
name=var_name,
sort=typename,
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)
# Generate code for the floating labels
code_labels = []
......
......@@ -91,6 +91,10 @@ def _automaton(ast, scene):
if ast.start:
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
for label in ast.floating_labels:
top_level_symbols.append(render(label, scene, ast.states))
......@@ -128,6 +132,9 @@ def _state(ast, scene, states, terminators, parent=None):
for inp in ast.inputs:
render(inp, scene=scene, parent=new_state, states=states)
new_state.nested_scene = ast.composite or ogAST.CompositeState()
return new_state
......@@ -161,6 +168,18 @@ def _start(ast, scene, states, parent=None):
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)
def _procedure_start(ast, scene, states, parent=None):
''' Add the procedure start symbol to a scene '''
......
......@@ -561,6 +561,8 @@ class Symbol(QObject, QGraphicsPathItem, object):
redbold = ()
# Specify if the symbol can be drawn with anti-aliasing
_antialiasing = True
# Specify if the symbol text can be edited
editable = True
def __init__(self, parent=None):
'''
......@@ -1342,7 +1344,7 @@ class HorizontalSymbol(Symbol, object):
super(HorizontalSymbol, self).__init__(parent)
self.minDistanceToSymbolAbove = 20
self.connection = None
if text:
if self.editable:
self.text = EditableText(parent=self, text=text,
hyperlink=hyperlink)
if parent:
......@@ -1506,7 +1508,10 @@ class VerticalSymbol(Symbol, object):
x=None, y=None, hyperlink=None):
super(VerticalSymbol, self).__init__(parent)
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
if parent:
local_pos = self.mapFromScene(0, y or 0)
......
......@@ -2,8 +2,8 @@
# Resource object code
#
# Created: Fri Feb 28 10:41:17 2014
# by: The Resource Compiler for PySide (Qt v4.8.4)
# Created: Fri Mar 7 19:56:41 2014
# by: The Resource Compiler for PySide (Qt v4.8.6)
#
# WARNING! All changes made in this file will be lost!
......@@ -268,7 +268,7 @@ class Answer(object):
''' AST Entry for a decision answer '''
def __init__(self):
''' One ANSWER of a DECISION '''
self.inputString = 'case'
self.inputString = ''
self.line = None
self.charPositionInLine = None
self.pos_x = 0
......@@ -303,7 +303,7 @@ class Task(object):
''' AST Entry for TASKS '''
def __init__(self):
''' Initialize TASK attributes (set of ASSIGN statements) '''
self.inputString = 'x := 1'
self.inputString = ''
self.line = None
self.charPositionInLine = None
self.pos_x = 0
......@@ -342,7 +342,7 @@ class TaskForLoop(Task):
class Output(object):
''' AST Entry for OUTPUT statements '''
def __init__(self, defName='RI'):
def __init__(self, defName=''):
''' Set of OUTPUT statements '''
self.inputString = defName
self.pos_x = 0
......@@ -392,6 +392,8 @@ class Terminator(object):
self.return_expr = None
# via clause, used for entering nested state with an entry point
self.via = None
# some transitions can be chained, when entering/leaving nested states
self.next_id = -1
def __repr__(self):
''' Debug output for terminators '''
......@@ -406,7 +408,7 @@ class Label(object):
def __init__(self):
''' Initialize the label attributes '''
# inputString holds the label name
self.inputString = 'Here'
self.inputString = ''
self.pos_x = 0
self.pos_y = 0
self.width = 70
......@@ -475,7 +477,7 @@ class Input(object):
def __init__(self):
''' Initialize the Input attributes '''
# inputString is the user text, it can contain several inputs
self.inputString = 'PI'
self.inputString = ''
self.pos_x = 0
self.pos_y = 0
self.width = 70
......@@ -526,7 +528,7 @@ class Start(object):
def __repr__(self):
''' Debug output for a START symbol '''
return 'START'
return 'START {}'.format(self.inputString)
class Procedure_start(Start):
......@@ -534,6 +536,11 @@ class Procedure_start(Start):
pass
class CompositeState_start(Start):
''' Composite state start symbol - inherits from Start, can have a name '''
pass
class Comment(object):
''' AST Entry for COMMENT symbols '''
def __init__(self):
......@@ -575,6 +582,8 @@ class State(object):
self.comment = None
# optional hyperlink
self.hyperlink = None
# optional composite state content (type CompositeState)
self.composite = None
def __repr__(self):
''' Debug output for a STATE symbol '''
......@@ -588,7 +597,7 @@ class TextArea(object):
def __init__(self):
''' Text area (raw content for rendering only) '''
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), ...}
self.variables = {}
self.line = None
......@@ -617,13 +626,14 @@ class Automaton(object):
self.start = None
self.floating_labels = []
self.states = []
self.named_start = []
class Procedure(object):
''' Internal procedure definition '''
def __init__(self):
''' Procedure AST default value '''
self.inputString = 'Proc'
self.inputString = ''
self.line = None
self.charPositionInLine = None
# Set default coordinates and width/height
......@@ -712,6 +722,9 @@ class Process(object):
# list of type Transition - use 'mapping' to map index to inputs/states
self.transitions = []
# list of type CompositeState
self.composite_states = []
# Set of symbols contained in the process (type Automaton)
# (Includes inner procedures)
self.content = Automaton(parent=self)
......@@ -720,6 +733,27 @@ class Process(object):
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):
''' AST for a BLOCK entity '''
def __init__(self):
......
This diff is collapsed.
......@@ -53,7 +53,7 @@ from PySide import QtSvg
from genericSymbols import Symbol, Comment, EditableText, Cornergrabber
from sdlSymbols import(Input, Output, Decision, DecisionAnswer, Task,
ProcedureCall, TextSymbol, State, Start, Join, Label, Procedure,
ProcedureStart, ProcedureStop)
ProcedureStart, ProcedureStop, StateStart)
# Icons and png files generated from the resource file:
import icons # NOQA
......@@ -128,7 +128,9 @@ ACTIONS = {
'procedure': [ProcedureStart, Task, Decision,
DecisionAnswer, Output, ProcedureCall, TextSymbol,
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):
scene_left = QtCore.Signal()
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__()
self.mode = 'idle'
self.context = context
......@@ -273,7 +278,8 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
# buttonSelected is used to set which symbol to draw
# on next scene click (see mousePressEvent)
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.click_coordinates = None
self.process_name = 'opengeode'
......@@ -922,9 +928,9 @@ class SDL_Scene(QtGui.QGraphicsScene, object):
# 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)
elif item_type == Procedure:
# Create a sub-scene for the procedure
subscene = SDL_Scene(context='procedure')
elif item_type in (Procedure, State):
# Create a sub-scene for the procedure or nested state
subscene = SDL_Scene(context=item_type.__name__.lower())
subscene.messages_window = self.messages_window
item.nested_scene = subscene
......
......@@ -12,6 +12,7 @@
<file>icons/procedurestop.png</file>
<file>icons/procedure.png</file>
<file>icons/start.png</file>
<file>icons/statestart.png</file>
<file>icons/state.png</file>
<file>icons/task.png</file>
<file>icons/textsymbol.png</file>
......
......@@ -111,8 +111,9 @@ tokens {
BLOCK;
PARAMNAMES;
ASN1;
FLOATING_LABEL;
FLOATING_LABEL;
COMPOSITE_STATE;
CONNECT;
}
......@@ -307,7 +308,7 @@ floating_label
state
: cif?
: cif?
hyperlink?
STATE statelist e=end
(state_part)*
......@@ -317,7 +318,7 @@ state
statelist
: ((statename)(',' statename)*)
-> ^(STATELIST statename+)
-> ^(STATELIST statename+)
| ASTERISK exception_state?
-> ^(ASTERISK exception_state?);
......@@ -330,17 +331,17 @@ exception_state
composite_state
: STATE statename e=end
SUBSTRUCTURE
cnx=connection_points*
connection_points*
body=composite_state_body
ENDSUBSTRUCTURE statename? f=end
-> ^(COMPOSITE_STATE statename $cnx* $body $e?);
-> ^(COMPOSITE_STATE statename connection_points* $body $e?);
connection_points
: IN state_entry_exit_points e=end
-> ^(IN state_entry_exit_points $e?)
| OUT state_entry_exit_points f=end
-> ^(OUT state_entry_exit_points $f?);
: IN state_entry_exit_points end
-> ^(IN state_entry_exit_points end?)
| OUT state_entry_exit_points end
-> ^(OUT state_entry_exit_points end?);
state_entry_exit_points
......@@ -358,7 +359,21 @@ state_part
//| priority_input // Not supported
| save_part // Not supported in openGEODE
| spontaneous_transition
| continuous_signal; // Not supoorted in openGEODE
| continuous_signal // Not supoorted in openGEODE
| connect_part;
// connect part is used to connect nested state exit points to a transition
connect_part
: CONNECT connect_list? end
transition
-> ^(CONNECT connect_list? end? transition);
connect_list
: state_exit_point_name (',' state_exit_point_name)*
-> state_exit_point_name+
| ASTERISK;
spontaneous_transition
......@@ -1040,6 +1055,8 @@ dash_nextstate : DASH;
connector_name : ID;
signal_id : ID;
statename : ID;
state_exit_point_name
: ID;
state_entry_point_name
: ID;
variable_id : ID;
......
This diff is collapsed.
This diff is collapsed.
......@@ -19,7 +19,8 @@
__all__ = ['Input', 'Output', 'State', 'Task', 'ProcedureCall', 'Label',
'Decision', 'DecisionAnswer', 'Join', 'Start', 'TextSymbol',
'Procedure', 'ProcedureStart', 'ProcedureStop', 'ASN1Viewer']
'Procedure', 'ProcedureStart', 'ProcedureStop', 'ASN1Viewer',
'StateStart']
from genericSymbols import(
HorizontalSymbol, VerticalSymbol, Connection, Comment)
......@@ -52,7 +53,7 @@ SDL_REDBOLD = ['\\b{word}\\b'.format(word=word) for word in (
# pylint: disable=R0904
class Input(HorizontalSymbol, object):
class Input(HorizontalSymbol):
''' SDL INPUT Symbol '''
_unique_followers = ['Comment']
_insertable_followers = ['Task', 'ProcedureCall', 'Output', 'Decision',
......@@ -86,7 +87,6 @@ class Input(HorizontalSymbol, object):
_, syntax_errors, ___, ____, _____ = self.parser.parseSingleElement(
self.common_name, self.pr())
try:
# LOG.error('\n'.join(syntax_errors))
self.scene().raise_syntax_errors(syntax_errors)
except AttributeError:
LOG.debug('raise_syntax_error raised an exception')
......@@ -133,8 +133,14 @@ class Input(HorizontalSymbol, object):
return '\n'.join(result)
class Connect(Input):
''' Connect point below a nested state '''
common_name = 'connect_part'
# TODO
# pylint: disable=R0904
class Output(VerticalSymbol, object):
class Output(VerticalSymbol):
''' SDL OUTPUT Symbol '''
_unique_followers = ['Comment']
_insertable_followers = [
......@@ -184,7 +190,7 @@ class Output(VerticalSymbol, object):
# pylint: disable=R0904
class Decision(VerticalSymbol, object):
class Decision(VerticalSymbol):
''' SDL DECISION Symbol '''
_unique_followers = ['Comment']
_insertable_followers = ['DecisionAnswer', 'Task', 'ProcedureCall', 'Output',
......@@ -220,7 +226,7 @@ class Decision(VerticalSymbol, object):
for branch in self.branches():
if not branch.last_branch_item.terminal_symbol:
return False
else:
else:
return True
def branches(self):
......@@ -351,7 +357,7 @@ class Decision(VerticalSymbol, object):
# pylint: disable=R0904
class DecisionAnswer(HorizontalSymbol, object):
class DecisionAnswer(HorizontalSymbol):
''' If Decision is a "switch", DecisionAnswer is a "case" '''
_insertable_followers = ['DecisionAnswer', 'Task', 'ProcedureCall',
'Output', 'Decision', 'Label']
......@@ -441,7 +447,7 @@ class DecisionAnswer(HorizontalSymbol, object):
# pylint: disable=R0904
class Join(VerticalSymbol, object):
class Join(VerticalSymbol):
''' JOIN symbol (GOTO) '''
auto_expand = False
arrow_head = True
......@@ -453,7 +459,7 @@ class Join(VerticalSymbol, object):
def __init__(self, parent=None, ast=None):
if not ast:
ast = ogAST.Terminator(defName='Go')
ast = ogAST.Terminator(defName='')
ast.pos_y = 0
ast.width = 35
ast.height = 35
......@@ -491,7 +497,7 @@ class Join(VerticalSymbol, object):
h=int(self.boundingRect().height())))
class ProcedureStop(Join, object):
class ProcedureStop(Join):
''' Procedure STOP symbol - very similar to JOIN '''
# Define reserved keywords for the syntax highlighter
blackbold = SDL_BLACKBOLD
......@@ -535,7 +541,7 @@ class ProcedureStop(Join, object):
# pylint: disable=R0904
class Label(VerticalSymbol, object):
class Label(VerticalSymbol):
''' LABEL symbol '''
_insertable_followers = [
'Task', 'ProcedureCall', 'Output', 'Decision', 'Label']
......@@ -619,7 +625,7 @@ class Label(VerticalSymbol, object):
# pylint: disable=R0904
class Task(VerticalSymbol, object):
class Task(VerticalSymbol):
''' TASK symbol '''
_unique_followers = ['Comment']
_insertable_followers = [
......@@ -668,7 +674,7 @@ class Task(VerticalSymbol, object):
# pylint: disable=R0904
class ProcedureCall(VerticalSymbol