Commit f98201dc authored by Maxime Perrotin's avatar Maxime Perrotin
Browse files

Minor fixes

parent f1746aa7
...@@ -69,6 +69,8 @@ ...@@ -69,6 +69,8 @@
import logging import logging
from itertools import chain
from singledispatch import singledispatch from singledispatch import singledispatch
import ogAST import ogAST
...@@ -98,7 +100,6 @@ def generate(ast): ...@@ -98,7 +100,6 @@ def generate(ast):
def _process(process): def _process(process):
''' Generate the code for a complete process (AST Top level) ''' ''' Generate the code for a complete process (AST Top level) '''
process_name = process.processName process_name = process.processName
VARIABLES.update(process.variables)
global TYPES global TYPES
TYPES = process.dataview TYPES = process.dataview
del OUT_SIGNALS[:] del OUT_SIGNALS[:]
...@@ -106,24 +107,9 @@ def _process(process): ...@@ -106,24 +107,9 @@ def _process(process):
del INNER_PROCEDURES[:] del INNER_PROCEDURES[:]
OUT_SIGNALS.extend(process.output_signals) OUT_SIGNALS.extend(process.output_signals)
PROCEDURES.extend(process.procedures) PROCEDURES.extend(process.procedures)
INNER_PROCEDURES.extend(process.content.inner_procedures)
LOG.info('Generating Ada code for process ' + str(process_name)) LOG.info('Generating Ada code for process ' + str(process_name))
# Generate the code to declare process-level variables
process_level_decl = []
for var_name, (var_type, def_value) in process.variables.viewitems():
if def_value:
# Expression must be a ground expression, i.e. must not
# require temporary variable to store computed result
dst, dstr, dlocal = generate(def_value)
assert not dst and not dlocal, 'DCL: Expecting a ground expression'
process_level_decl.append(
'l_{n} : aliased asn1Scc{t}{default};'.format(
n=var_name,
t=var_type.ReferencedTypeName.replace('-','_'),
default=' := ' + dstr if def_value else ''))
# Flatten the nested states, add states to the process state list # Flatten the nested states, add states to the process state list
# Requires renaming of nested states and setting chaining of transitions # Requires renaming of nested states and setting chaining of transitions
def update_terminator(context, term, process): def update_terminator(context, term, process):
...@@ -144,9 +130,18 @@ def _process(process): ...@@ -144,9 +130,18 @@ def _process(process):
to process, updating indexes, and update terminators to process, updating indexes, and update terminators
''' '''
trans_idx = len(process.transitions) trans_idx = len(process.transitions)
state.mapping = {state.statename + '_' + key:state.mapping.pop(key) prefix = state.statename + '_'
state.mapping = {prefix + key:state.mapping.pop(key)
for key in state.mapping.keys()} for key in state.mapping.keys()}
process.transitions.extend(state.transitions) 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(): for key, value in state.mapping.viewitems():
# Update transition indices # Update transition indices
if isinstance(value, int): if isinstance(value, int):
...@@ -155,17 +150,60 @@ def _process(process): ...@@ -155,17 +150,60 @@ def _process(process):
for inp in value: for inp in value:
inp.transition_id += trans_idx inp.transition_id += trans_idx
process.mapping.update(state.mapping) 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: for inner in state.composite_states:
inner.statename = state.statename + '_' + inner.statename # Go recursively in inner composite states
inner.statename = prefix + inner.statename
update_composite_state(inner, process) update_composite_state(inner, process)
for each in state.terminators: for each in state.terminators:
# Update state names in terminators and set next transition id # Give prefix to terminators
if each.label:
each.label.inputString = prefix + each.label.inputString
if each.kind == 'next_state': if each.kind == 'next_state':
each.inputString = state.statename + '_' + each.inputString each.inputString = prefix + each.inputString
# Set next transition id
update_terminator(state, each, process) 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): def propagate_inputs(nested_state, inputlist):
''' Nested states: Inputs at level N but be handled at level N-1 ''' 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 that is, all inputs of a composite states (the ones that allow
to exit the composite state from the outer scope) must be to exit the composite state from the outer scope) must be
processed by each of the substates. processed by each of the substates.
...@@ -190,6 +228,24 @@ def _process(process): ...@@ -190,6 +228,24 @@ def _process(process):
if each.kind == 'next_state': if each.kind == 'next_state':
update_terminator(process, each, process) update_terminator(process, each, process)
VARIABLES.update(process.variables)
INNER_PROCEDURES.extend(process.content.inner_procedures)
# Generate the code to declare process-level variables
process_level_decl = []
for var_name, (var_type, def_value) in process.variables.viewitems():
if def_value:
# Expression must be a ground expression, i.e. must not
# require temporary variable to store computed result
dst, dstr, dlocal = generate(def_value)
assert not dst and not dlocal, 'DCL: Expecting a ground expression'
process_level_decl.append(
'l_{n} : aliased asn1Scc{t}{default};'.format(
n=var_name,
t=var_type.ReferencedTypeName.replace('-','_'),
default=' := ' + dstr if def_value else ''))
# 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(name for name in process.mapping.iterkeys() states_decl += ', '.join(name for name in process.mapping.iterkeys()
...@@ -437,9 +493,9 @@ package {process_name} is'''.format(process_name=process_name, ...@@ -437,9 +493,9 @@ package {process_name} is'''.format(process_name=process_name,
# 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: #if code_labels:
taste_template.append('<<next_transition>>') taste_template.append('<<next_transition>>')
taste_template.append('null;') taste_template.append('null;')
taste_template.append('end loop;') taste_template.append('end loop;')
taste_template.append('end runTransition;') taste_template.append('end runTransition;')
taste_template.append('\n') taste_template.append('\n')
...@@ -587,14 +643,17 @@ def _call_external_function(output): ...@@ -587,14 +643,17 @@ def _call_external_function(output):
code.extend(p_code) code.extend(p_code)
local_decl.extend(p_local) local_decl.extend(p_local)
# Create a temporary variable for input parameters only # Create a temporary variable for input parameters only
if param_direction == 'in': # (If needed, i.e. if argument is not a local variable)
tmp_id = out['tmpVars'][idx] if param_direction == 'in' \
local_decl.append('tmp{idx} : aliased asn1Scc{oType};' and (not (isinstance(param, ogAST.PrimVariable) and
.format(idx=tmp_id, oType=typename)) p_id.startswith('l_')) or isinstance(param, ogAST.PrimFPAR)):
code.append('tmp{idx} := {p_id};'.format( tmp_id = out['tmpVars'][idx]
idx=tmp_id, p_id=p_id)) local_decl.append('tmp{idx} : aliased asn1Scc{oType};'
list_of_params.append("tmp{idx}'access". .format(idx=tmp_id, oType=typename))
format(idx=out['tmpVars'][idx])) code.append('tmp{idx} := {p_id};'
.format(idx=tmp_id, p_id=p_id))
list_of_params.append("tmp{idx}'access"
.format(idx=out['tmpVars'][idx]))
else: else:
# Output parameters - no need for a temp variable # Output parameters - no need for a temp variable
list_of_params.append("{var}'access".format(var=p_id)) list_of_params.append("{var}'access".format(var=p_id))
...@@ -1228,15 +1287,14 @@ def _decision(dec): ...@@ -1228,15 +1287,14 @@ def _decision(dec):
actual_type = getattr( actual_type = getattr(
question_type, 'ReferencedTypeName', None) or question_type.kind question_type, 'ReferencedTypeName', None) or question_type.kind
actual_type = actual_type.replace('-', '_') actual_type = actual_type.replace('-', '_')
basic = False basic = find_basic_type(question_type).kind in ('IntegerType',
if actual_type in ('IntegerType', 'Integer32Type', 'BooleanType', 'Integer32Type', 'BooleanType',
'RealType', 'EnumeratedType', 'ChoiceEnumeratedType'): 'RealType', 'EnumeratedType', 'ChoiceEnumeratedType')
basic = True
# for ASN.1 types, declare a local variable # for ASN.1 types, declare a local variable
# to hold the evaluation of the question # to hold the evaluation of the question
if not basic: if not basic:
local_decl.append('tmp{idx} : aliased asn1Scc{actType};'.format( local_decl.append('tmp{idx} : aliased asn1Scc{actType};'.format(
idx=dec.tmpVar, actType=actual_type)) idx=dec.tmpVar, actType=actual_type))
q_stmts, q_str, q_decl = generate(dec.question) q_stmts, q_str, q_decl = generate(dec.question)
# Add code-to-model traceability # Add code-to-model traceability
code.extend(traceability(dec)) code.extend(traceability(dec))
...@@ -1246,6 +1304,7 @@ def _decision(dec): ...@@ -1246,6 +1304,7 @@ def _decision(dec):
code.append('tmp{idx} := {q};'.format(idx=dec.tmpVar, q=q_str)) code.append('tmp{idx} := {q};'.format(idx=dec.tmpVar, q=q_str))
sep = 'if ' sep = 'if '
for a in dec.answers: for a in dec.answers:
code.extend(traceability(a))
if a.kind in ('open_range', 'constant'): if a.kind in ('open_range', 'constant'):
# Note: removed and a.transition here because empty transitions # Note: removed and a.transition here because empty transitions
# have a different meaning, and a "null;" statement has to be # have a different meaning, and a "null;" statement has to be
...@@ -1261,7 +1320,7 @@ def _decision(dec): ...@@ -1261,7 +1320,7 @@ def _decision(dec):
exp = 'not ' + exp exp = 'not ' + exp
else: else:
exp = 'tmp{idx} {op} {ans}'.format(idx=dec.tmpVar, exp = 'tmp{idx} {op} {ans}'.format(idx=dec.tmpVar,
op=OPERANDS[a.openRangeOp], ans=ans_str) op=a.openRangeOp.operand, ans=ans_str)
else: else:
exp = '{q} {op} {ans}'.format(q=q_str, exp = '{q} {op} {ans}'.format(q=q_str,
op=a.openRangeOp.operand, op=a.openRangeOp.operand,
...@@ -1334,6 +1393,7 @@ def _transition(tr): ...@@ -1334,6 +1393,7 @@ def _transition(tr):
if tr.terminator.next_id == -1: if tr.terminator.next_id == -1:
code.append('state := {nextState};'.format( code.append('state := {nextState};'.format(
nextState=tr.terminator.inputString)) nextState=tr.terminator.inputString))
code.append('goto next_transition;')
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))
...@@ -1348,9 +1408,11 @@ def _transition(tr): ...@@ -1348,9 +1408,11 @@ def _transition(tr):
(tr.terminator.return_expr) (tr.terminator.return_expr)
code.extend(stmts) code.extend(stmts)
local_decl.extend(local) local_decl.extend(local)
code.append('return{};'.format(' ' + string if string else '')) code.append('return{};'
.format(' ' + string if string else ''))
else: else:
code.append('trId := ' + str(tr.terminator.next_id) + ';') code.append('trId := ' + str(tr.terminator.next_id) + ';')
code.append('goto next_transition;')
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;')
...@@ -1578,6 +1640,144 @@ def find_labels(trans): ...@@ -1578,6 +1640,144 @@ def find_labels(trans):
yield new_fl 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): def format_ada_code(stmts):
''' Indent properly the Ada code ''' ''' Indent properly the Ada code '''
indent = 0 indent = 0
......
...@@ -90,7 +90,8 @@ def copy_branch(top_level_item): ...@@ -90,7 +90,8 @@ def copy_branch(top_level_item):
(term.pos_x, term.pos_y, term.width, term.height).center()) (term.pos_x, term.pos_y, term.width, term.height).center())
for symbol in symbols: for symbol in symbols:
if (isinstance(symbol, sdlSymbols.State) and [c for c in if (isinstance(symbol, sdlSymbols.State) and [c for c in
symbol.childSymbols() if isinstance(c, sdlSymbols.Input)]): symbol.childSymbols() if isinstance(c, (sdlSymbols.Input,
sdlSymbols.Connect))]):
term_branch, term_inators = copy_branch(symbol) term_branch, term_inators = copy_branch(symbol)
branch.extend(term_branch) branch.extend(term_branch)
res_terminators.extend(term_inators) res_terminators.extend(term_inators)
......
...@@ -18,19 +18,15 @@ ...@@ -18,19 +18,15 @@
""" """
import logging import logging
from singledispatch import singledispatch
from llvm import core, passes, ee from llvm import core, passes, ee
import ogAST import ogAST
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
# reference to the ASN.1 Data view and to the process variables __all__ = ['generate']
TYPES = None
VARIABLES = {}
# List of output signals and procedures
OUT_SIGNALS = []
PROCEDURES = []
INNER_PROCEDURES = []
# LLVM Global variable - Initialized when the generator is invoked # LLVM Global variable - Initialized when the generator is invoked
LLVM = { LLVM = {
...@@ -46,786 +42,18 @@ LLVM = { ...@@ -46,786 +42,18 @@ LLVM = {
} }
# lookup table for mapping SDL operands with the corresponding Ada ones
OPERANDS = {'plus': '+', 'mul': '*', 'minus': '-', 'or': 'or',
'and': 'and', 'xor': 'CHECKME', 'eq': '=', 'neq': '/=', 'gt': '>',
'ge': '>=', 'lt': '<', 'le': '<=', 'div': '/', 'mod': 'mod'}
def find_basic_type(a_type):
''' Return the ASN.1 basic type of aType '''
basic_type = a_type
while basic_type['Kind'] == 'ReferenceType':
# Find type with proper case in the data view
for typename in TYPES.viewkeys():
if typename.lower() == basic_type['ReferencedTypeName'].lower():
basic_type = TYPES[typename]['type']
break
return basic_type
def get_type_of_parent(identifier):
''' Return the type of a "parent" construct (a!b!c)=>return type of b '''
kind = ''
name = ''
if len(identifier) > 1:
if identifier[0] in VARIABLES:
name = VARIABLES[identifier[0]].replace('_', '-')
current_type = TYPES[name]['type']
for index in range(1, len(identifier)):
# TODO find type...
LOG.warning('** INCOMPLETE FEATURE ** ')
kind = current_type['Kind'].replace('-', '_')
return kind, name
def traceability(symbol):
''' Return a string with code-to-model traceability '''
trace = ['-- {line}'.format(line=l) for l in
repr(symbol).split('\n')]
if hasattr(symbol, 'comment') and symbol.comment:
trace.extend(traceability(symbol.comment))
return trace
@singledispatch
def generate(ast):
''' Generate the code for an item of the AST '''
raise TypeError('[Backend] Unsupported AST construct')
def write_statement(param, newline): # Processing of the AST
''' Generate the code for the special "write" operator '''
code = []
string = ''
local = []
basic_type = find_basic_type(param.exprType) or {}
type_kind = basic_type.get('Kind')
if type_kind == 'StringType':
# Raw string
string = '"' + param.inputString[1:-1] + '"'
elif type_kind in ('IntegerType', 'RealType', 'BooleanType'):
code, string, local = decipher_expression(param)
if type_kind == 'IntegerType':
cast = "Interfaces.Integer_64"
elif type_kind == 'RealType':
cast = 'Long_Float'
elif type_kind == 'BooleanType':
cast = 'Boolean'
string = "{cast}'Image({s})".format(cast=cast, s=string)
else:
error = ('Unsupported parameter in write call ' +
param.var.inputString)
LOG.error(error)
raise TypeError(error)
code.append('Put{line}({string});'.format(
line='_Line' if newline else '',
string=string))
return code, string, local
def output_statement(output):
''' Generate the code of a set of output or procedure call statement '''
code = []
local_decl = []
# Add the traceability information
code.extend(traceability(output))
for out in output.output:
signal_name = out['outputName']
if signal_name.lower() in ('write', 'writeln'):