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

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):
......
...@@ -732,6 +732,159 @@ def find_type(path, context): ...@@ -732,6 +732,159 @@ def find_type(path, context):
return result return result
def fix_expression_types(expr, context):
''' Check/ensure type consistency in expressions having two sides '''
if isinstance(expr, ogAST.Primary):
return
for side in ('left', 'right'):
# Determine if the expression is a variable
uk_expr = getattr(expr, side)
if uk_expr.exprType == UNKNOWN_TYPE \
and isinstance(uk_expr, ogAST.PrimPath) \
and len(uk_expr.value) == 1:
try:
#exprType = find_variable(uk_expr.inputString, context)
exprType = find_variable(uk_expr.value[0], context)
setattr(expr, side, ogAST.PrimVariable(primary=uk_expr))
getattr(expr, side).exprType = exprType
except AttributeError:
pass
# If a side of the expression is of Enumerated of Choice type, check if
# the other side is a literal of that sort, and change type accordingly
for side in (('left', 'right'), ('right', 'left')):
side_type = find_basic_type(getattr(expr, side[0]).exprType).kind
if side_type == 'EnumeratedType':
prim = ogAST.PrimEnumeratedValue(primary=getattr(expr, side[1]))
elif side_type == 'ChoiceEnumeratedType':
prim = ogAST.PrimChoiceDeterminant(primary=getattr(expr, side[1]))
try:
check_type_compatibility(prim, getattr(expr, side[0]).exprType,
context)
setattr(expr, side[1], prim)
getattr(expr, side[1]).exprType = getattr(expr, side[0]).exprType
except (UnboundLocalError, AttributeError, TypeError):
pass
# If a side type remains unknown, check if it is an ASN.1 constant
for side in (('left', 'right'), ('right', 'left')):
value = getattr(expr, side[0])
if value.exprType == UNKNOWN_TYPE and is_constant(value):
setattr(expr, side[0], ogAST.PrimConstant(primary=value))
getattr(expr, side[0]).exprType = getattr(expr, side[1]).exprType
for side in (expr.right, expr.left):
if side.is_raw:
raw_expr = side
else:
typed_expr = side
ref_type = typed_expr.exprType
# Type check that is specific to IN expressions
if isinstance(expr, ogAST.ExprIn):
# check that left part is a SEQUENCE OF or a string
container_basic_type = find_basic_type(expr.left.exprType)
if container_basic_type.kind == 'SequenceOfType':
ref_type = container_basic_type.type
elif container_basic_type.kind.endswith('StringType'):
ref_type = container_basic_type
else: