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

Merge pull request #31 from lattuada/master

C Backend
parents 2b77c93e 5c8a87a0
......@@ -7,4 +7,34 @@ install.record
antlr-3.1.3.tar.bz2
antlr-3.1.3/
test_c
tests/**/*.stg
tests/regression/test-controlflow/controlflow.*
tests/regression/test-debug/orchestrator.*
tests/regression/test-equal/og.*
tests/regression/test-exitnested/challenge.*
tests/regression/test-expressions/expressions.*
tests/regression/test-llvm/orchestrator.*
tests/regression/test-nocif/orchestrator.*
tests/regression/test-operators/operators.*
tests/regression/test-processfpar1/og.*
tests/regression/test-processfpar2/og.*
tests/regression/test-simu/orchestrator.*
tests/regression/test-standalone/og.*
tests/regression/test-strings/og.*
tests/regression/test-substrings/myfunction.*
tests/regression/test-types/function1.*
tests/regression/test1/og.*
tests/regression/test10/challenge.*
tests/regression/test11/og.*
tests/regression/test12/trafficlight.*
tests/regression/test2/orchestrator.*
tests/regression/test3/fce.*
tests/regression/test4/asn1crt.*
tests/regression/test4/orchestrator.*
tests/regression/test5/function0.*
tests/regression/test6/myfunction.*
tests/regression/test7/orchestrator.*
tests/regression/test8/orchestrator.*
tests/regression/test9/challenge.*
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
OpenGEODE - A tiny SDL Editor for TASTE
This module generates C code from SDL process models.
Copyright (c) 2015 Politecnico di Milano
Designed and implemented by Marco Lattuada
Contact: marco.lattuada@polimi.it
"""
import logging
from singledispatch import singledispatch
import Helper
import ogAST
LOG = logging.getLogger(__name__)
__all__ = ['generate']
VARIABLES = {}
LOCAL_OUT_PARS = {}
LOCAL_VAR = {}
LOCAL_VARIABLE_TYPES = {}
OUT_SIGNALS = []
PROCEDURES = []
UNICODE_SEP = u'___'
LPREFIX = u'context'
LEFT_TYPE = ''
VAR_COUNTER = 0
# Specify that the target is a shared library
SHARED_LIB = False
@singledispatch
def generate(*args, **kwargs):
''' Generate the code for an item of the AST '''
for arg in args:
LOG.info(arg)
raise TypeError('[CGenerator] Unsupported AST construct')
return [], []
# Processing of the AST
@generate.register(ogAST.Decision)
def _decision(dec, **kwargs):
''' generate the code for a decision '''
global VAR_COUNTER
stmts, decls = [], []
if dec.kind == 'any':
LOG.warning('C backend does not support the "ANY" statement')
stmts.append('// "DECISION ANY" statement was ignored')
return stmts, decls
elif dec.kind == 'informal_text':
LOG.warning('Informal decision ignored')
stmts.append('// Informal decision was ignored: {}'
.format(dec.inputString))
return stmts, decls
question_type = dec.question.exprType
actual_type = type_name(question_type)
basic = find_basic_type(question_type).kind in ('IntegerType', 'Integer32Type', 'BooleanType', 'RealType', 'EnumeratedType', 'ChoiceEnumeratedType')
# for ASN.1 types, declare a local variable
# to hold the evaluation of the question
if not basic:
decls.append('{actType} tmp{idx};'.format(idx=dec.tmpVar, actType=actual_type))
question_stmts, question_string, question_decls = expression(dec.question)
# Add code-to-model traceability
stmts.extend(traceability(dec))
decls.extend(question_decls)
stmts.extend(question_stmts)
if not basic:
stmts.append('tmp{idx} = {q};'.format(idx=dec.tmpVar, q=question_string))
sep = 'if('
for a in dec.answers:
stmts.extend(traceability(a))
if a.kind in ('open_range', 'constant'):
# Note: removed and a.transition here because empty transitions
# have a different meaning, and a "null;" statement has to be
# generated, to go into the branch
ans_stmts, ans_str, ans_decl = expression(a.constant)
stmts.extend(ans_stmts)
decls.extend(ans_decl)
if not basic:
if a.openRangeOp in (ogAST.ExprEq, ogAST.ExprNeq):
if isinstance(a.constant, (ogAST.PrimSequenceOf, ogAST.PrimStringLiteral)):
ans_str = array_content(a.constant, ans_str, find_basic_type(question_type))
VAR_COUNTER = VAR_COUNTER + 1
decls.append(u'{ty} temp_equal_{var_counter} = {init};'.format(ty=actual_type, var_counter=VAR_COUNTER, init=ans_str))
ans_str = u'temp_equal_{var_counter}'.format(var_counter=VAR_COUNTER)
elif isinstance(a.constant, ogAST.PrimChoiceItem):
VAR_COUNTER = VAR_COUNTER + 1
decls.append(u'{ty} temp_equal_{var_counter};'.format(ty=actual_type, var_counter=VAR_COUNTER))
stmts.append(u'temp_equal_{var_counter} = {init};'.format(var_counter=VAR_COUNTER, init=ans_str))
ans_str = u'temp_equal_{var_counter}'.format(var_counter=VAR_COUNTER)
exp = u'{actType}_Equal(&tmp{idx}, &{ans})'.format(actType=actual_type, idx=dec.tmpVar, ans=ans_str)
if a.openRangeOp == ogAST.ExprNeq:
exp = u'! {}'.format(exp)
else:
exp = u'tmp{idx} {op} {ans}'.format(idx=dec.tmpVar, op='==' if a.openRangeOp.operand == '=' else a.openRangeOp.operand, ans=ans_str)
else:
exp = u'({q}) {op} {ans}'.format(q=question_string, op='==' if a.openRangeOp.operand == '=' else a.openRangeOp.operand, ans=ans_str)
stmts.append(sep + exp + ')')
stmts.append('{')
if a.transition:
transition_stmts, transition_decls = generate(a.transition)
else:
transition_stmts, transition_decls = [';'], []
stmts.extend(transition_stmts)
decls.extend(transition_decls)
stmts.append('}')
sep = 'else if('
elif a.kind == 'closed_range':
cl0_stmts, cl0_str, cl0_decl = expression(a.closedRange[0])
cl1_stmts, cl1_str, cl1_decl = expression(a.closedRange[1])
stmts.extend(cl0_stmts)
decls.extend(cl0_decl)
stmts.extend(cl1_stmts)
decls.extend(cl1_decl)
stmts.append('{sep} {dec} >= {cl0} && {dec} <= {cl1})'.format(sep=sep, dec=question_string, cl0=cl0_str, cl1=cl1_str))
stmts.append('{')
if a.transition:
transition_stmts, transition_decls = generate(a.transition)
else:
transition_stmts, transition_decls = [';'], []
stmts.extend(transition_stmts)
decls.extend(transition_decls)
stmts.append('}')
sep = 'else if('
elif a.kind == 'informal_text':
continue
elif a.kind == 'else':
# Keep the ELSE statement for the end
if a.transition:
else_stmts, else_decl = generate(a.transition)
else_stmts.insert(0, '{')
else_stmts.append('}')
else:
else_stmts, else_decl = ['{',';','}'], []
decls.extend(else_decl)
try:
if sep != 'if(':
# If there is at least one 'if' branch
else_stmts.insert(0, 'else')
stmts.extend(else_stmts)
else:
stmts.extend(else_stmts)
except:
pass
return stmts, decls
@generate.register(ogAST.Floating_label)
def _floating_label(label, **kwargs):
''' Generate the code for a floating label (C label + transition) '''
code = []
local_decl = []
# Add the traceability information
code.extend(traceability(label))
code.append(u'{label}:'.format(label=label.inputString))
if label.transition:
code_trans, local_trans = generate(label.transition)
code.extend(code_trans)
local_decl.extend(local_trans)
else:
code.append('return;')
return code, local_decl
@generate.register(ogAST.Label)
def _label(lab, **kwargs):
''' Transition following labels are generated in a separate section
for visibility reasons
'''
return ['goto {label};'.format(label=lab.inputString)], []
@generate.register(ogAST.Output)
@generate.register(ogAST.ProcedureCall)
def _call_external_function(output, **kwargs):
''' Generate the code of a set of output or procedure call statement '''
stmts = []
decls = []
# Add the traceability information
stmts.extend(traceability(output))
for out in output.output:
signal_name = out['outputName']
if signal_name.lower() in ('write', 'writeln'):
# special built-in SDL procedure for printing strings
# supports printing of native types (int, real, bool)
# but not yet complex ASN.1 structures (sequence/seqof/choice)
for param in out['params'][:-1]:
write_stmts, _, local = write_statement(param, newline=False)
stmts.extend(write_stmts)
decls.extend(local)
for param in out['params'][-1:]:
# Last parameter - add newline if necessary
write_stmts, _, local = write_statement(param, newline=True if signal_name.lower() == 'writeln' else False)
stmts.extend(write_stmts)
decls.extend(local)
continue
elif signal_name.lower() == 'reset_timer':
# built-in operator for resetting timers. param = timer name
param, = out['params']
param_stmts, p_id, p_local = expression(param)
stmts.extend(param_stmts)
decls.extend(p_local)
if not SHARED_LIB:
stmts.append('RESET_{};'.format(p_id))
else:
stmts.append('RESET_{t}("{t}");'.format(t=p_id))
continue
elif signal_name.lower() == 'set_timer':
# built-in operator for setting a timer: SET(1000, timer_name)
timer_value, timer_id = out['params']
timer_stmts, t_val, t_local = expression(timer_value)
param_stmts, p_id, p_local = expression(timer_id)
stmts.extend(timer_stmts)
stmts.extend(param_stmts)
decls.extend(t_local)
decls.extend(p_local)
if not SHARED_LIB:
# Use a temporary variable to store the timer value
tmp_id = 'tmp' + str(out['tmpVars'][0])
decls.append('asn1SccUint32 {};'.format(tmp_id))
stmts.append('{tmp} = {val};'.format(tmp=tmp_id, val=t_val))
stmts.append("SET_{timer}(&{value});".format(timer=p_id, value=tmp_id))
else:
stmts.append('SET_{t}("{t}", {val});'.format(t=p_id, val=t_val))
continue
proc, out_sig = None, None
is_out_sig = False
try:
out_sig, = [sig for sig in OUT_SIGNALS if sig['name'].lower() == signal_name.lower()]
is_out_sig = True if SHARED_LIB else False
except ValueError:
# Not an output, try if it is an external or inner procedure
try:
proc, = [sig for sig in PROCEDURES if sig.inputString.lower() == signal_name.lower()]
if proc.external:
out_sig = proc
except ValueError:
# Not there? Impossible, the parser would have barked
raise ValueError(u'Probably a bug - please report')
if out_sig:
list_of_params = []
for idx, param in enumerate(out.get('params') or []):
param_direction = 'in'
try:
# If it is an output, there is a single parameter
param_type = out_sig['type']
except TypeError:
# Else if it is a procedure, get the type
param_type = out_sig.fpar[idx]['type']
param_direction = out_sig.fpar[idx]['direction']
typename = type_name(param_type)
param_stmts, p_id, p_local = expression(param)
stmts.extend(param_stmts)
decls.extend(p_local)
# Create a temporary variable for input parameters only
# (If needed, i.e. if argument is not a local variable)
if param_direction == 'in' and (not (isinstance(param, ogAST.PrimVariable) and p_id.startswith(LPREFIX)) or isinstance(param, ogAST.PrimFPAR)):
tmp_id = 'tmp{}'.format(out['tmpVars'][idx])
if isinstance(param, ogAST.PrimStringLiteral):
decls.append('{sort} {tmp} = {init};'.format(tmp=tmp_id, sort=typename, init= array_content(param, p_id, find_basic_type(param_type))))
else:
decls.append('{sort} {tmp};'.format(tmp=tmp_id, sort=typename))
if isinstance(param, ogAST.PrimSequenceOf):
p_id = array_content(param, p_id, find_basic_type(param_type))
stmts.append('{} = {};'.format(tmp_id, p_id))
list_of_params.append("&{}{}".format(tmp_id,", sizeof({})".format(tmp_id) if is_out_sig else ""))
else:
# Output parameters/local variables
list_of_params.append(u"&{var}{shared}".format(var=p_id, shared=", sizeof({})".format(p_id) if is_out_sig else ""))
if list_of_params:
stmts.append(u'{RI}({params});'.format(RI=out['outputName'], params=', '.join(list_of_params)))
else:
if not SHARED_LIB:
stmts.append(u'{RI};'.format(RI=out['outputName']))
else:
stmts.append(u'{RI}(("{RI}"));'.format(RI=out['outputName']))
else:
# inner procedure call
list_of_params = []
param_counter = 0
for param in out.get('params', []):
param_stmts, p_id, p_local = expression(param)
stmts.extend(param_stmts)
decls.extend(p_local)
# no need to use temporary variables, we are in pure Ada
if proc.fpar[param_counter].get('direction') == 'out':
p_id = u'&' + p_id
list_of_params.append( p_id)
param_counter = param_counter + 1
if list_of_params:
stmts.append(u'{sep}{proc}({params});'.format(sep=UNICODE_SEP, proc=proc.inputString, params=', '.join(list_of_params)))
else:
stmts.append(u'{}{}();'.format(UNICODE_SEP, proc.inputString))
return stmts, decls
@generate.register(ogAST.Procedure)
def _inner_procedure(proc, **kwargs):
''' Generate the code for a procedure - does not support states '''
LOG.debug('Expanding procedure ' + proc.inputString)
code = []
local_decl = []
# TODO: Update the global list of procedures
# with procedure defined inside the current procedure
# Not critical: the editor forbids procedures inside procedures
# Save variable scopes (as local variables may shadow process variables)
outer_scope = dict(VARIABLES)
local_scope = dict(LOCAL_VAR)
outer_params = dict(LOCAL_OUT_PARS)
VARIABLES.update(proc.variables)
# Store local variables in global context
LOCAL_VAR.update(proc.variables)
LOCAL_OUT_PARS.clear()
# Also add procedure parameters in scope
for var in proc.fpar:
elem = {var['name']: (var['type'], None)}
VARIABLES.update(elem)
LOCAL_VAR.update(elem)
if var.get('direction') == 'out':
LOCAL_OUT_PARS.update(elem)
# Build the procedure signature (function if it can return a value)
ret_type = type_name(proc.return_type) if proc.return_type else None
pi_header = u'{ext}{ret_type} {sep}{proc_name}'.format(ext='extern ' if proc.external else '', ret_type='void' if not ret_type else ret_type, proc_name=proc.inputString, sep=UNICODE_SEP if not proc.external else '')
pi_header += '('
if proc.fpar:
params = []
first = True
for fpar in proc.fpar:
typename = type_name(fpar['type'])
params.append(u'{ptype} {pt} {name}'.format(ptype=typename, pt='*' if fpar.get('direction') == 'out' or proc.external else '', name=fpar.get('name')))
first = False
pi_header += ','.join(params)
pi_header += ')'
local_decl.append(pi_header + ';')
# Remote procedures need to be exported with a C calling convention
if not proc.external:
# Generate the code for the procedure itself
# local variables and code of the START transition
# Recursively generate the code for inner-defined procedures
for inner_proc in proc.content.inner_procedures:
inner_code, inner_local = generate(inner_proc)
local_decl.extend(inner_local)
code.extend(inner_code)
code.append(pi_header)
code.append(u'{')
for var_name, (var_type, def_value) in proc.variables.viewitems():
typename = type_name(var_type)
if def_value:
# Expression must be a ground expression, i.e. must not
# require temporary variable to store computed result
dst, dstr, dlocal = expression(def_value)
varbty = find_basic_type(var_type)
if varbty.kind in ('SequenceOfType', 'OctetStringType'):
dstr = array_content(def_value, dstr, varbty)
assert not dst and not dlocal, 'Ground expression error'
code.append(u'{ty} {name} {default};'.format(ty=typename, name=var_name, default=' = ' + dstr if def_value else ''))
# Look for labels in the diagram and transform them in floating labels
Helper.inner_labels_to_floating(proc)
if proc.content.start:
tr_code, tr_decl = generate(proc.content.start.transition)
else:
tr_code, tr_decl = ['; // Empty procedure'], []
# Generate code for the floating labels
code_labels = []
for label in proc.content.floating_labels:
code_label, label_decl = generate(label)
code_labels.extend(code_label)
tr_decl.extend(label_decl)
code.extend(set(tr_decl))
code.extend(tr_code)
code.extend(code_labels)
code.append(u'}')
code.append('\n')
# Reset the scope to how it was prior to the procedure definition
VARIABLES.clear()
VARIABLES.update(outer_scope)
LOCAL_VAR.clear()
LOCAL_VAR.update(local_scope)
LOCAL_OUT_PARS.clear()
LOCAL_OUT_PARS.update(outer_params)
return code, local_decl
@generate.register(ogAST.Process)
def _process(process, simu=False, **kwargs):
''' Generate the code for a complete process (AST Top level) '''
#Types
global TYPES
TYPES = process.dataview
del OUT_SIGNALS[:]
OUT_SIGNALS.extend(process.output_signals)
del PROCEDURES[:]
PROCEDURES.extend(process.procedures)
#The name of the process
process_name = process.processName
#The generated source code
c_source_code = []
h_source_code = []
#The global declarations
global_decls = []
#The number of temporary variable
global tmp_var_id
tmp_var_id = 0
#True if math.h has to be included
global math_include
math_include = False
#True if stdio.h has to be included
global STDIO_INCLUDE
STDIO_INCLUDE = False
#True if string.h has to be included
global string_include
string_include = False
global LPREFIX
global SHARED_LIB
if simu:
SHARED_LIB = True
LPREFIX = process_name + u'_ctxt'
# When building a shared library (with simu=True), generate a "mini-cv"
# for aadl2glueC to create the code interfacing with asn1scc
minicv = ['// Automatically generated by OpenGEODE - do NOT modify!']
def aadl_template(sp_name, io_param, pi_or_ri):
''' AADL mini-cv code in case of shared library
sp_name : name of the PI or RI
io_param : list of (param_name, type_name, direction)
pi_or_ri : string "PI" or "RI" depending on the direction
return a string
'''
res = []
if not io_param:
LOG.info('Parameterless interface "{}" will not appear in the'
' AADL file but will be handled directly by the GUI'
.format(sp_name))
return ''
# In case of shared library, generate the AADL "mini-cv" code
res.append('SUBPROGRAM {}'.format(sp_name))
if io_param:
res.append('FEATURES')
for param_name, sort, direction in io_param:
res.append(' {pname}: {io} PARAMETER DataView::{sort} '
'{{encoding=>Native;}};'.format(pname=param_name,
sort=sort,
io=direction))
res.append('END {};\n'.format(sp_name))
res.append('SUBPROGRAM IMPLEMENTATION {}.GUI_{}'
.format(sp_name, pi_or_ri))
res.append('PROPERTIES')
res.append(' FV_Name => "{}";'.format(process_name))
res.append(' Source_Language => GUI_{};'.format(pi_or_ri))
res.append('END {}.GUI_{};\n'.format(sp_name, pi_or_ri))
return '\n'.join(res)
# bash script to simulate the system (TEMPORARY)
simu_script = '''#!/bin/bash -e
opengeode {pr}.pr --shared --toC
asn1.exe -c dataview-uniq.asn -typePrefix asn1Scc -equal
gcc -c *.c -fPIC
gcc -shared -fPIC -o lib{pr}.so {pr}.o dataview-uniq.o adaasn1rtl.o -lgnat
rm -rf simu
mkdir -p simu
asn2aadlPlus dataview-uniq.asn simu/DataView.aadl
cp lib{pr}.so dataview-uniq.asn *.pr simu
mv *.aadl simu
cd simu
aadl2glueC DataView.aadl {pr}_interface.aadl
asn2dataModel -toPython dataview-uniq.asn
make -f Makefile.python
echo "errCodes=$(taste-asn1-errCodes ./dataview-uniq.h)" >>datamodel.py
LD_LIBRARY_PATH=. taste-gui -l
'''.format(pr=process_name)
LOG.info('Generating C code for process ' + str(process_name))
# In case model has nested states, flatten everything
Helper.flatten(process, sep=UNICODE_SEP)
# Make an maping {input: {state: transition...}} in order to easily
# generate the lookup tables for the state machine runtime
mapping = Helper.map_input_state(process)
VARIABLES.update(process.variables)
#Computing state lists
state_list = ', '.join(name for name in process.mapping.iterkeys() if not name.endswith(u'START')) or 'No_State'
#Declaring global type modeling the state
if state_list:
state_decl = u'typedef enum {{{}}} states_t;'.format(state_list)
global_decls.append(state_decl)
#Declaring global type modeling the context
context_type = []
context_init_code = []
context_type.append(u'typedef struct')
context_type.append(u'{')
context_init_code.append(u'void CInit()')
context_init_code.append(u'{')
if state_list:
context_type.append(u'states_t state;')
for var_name, (var_type, init) in process.variables.viewitems():
init_stmt = []
init_string = ''
init_decl = []
if init:
init_stmt, temp_init_string, init_decls = expression(init)
if find_basic_type(var_type).kind in ('SequenceOfType', 'OctetStringType'):
init_string = array_content(init, temp_init_string, find_basic_type(var_type ))
if find_basic_type(var_type).Min == find_basic_type(var_type).Max:
init_string = u'{{{init}}}'.format(init=init_string)
else:
init_stmt, init_string, init_decl = expression(init)
if find_basic_type(var_type).kind in ('SequenceOfType', 'OctetStringType', 'SequenceType'):
init_string = u'(' + type_name(var_type) + u') ' + init_string
LOG.debug(var_name)
context_init_code.append(u'{ct}.{field} = {init};'.format(ct=LPREFIX, field=var_name, init=init_string))
assert not init_stmt, 'Initialization of ' + init_name + ' requires to add statement'
assert not init_decl, 'Initialization of ' + init_name + ' requires to add declartions'
context_type.append(u'{tn} {vn};'.format(tn = type_name(var_type), vn = var_name))
context_type.extend(['} context_t;'])
context_init_code.append(u'}')
global_decls.extend(context_type)
# Adding the declaration of the state variable
global_decls.append(u'context_t {ct};'.format(ct=LPREFIX))
global_decls.extend(context_init_code)
for name, val in process.mapping.viewitems():
if name.endswith(u'START') and name !=