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):
......
......@@ -732,6 +732,159 @@ def find_type(path, context):
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:
raise TypeError('IN expression: right part must be a list')
compare_types(expr.right.exprType, ref_type)
return
if expr.right.is_raw == expr.left.is_raw == False:
unknown = [uk_expr for uk_expr in expr.right, expr.left
if uk_expr.exprType == UNKNOWN_TYPE]
if unknown:
raise TypeError('Cannot resolve type of "{}"'
.format(unknown[0].inputString))
# In Sequence, Choice, SEQUENCE OF, and IfThenElse expressions,
# we must fix missing inner types
# (due to similarities, the following should be refactored FIXME)
if isinstance(expr.right, ogAST.PrimSequence):
# left side must have a known type
asn_type = find_basic_type(expr.left.exprType)
if asn_type.kind != 'SequenceType':
raise TypeError('left side must be a SEQUENCE type')
for field, fd_expr in expr.right.value.viewitems():
if fd_expr.exprType == UNKNOWN_TYPE:
try:
expected_type = asn_type.Children.get(
field.replace('_', '-')).type
except AttributeError:
raise TypeError('Field not found: ' + field)
check_expr = ogAST.ExprAssign()
check_expr.left = ogAST.PrimPath()
check_expr.left.exprType = expected_type
check_expr.right = fd_expr
fix_expression_types(check_expr, context)
# Id of fd_expr may have changed (enumerated, choice)
expr.right.value[field] = check_expr.right
elif isinstance(expr.right, ogAST.PrimChoiceItem):
asn_type = find_basic_type(expr.left.exprType)
field = expr.right.value['choice'].replace('_', '-')
if asn_type.kind != 'ChoiceType' \
or field.lower() not in [key.lower()
for key in asn_type.Children.viewkeys()]:
raise TypeError('Field is not valid in CHOICE:' + field)
key, = [key for key in asn_type.Children.viewkeys()
if key.lower() == field.lower()]
if expr.right.value['value'].exprType == UNKNOWN_TYPE:
try:
expected_type = asn_type.Children.get(key).type
except AttributeError:
raise TypeError('Field not found in CHOICE: ' + field)
check_expr = ogAST.ExprAssign()
check_expr.left = ogAST.PrimPath()
check_expr.left.exprType = expected_type
check_expr.right = expr.right.value['value']
fix_expression_types(check_expr, context)
expr.right.value['value'] = check_expr.right
elif isinstance(expr.right, ogAST.PrimIfThenElse):
for det in ('then', 'else'):
if expr.right.value[det].exprType == UNKNOWN_TYPE:
expr.right.value[det].exprType = expr.left.exprType
# Recursively fix possibly missing types in the expression
check_expr = ogAST.ExprAssign()
check_expr.left = ogAST.PrimPath()
check_expr.left.exprType = expr.left.exprType
check_expr.right = expr.right.value[det]
fix_expression_types(check_expr, context)
expr.right.value[det] = check_expr.right
elif isinstance(expr.right, ogAST.PrimSequenceOf):
asn_type = find_basic_type(expr.left.exprType).type
for idx, elem in enumerate(expr.right.value):
check_expr = ogAST.ExprAssign()
check_expr.left = ogAST.PrimPath()
check_expr.left.exprType = asn_type
check_expr.right = elem
fix_expression_types(check_expr, context)
expr.right.value[idx] = check_expr.right
# the type of the raw PrimSequenceOf can be set now
expr.right.exprType.type = asn_type
if isinstance(expr, (ogAST.ExprAnd, ogAST.ExprOr, ogAST.ExprXor)):
# Bitwise operators: check that both sides are booleans
for side in expr.left, expr.right:
basic_type = find_basic_type(side.exprType)
if basic_type.kind in ('BooleanType', 'BitStringType'):
continue
elif basic_type.kind == 'SequenceOfType':
if find_basic_type(side.exprType).type.kind == 'BooleanType':
continue
else:
raise TypeError('Bitwise operators only work with '
'booleans and arrays of booleans')
if expr.right.is_raw != expr.left.is_raw:
check_type_compatibility(raw_expr, ref_type, context)
raw_expr.exprType = ref_type
else:
compare_types(expr.left.exprType, expr.right.exprType)
def expression_list(root, context):
''' Parse a list of expression parameters '''
errors = []
......@@ -1157,9 +1310,96 @@ def fpar(root):
str(child.type))
return params, errors, warnings
def composite_state(root, parent=None, context=None):
''' Parse a composite state definition '''
comp = ogAST.CompositeState()
errors, warnings = [], []
# Create a list of all inherited data
try:
comp.global_variables = dict(context.variables)
comp.global_variables.update(context.global_variables)
comp.input_signals = context.input_signals
comp.output_signals = context.output_signals
comp.procedures = context.procedures
comp.operators = dict(context.operators)
except AttributeError:
LOG.debug('Procedure context is undefined')
# Gather the list of states defined in the composite state
# and map a list of transitions to each state
comp.mapping = {name: [] for name in get_state_list(root)}
for child in root.getChildren():
if child.type == lexer.ID:
comp.line = child.getLine()
comp.charPositionInLine = child.getCharPositionInLine()
comp.statename = child.toString().lower()
elif child.type == lexer.COMMENT:
comp.comment, _, _ = end(child)
elif child.type == lexer.IN:
# state entry point
for point in child.getChildren():
comp.state_entrypoints.append(point.toString().lower())
elif child.type == lexer.OUT:
# state exit point
for point in child.getChildren():
comp.state_exitpoints.append(point.toString().lower())
elif child.type == lexer.TEXTAREA:
textarea, err, warn = text_area(child, context=comp)
errors.extend(err)
warnings.extend(warn)
comp.content.textAreas.append(textarea)
elif child.type == lexer.PROCEDURE:
new_proc, err, warn = procedure(child, context=proc)
errors.extend(err)
warnings.extend(warn)
if new_proc.inputString.strip().lower() == 'entry':
comp.entry_procedure = new_proc
elif new_proc.inputString.strip().lower() == 'exit':
comp.exit_procedure = new_proc
comp.content.inner_procedures.append(new_proc)
elif child.type == lexer.COMPOSITE_STATE:
inner_comp, err, warn = composite_state(child,
parent=None,
context=comp)
errors.extend(err)
warnings.extend(warn)
comp.composite_states.append(comp)
warnings.append('Inner Composite state detected')
elif child.type == lexer.STATE:
# STATE - fills up the 'mapping' structure.
newstate, err, warn = state(child, parent=None, context=comp)
errors.extend(err)
warnings.extend(warn)
comp.content.states.append(newstate)
elif child.type == lexer.FLOATING_LABEL:
lab, err, warn = floating_label(child, parent=None, context=comp)
errors.extend(err)
warnings.extend(warn)
comp.content.floating_labels.append(lab)
elif child.type == lexer.START:
# START transition (fills the mapping structure)
st, err, warn = start(child, context=comp)
errors.extend(err)
warnings.extend(warn)
if st.inputString: