Commit 7abbb982 authored by Maxime Perrotin's avatar Maxime Perrotin
Browse files

Use Helper module in backends

parent 58bf16f3
......@@ -74,6 +74,7 @@ from itertools import chain
from singledispatch import singledispatch
import ogAST
import Helper
LOG = logging.getLogger(__name__)
......@@ -92,7 +93,9 @@ INNER_PROCEDURES = []
@singledispatch
def generate(ast):
''' Generate the code for an item of the AST '''
_ = ast
raise TypeError('[AdaGenerator] Unsupported AST construct')
return [], []
# Processing of the AST
......@@ -110,123 +113,8 @@ def _process(process):
LOG.info('Generating Ada code for process ' + str(process_name))
# 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.entrypoint.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)
prefix = state.statename + '_'
state.mapping = {prefix + key:state.mapping.pop(key)
for key in state.mapping.keys()}
process.transitions.extend(state.transitions)
# Add prefix to local variable names and push them at process level
for dcl in state.variables.viewkeys():
rename_everything(state.content, dcl, prefix + dcl)
state.variables = {prefix + key: state.variables.pop(key)
for key in state.variables.keys()}
process.variables.update(state.variables)
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)
# If composite state has entry procedures, add the call
if state.entry_procedure:
for each in (trans for trans in state.mapping.viewvalues()
if isinstance(trans, int)):
call_entry = ogAST.ProcedureCall()
call_entry.inputString = 'entry'
call_entry.output = [{'outputName': prefix + 'entry',
'params': [], 'tmpVars': []}]
process.transitions[each].actions.insert(0, call_entry)
# If composite state has exit procedure, add the call
if state.exit_procedure:
for each in chain(state.transitions, (lab.transition for lab in
state.content.floating_labels)):
if each.terminator.kind == 'return':
call_exit = ogAST.ProcedureCall()
call_exit.inputString = 'exit'
call_exit.output = [{'outputName': prefix + 'exit',
'params': [], 'tmpVars': []}]
each.actions.append(call_exit)
for inner in state.composite_states:
# Go recursively in inner composite states
inner.statename = prefix + inner.statename
update_composite_state(inner, process)
for each in state.terminators:
# Give prefix to terminators
if each.label:
each.label.inputString = prefix + each.label.inputString
if each.kind == 'next_state':
each.inputString = prefix + each.inputString
# Set next transition id
update_terminator(state, each, process)
elif each.kind == 'join':
rename_everything(state.content,
each.inputString,
prefix + each.inputString)
for each in state.labels:
# Give prefix to labels in transitions
rename_everything(state.content,
each.inputString,
prefix + each.inputString)
# Add prefixed floating labels of the composite state to the process
process.content.floating_labels.extend(state.content.floating_labels)
# Rename inner procedures
for each in state.content.inner_procedures:
rename_everything(state.content, each.inputString,
prefix + each.inputString)
each.inputString = prefix + each.inputString
process.content.inner_procedures.extend(state.content.inner_procedures)
def propagate_inputs(nested_state, inputlist):
''' Nested states: Inputs at level N must be handled at level N-1
that is, all inputs of a composite states (the ones that allow
to exit the composite state from the outer scope) must be
processed by each of the substates.
'''
for _, val in nested_state.mapping.viewitems():
try:
val.extend(inputlist)
except AttributeError:
pass
for each in nested_state.composite_states:
# do the same recursively
propagate_inputs(each, nested_state.mapping[nested.statename])
del nested_state.mapping[nested.statename]
for each in process.composite_states:
update_composite_state(each, process)
propagate_inputs(each, process.mapping[each.statename])
del process.mapping[each.statename]
# Update terminators at process level
for each in process.terminators:
if each.kind == 'next_state':
update_terminator(process, each, process)
# In case model has nested states, flatten everything
Helper.flatten(process)
VARIABLES.update(process.variables)
INNER_PROCEDURES.extend(process.content.inner_procedures)
......@@ -242,7 +130,7 @@ def _process(process):
process_level_decl.append(
'l_{n} : aliased asn1Scc{t}{default};'.format(
n=var_name,
t=var_type.ReferencedTypeName.replace('-','_'),
t=var_type.ReferencedTypeName.replace('-', '_'),
default=' := ' + dstr if def_value else ''))
......@@ -433,21 +321,13 @@ package {process_name} is'''.format(process_name=process_name,
taste_template.append('procedure runTransition(Id: Integer) is')
taste_template.append('trId : Integer := Id;')
# Transform inner labels to floating labels
Helper.inner_labels_to_floating(process)
# Generate the code for all transitions
code_transitions = []
local_decl_transitions = []
# Transform inner labels of both floating labels and transitions
# into new floating labels, so that they get generated in a separate
# section of the Ada code, where they are in the scope of everybody
for idx in xrange(len(process.content.floating_labels)):
for new_floating in find_labels(
process.content.floating_labels[idx].transition):
process.content.floating_labels.append(new_floating)
for proc_tr in process.transitions:
for new_floating in find_labels(proc_tr):
process.content.floating_labels.append(new_floating)
code_tr, tr_local_decl = generate(proc_tr)
code_transitions.append(code_tr)
local_decl_transitions.extend(tr_local_decl)
......@@ -532,8 +412,7 @@ def write_statement(param, newline):
if type_kind.endswith('StringType'):
if isinstance(param, ogAST.PrimStringLiteral):
# Raw string
string =('"' +
param.value[1:-1].replace('"', "'") + '"')
string = '"' + param.value[1:-1].replace('"', "'") + '"'
else:
# XXX Cannot print an octet string like that...
code, string, local = generate(param)
......@@ -665,7 +544,7 @@ def _call_external_function(output):
else:
# inner procedure call
list_of_params = []
for param in (out.get('params') or []):
for param in out.get('params', []):
p_code, p_id, p_local = generate(param)
code.extend(p_code)
local_decl.extend(p_local)
......@@ -1014,7 +893,7 @@ def _bitwise_operators(expr):
def _append(expr):
''' Generate code for the APPEND construct: a // b '''
stmts, ada_string, local_decl = [], '', []
basic_type_expr = find_basic_type(expr.exprType)
# basic_type_expr = find_basic_type(expr.exprType)
# We can do a length check if both strings are literals
# XXX should be already done by the parser
# if(expr.right.kind == 'primary'
......@@ -1171,7 +1050,8 @@ def _constant(primary):
def _mantissa_base_exp(primary):
''' Generate code for a Real with Mantissa-base-Exponent representation '''
# TODO
pass
_ = primary
return [], '', []
@generate.register(ogAST.PrimIfThenElse)
def _if_then_else(ifThenElse):
......@@ -1208,10 +1088,6 @@ def _sequence(seq):
stmts, local_decl = [], []
seqType = seq.exprType
LOG.debug('PrimSequence: ' + str(seq) + str(seqType))
if seqType.kind == 'ReferenceType':
seq_ty = seqType.ReferencedTypeName
else:
seq_ty = seqType
ada_string = "asn1Scc{seqType}'(".format(
seqType=seqType.ReferencedTypeName.replace('-', '_'))
......@@ -1360,11 +1236,11 @@ def _decision(dec):
return code, local_decl
@generate.register(ogAST.Label)
def _label(tr):
def _label(lab):
''' Transition following labels are generated in a separate section
for visibility reasons (see Ada scope)
'''
return ['goto {label};'.format(label=tr.inputString)], []
return ['goto {label};'.format(label=lab.inputString)], []
@generate.register(ogAST.Transition)
......@@ -1492,12 +1368,7 @@ def _inner_procedure(proc):
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)
Helper.inner_labels_to_floating(proc)
tr_code, tr_decl = generate(proc.content.start.transition)
# Generate code for the floating labels
......@@ -1523,7 +1394,7 @@ def _inner_procedure(proc):
# A few helper functions needed by the Ada backend
def find_basic_type(a_type):
''' Return the ASN.1 basic type of aType '''
''' Return the ASN.1 basic type of a_type '''
basic_type = a_type
while basic_type.kind == 'ReferenceType':
# Find type with proper case in the data view
......@@ -1593,191 +1464,6 @@ def traceability(symbol):
trace.extend(traceability(symbol.comment))
return trace
def find_labels(trans):
'''
Yield a list of transition actions whenever a label is found
Used to transform labels into floating labels so that the gotos
in Ada can be contained within a single scope.
'''
if not trans:
return
# Terminators can have a label - add it to the transition actions
# (to trigger a goto at code generation)
if trans.terminator and trans.terminator.label:
trans.actions.append(trans.terminator.label)
trans.terminator.label = None
# Then for each action, check if there are labels and yield
# a new transition with the remaining actions (following the label)
for idx, action in enumerate(trans.actions):
if isinstance(action, ogAST.Label):
new_trans = ogAST.Transition()
# Create a floating label
flab = ogAST.Floating_label(label=action)
new_trans.actions = trans.actions[slice(idx+1, len(trans.actions))]
new_trans.terminator = trans.terminator
new_trans.terminators = trans.terminators
flab.transition = new_trans
# Transform the label into a JOIN in the original transition
trans.actions[idx:]=[]
trans.terminator = ogAST.Terminator()
trans.terminator.inputString = action.inputString
trans.terminator.kind = 'join'
# Recursively find labels in the new transition
for flabel in find_labels(flab.transition):
yield flabel
# Then yield the new transition
yield flab
elif isinstance(action, ogAST.Decision):
for answer in action.answers:
for new_fl in find_labels(answer.transition):
# Append the remaining actions of the transition
if not new_fl.transition.terminator:
new_fl.transition.actions.extend(
trans.actions[slice(idx+1, len(trans.actions))])
new_fl.transition.terminator = trans.terminator
yield new_fl
@singledispatch
def rename_everything(ast, from_name, to_name):
'''
Rename in all symbols all occurences of name_ref into new_name.
This is used to avoid name clashes in nested diagrams when they get
flattened. For example rename all accesses to a variable declared
in the scope of a composite state, so that they do not overwrite
a variable with the same name declared at a higher scope.
'''
pass
@rename_everything.register(ogAST.Automaton)
def _rename_automaton(ast, from_name, to_name):
''' Renaming at Automaton top level (content of digragrams) '''
transitions = []
if ast.start:
rename_everything(ast.start.transition, from_name, to_name)
for each in ast.named_start:
rename_everything(each.transition, from_name, to_name)
for each in ast.floating_labels:
rename_everything(each, from_name, to_name)
for each in ast.states:
for inp in each.inputs:
rename_everything(inp.transition, from_name, to_name)
for idx, param in enumerate(inp.parameters):
if param.lower() == from_name.lower():
inp.parameter[idx] = to_name
if each.composite:
rename_everything(each.composite.content, from_name, to_name)
for each in ast.inner_procedures:
# Check that from_name is not a redefined variable in the procedure
for varname in each.variables.viewkeys():
if varname.lower() == from_name:
break
else:
rename_everything(each.content, from_name, to_name)
@rename_everything.register(ogAST.Output)
@rename_everything.register(ogAST.ProcedureCall)
def _rename_output(ast, from_name, to_name):
''' Rename actual parameter names in output/procedure calls
and possibly the procedure name '''
for each in ast.output:
if each['outputName'].lower() == from_name.lower():
each['outputName'] = to_name
for param in each['params']:
rename_everything(param, from_name, to_name)
@rename_everything.register(ogAST.Label)
def _rename_label(ast, from_name, to_name):
''' Rename elements in the transition following a label '''
if ast.inputString.lower() == from_name.lower():
ast.inputString = to_name
rename_everything(ast.transition, from_name, to_name)
@rename_everything.register(ogAST.Decision)
def _rename_decision(ast, from_name, to_name):
''' Rename elements in decision '''
if ast.kind == 'question':
rename_everything(ast.question)
for each in ast.answers:
rename_everything(each, from_name, to_name)
@rename_everything.register(ogAST.Answer)
def _rename_answer(ast, from_name, to_name):
''' Rename elements in an answer branch '''
if ast.kind in ('constant', 'open_range'):
rename_everything(ast.constant, from_name, to_name)
elif ast.kind == 'closed_range':
pass # TODO when supported
rename_everything(ast.transition, from_name, to_name)
@rename_everything.register(ogAST.Transition)
def _rename_transition(ast, from_name, to_name):
''' Rename in all symbols of a transition '''
for each in chain(ast.actions, ast.terminators):
# Label, output, task, decision, terminators
rename_everything(each, from_name, to_name)
@rename_everything.register(ogAST.Terminator)
def _rename_terminator(ast, from_name, to_name):
''' Rename terminators: join/labels, next_state '''
if ast.inputString.lower() == from_name.lower():
ast.inputString = to_name
rename_everything(ast.label, from_name, to_name)
rename_everything(ast.return_expr, from_name, to_name)
@rename_everything.register(ogAST.TaskAssign)
def _rename_task_assign(ast, from_name, to_name):
''' List of assignments '''
for each in ast.elems:
rename_everything(each, from_name, to_name)
@rename_everything.register(ogAST.TaskForLoop)
def _rename_forloop(ast, from_name, to_name):
''' List of FOR loops '''
for each in ast.elems:
rename_everything(each['list'], from_name, to_name)
rename_everything(each['range']['start'], from_name, to_name)
rename_everything(each['range']['stop'], from_name, to_name)
rename_everything(each['transition'], from_name, to_name)
@rename_everything.register(ogAST.ExprPlus)
@rename_everything.register(ogAST.ExprMul)
@rename_everything.register(ogAST.ExprMinus)
@rename_everything.register(ogAST.ExprEq)
@rename_everything.register(ogAST.ExprNeq)
@rename_everything.register(ogAST.ExprGt)
@rename_everything.register(ogAST.ExprGe)
@rename_everything.register(ogAST.ExprLt)
@rename_everything.register(ogAST.ExprLe)
@rename_everything.register(ogAST.ExprDiv)
@rename_everything.register(ogAST.ExprMod)
@rename_everything.register(ogAST.ExprRem)
@rename_everything.register(ogAST.ExprAssign)
@rename_everything.register(ogAST.ExprOr)
@rename_everything.register(ogAST.ExprIn)
@rename_everything.register(ogAST.ExprAnd)
@rename_everything.register(ogAST.ExprXor)
@rename_everything.register(ogAST.ExprAppend)
@rename_everything.register(ogAST.ExprAssign)
def _rename_expr(ast, from_name, to_name):
''' Two-sided expressions '''
rename_everything(ast.left, from_name, to_name)
rename_everything(ast.right, from_name, to_name)
@rename_everything.register(ogAST.PrimPath)
@rename_everything.register(ogAST.PrimVariable)
def _rename_path(ast, from_name, to_name):
''' Ultimate seek point for the renaming: primary path/variables '''
if ast.value[0].lower() == from_name.lower():
ast.value[0] = to_name
def format_ada_code(stmts):
''' Indent properly the Ada code '''
indent = 0
......
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
OpenGEODE - A tiny SDL Editor for TASTE
This module provides helper functions typically used by backends:
flatten(ast) : transform a model with nested states to a flat model
rename_everything(ast, from_name, to_name) : rename symbols
Copyright (c) 2012-2014 European Space Agency
Designed and implemented by Maxime Perrotin
Contact: maxime.perrotin@esa.int
"""
import logging
from itertools import chain
from singledispatch import singledispatch
import ogAST
LOG = logging.getLogger(__name__)
__all__ = ['flatten', 'rename_everything', 'inner_labels_to_floating']
def inner_labels_to_floating(process):
'''
Transform inner labels of both floating labels and transitions
into new floating labels, so that they can be generated in a separate
section of code, where they are in the scope of everybody
Works with processes, procedures and nested states
'''
for idx in xrange(len(process.content.floating_labels)):
for new_floating in find_labels(
process.content.floating_labels[idx].transition):
process.content.floating_labels.append(new_floating)
for proc_tr in process.transitions:
for new_floating in find_labels(proc_tr):
process.content.floating_labels.append(new_floating)
for new_floating in find_labels(process.content.start.transition):
process.content.floating_labels.append(new_floating)
for each in process.content.named_start:
for new_floating in find_labels(each.transition):
process.content.floating_labels.append(new_floating)
def flatten(process):
''' Flatten the nested states:
Rename inner states, procedures, etc. and move them to process level
'''
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.entrypoint.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)
prefix = state.statename + '_'
state.mapping = {prefix + key:state.mapping.pop(key)
for key in state.mapping.keys()}
process.transitions.extend(state.transitions)
# Add prefix to local variable names and push them at process level
for dcl in state.variables.viewkeys():
rename_everything(state.content, dcl, prefix + dcl)
state.variables = {prefix + key: state.variables.pop(key)
for key in state.variables.keys()}
process.variables.update(state.variables)
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)
# If composite state has entry procedures, add the call
if state.entry_procedure:
for each in (trans for trans in state.mapping.viewvalues()
if isinstance(trans, int)):
call_entry = ogAST.ProcedureCall()
call_entry.inputString = 'entry'
call_entry.output = [{'outputName': prefix + 'entry',
'params': [], 'tmpVars': []}]
process.transitions[each].actions.insert(0, call_entry)
# If composite state has exit procedure, add the call
if state.exit_procedure:
for each in chain(state.transitions, (lab.transition for lab in
state.content.floating_labels)):
if each.terminator.kind == 'return':
call_exit = ogAST.ProcedureCall()
call_exit.inputString = 'exit'
call_exit.output = [{'outputName': prefix + 'exit',
'params': [], 'tmpVars': []}]
each.actions.append(call_exit)
for inner in state.composite_states:
# Go recursively in inner composite states
inner.statename = prefix + inner.statename
update_composite_state(inner, process)
for each in state.terminators:
# Give prefix to terminators
if each.label:
each.label.inputString = prefix + each.label.inputString
if each.kind == 'next_state':
each.inputString = prefix + each.inputString
# Set next transition id
update_terminator(state, each, process)
elif each.kind == 'join':
rename_everything(state.content,
each.inputString,
prefix + each.inputString)
for each in state.labels:
# Give prefix to labels in transitions
rename_everything(state.content,
each.inputString,
prefix + each.inputString)
# Add prefixed floating labels of the composite state to the process
process.content.floating_labels.extend(state.content.floating_labels)
# Rename inner procedures
for each in state.content.inner_procedures:
rename_everything(state.content, each.inputString,
prefix + each.inputString)
each.inputString = prefix + each.inputString
process.content.inner_procedures.extend(state.content.inner_procedures)
def propagate_inputs(nested_state, inputlist):
''' Nested states: Inputs at level N must be handled at level N-1
that is, all inputs of a composite states (the ones that allow
to exit the composite state from the outer scope) must be
processed by each of the substates.
'''
for _, val in nested_state.mapping.viewitems():
try:
val.extend(inputlist)
except AttributeError:
pass
for each in nested_state.composite_states:
# do the same recursively
propagate_inputs(each, nested_state.mapping[each.statename])
del nested_state.mapping[each.statename]
for each in process.composite_states: