Commit 9162d67f authored by dbarbera's avatar dbarbera
Browse files

Merge remote-tracking branch 'upstream/master' into llvm

Conflicts:
	.gitignore
	LlvmGenerator.py
	tests/regression/test13/Makefile
	tests/regression/test13/basic.pr
	tests/regression/test13/dataview-uniq.asn
	tests/regression/test13/expected
	tests/regression/test6/Makefile
	tests/regression/test8/Makefile
	tests/test.py
parents 7408b7b2 90ee507e
*.pyc
*.autosave
tests/**/*.ads
tests/**/*.adb
tests/**/*.ali
tests/**/*.ll
tests/**/*.s
tests/**/*.o
tests/**/*.idx
tests/**/*.wrn
tests/**/*.gpr
tests/**/*.sh
tests/**/*.cfg
tests/**/result
tests/**/testcase
antlr-3.1.3.tar.bz2
antlr-3.1.3/
tests/**/python.stg
tests/**/*.stg
......@@ -1183,17 +1183,21 @@ def _mantissa_base_exp(primary):
return [], u'', []
@expression.register(ogAST.PrimIfThenElse)
def _if_then_else(ifThenElse):
''' Return string and statements for ternary operator '''
resType = ifThenElse.exprType
@expression.register(ogAST.PrimConditional)
def _conditional(cond):
''' Return string and statements for conditional expressions '''
resType = cond.exprType
stmts = []
if resType.kind.startswith('Integer'):
if resType.kind in ('IntegerType', 'Integer32Type'):
tmp_type = 'Asn1Int'
elif resType.kind == 'StandardStringType':
print ifThenElse.value['then'].value
then_str = ifThenElse.value['then'].value.replace("'", '"')
else_str = ifThenElse.value['else'].value.replace("'", '"')
elif resType.kind == 'RealType':
tmp_type = 'Asn1Real'
elif resType.kind == 'BooleanType':
tmp_type = 'Boolean'
elif resType.kind == 'StringType':
then_str = cond.value['then'].value.replace("'", '"')
else_str = cond.value['else'].value.replace("'", '"')
lens = [len(then_str), len(else_str)]
tmp_type = 'String(1 .. {})'.format(max(lens) - 2)
# Ada require fixed-length strings, adjust with spaces
......@@ -1203,29 +1207,30 @@ def _if_then_else(ifThenElse):
else_str = else_str[0:-1] + ' ' * (lens[0] - lens[1]) + '"'
else:
tmp_type = 'asn1Scc' + resType.ReferencedTypeName.replace('-', '_')
local_decl = ['tmp{idx} : {tmpType};'.format(
idx=ifThenElse.value['tmpVar'],
idx=cond.value['tmpVar'],
tmpType=tmp_type)]
if_stmts, if_str, if_local = expression(ifThenElse.value['if'])
if_stmts, if_str, if_local = expression(cond.value['if'])
stmts.extend(if_stmts)
local_decl.extend(if_local)
if resType.kind != 'StandardStringType':
then_stmts, then_str, then_local = expression(ifThenElse.value['then'])
else_stmts, else_str, else_local = expression(ifThenElse.value['else'])
if resType.kind != 'StringType':
then_stmts, then_str, then_local = expression(cond.value['then'])
else_stmts, else_str, else_local = expression(cond.value['else'])
stmts.extend(then_stmts)
stmts.extend(else_stmts)
local_decl.extend(then_local)
local_decl.extend(else_local)
stmts.append(u'if {if_str} then'.format(if_str=if_str))
stmts.append(u'tmp{idx} := {then_str};'.format(
idx=ifThenElse.value['tmpVar'],
idx=cond.value['tmpVar'],
then_str=then_str))
stmts.append('else')
stmts.append(u'tmp{idx} := {else_str};'.format(
idx=ifThenElse.value['tmpVar'],
idx=cond.value['tmpVar'],
else_str=else_str))
stmts.append('end if;')
ada_string = u'tmp{idx}'.format(idx=ifThenElse.value['tmpVar'])
ada_string = u'tmp{idx}'.format(idx=cond.value['tmpVar'])
return stmts, unicode(ada_string), local_decl
......
......@@ -387,9 +387,9 @@ def _rename_path(ast, from_name, to_name):
ast.value[0] = to_name
@rename_everything.register(ogAST.PrimIfThenElse)
@rename_everything.register(ogAST.PrimConditional)
def _rename_ifhthenelse(ast, from_name, to_name):
''' Rename expressions in If-Then-Else-Fi construct '''
''' Rename expressions in Conditional expression construct '''
for expr in ('if', 'then', 'else'):
rename_everything(ast.value[expr], from_name, to_name)
......
......@@ -1430,7 +1430,7 @@ def _mantissa_base_exp(primary):
raise NotImplementedError
@expression.register(ogAST.PrimIfThenElse)
@expression.register(ogAST.PrimConditional)
def _if_then_else(ifthen):
''' Generate the code for ternary operator '''
func = ctx.builder.basic_block.function
......
......@@ -30,5 +30,6 @@ publish: install
python setup.py sdist upload
clean:
make -C tests/regression clean
find . -name '*~' | xargs rm -f
find . -name '*.o' | xargs rm -f
......@@ -232,7 +232,7 @@ class PrimOctetStringLiteral(Primary):
pass
class PrimIfThenElse(Primary):
class PrimConditional(Primary):
''' value is a dictionnary:
{ 'if': Expression, 'then': Expression,
'else': Expression, 'tmpVar': integer}
......
......@@ -28,12 +28,10 @@ __author__ = 'Maxime Perrotin'
import sys
import os
import re
import fnmatch
import logging
import traceback
from itertools import chain, permutations, combinations
from collections import defaultdict
from collections import defaultdict, Counter
import antlr3
import antlr3.tree
......@@ -68,7 +66,6 @@ EXPR_NODE = {
lexer.NOT: ogAST.ExprNot,
lexer.NEG: ogAST.ExprNeg,
lexer.PRIMARY: ogAST.Primary,
lexer.IFTHENELSE: ogAST.PrimIfThenElse,
}
# Insert current path in the search list for importing modules
......@@ -394,11 +391,7 @@ def fix_special_operators(op_name, expr_list, context):
for each in (INTEGER, REAL, BOOLEAN, RAWSTRING, OCTETSTRING):
try:
check_type_compatibility(param, each, context)
if each is OCTETSTRING and isinstance(param,
ogAST.PrimIfThenElse):
param.exprType = param.value['then'].exprType
else:
param.exprType = each
param.exprType = each
break
except TypeError:
continue
......@@ -551,28 +544,15 @@ def check_type_compatibility(primary, typeRef, context):
'" not in this enumeration: ' +
str(actual_type.EnumValues.keys()))
raise TypeError(err)
elif isinstance(primary, ogAST.PrimIfThenElse):
# check that IF expr returns BOOL, and that Then and Else expressions
# are compatible with actual_type
if_expr = primary.value['if']
elif isinstance(primary, ogAST.PrimConditional):
then_expr = primary.value['then']
else_expr = primary.value['else']
if if_expr.exprType.kind != 'BooleanType':
raise TypeError('IF expression does not return a boolean')
else:
for expr in (then_expr, else_expr):
if expr.is_raw:
check_type_compatibility(expr, typeRef, context)
# compare the types for semantic equivalence:
else:
if expr.exprType is UNKNOWN_TYPE:
# If it was not resolved before, it must be a variable
# this can happen in the context of a special operator
# (write), where at no point before where the type
# could be compared to another type
expr.exprType = find_variable(expr.value[0], context)
compare_types(expr.exprType, typeRef)
for expr in (then_expr, else_expr):
if expr.is_raw:
check_type_compatibility(expr, typeRef, context)
return
elif isinstance(primary, ogAST.PrimVariable):
try:
compare_types(primary.exprType, typeRef)
......@@ -634,7 +614,7 @@ def check_type_compatibility(primary, typeRef, context):
# Compare the types for semantic equivalence
try:
compare_types(
primary.value[ufield].exprType, fd_data.type)
primary.value[ufield].exprType, fd_data.type)
except TypeError as err:
raise TypeError('Field ' + ufield +
' is not of the proper type, i.e. ' +
......@@ -871,7 +851,7 @@ def fix_expression_types(expr, context):
raise TypeError('Cannot resolve type of "{}"'
.format(unknown[0].inputString))
# In Sequence, Choice, SEQUENCE OF, and IfThenElse expressions,
# In Sequence, Choice and SEQUENCE OF expressions,
# we must fix missing inner types
# (due to similarities, the following should be refactored FIXME)
if isinstance(expr.right, ogAST.PrimSequence):
......@@ -913,7 +893,7 @@ def fix_expression_types(expr, context):
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):
elif isinstance(expr.right, ogAST.PrimConditional):
for det in ('then', 'else'):
# Recursively fix possibly missing types in the expression
check_expr = ogAST.ExprAssign()
......@@ -1040,8 +1020,8 @@ def expression(root, context):
return neg_expression(root, context)
elif root.type == lexer.PAREN:
return expression(root.children[0], context)
elif root.type == lexer.IFTHENELSE:
return if_then_else_expression(root, context)
elif root.type == lexer.CONDITIONAL:
return conditional_expression(root, context)
elif root.type == lexer.PRIMARY:
return primary(root.children[0], context)
elif root.type == lexer.CALL:
......@@ -1076,13 +1056,16 @@ def logic_expression(root, context):
errors.append(error(root, msg))
break
if bty.kind in ('BooleanType', 'BitStringType'):
if bty.kind == 'BooleanType':
continue
elif bty.kind == 'SequenceOfType' and bty.type.kind == 'BooleanType':
elif bty.kind == 'BitStringType' and bty.Min == bty.Max:
continue
elif bty.kind == 'SequenceOfType' and bty.type.kind == 'BooleanType'\
and bty.Min == bty.Max:
continue
else:
msg = 'Bitwise operators only work with Booleans, ' \
'SequenceOf Booleans or BitStrings'
'fixed size SequenceOf Booleans or fixed size BitStrings'
errors.append(error(root, msg))
break
......@@ -1260,11 +1243,11 @@ def neg_expression(root, context):
return expr, errors, warnings
def if_then_else_expression(root, context):
''' If Then Else expression analysis '''
def conditional_expression(root, context):
''' Conditional expression analysis '''
errors, warnings = [], []
expr = ogAST.PrimIfThenElse(
expr = ogAST.PrimConditional(
get_input_string(root),
root.getLine(),
root.getCharPositionInLine()
......@@ -1286,7 +1269,20 @@ def if_then_else_expression(root, context):
errors.extend(err)
warnings.extend(warn)
if find_basic_type(if_expr.exprType).kind != 'BooleanType':
msg = 'Conditions in conditional expressions must be of type Boolean'
errors.append(error(root, msg))
# TODO: Refactor this
try:
expr.left = then_expr
expr.right = else_expr
fix_expression_types(expr, context)
expr.exprType = then_expr.exprType
except (AttributeError, TypeError) as err:
if UNKNOWN_TYPE not in (then_expr.exprType, else_expr.exprType):
errors.append(error(root, str(err)))
expr.value = {
'if': if_expr,
'then': then_expr,
......@@ -1410,7 +1406,6 @@ def primary_call(root, context):
'Max': str(max(enum_values))
})
except AttributeError:
msg = 'Type Error, check the parameter'
errors.append(error(root, '"Num" parameter error'))
return node, errors, warnings
......@@ -1620,7 +1615,7 @@ def primary(root, context):
elif root.type == lexer.OCTSTR:
prim = ogAST.PrimOctetStringLiteral()
warnings.append(
warning(root, 'Octet string literal not supported yet'))
warning(root, 'Octet string literal not supported yet'))
else:
# TODO: return error message
raise NotImplementedError
......@@ -1680,8 +1675,13 @@ def variables(root, ta_ast, context):
str(child.type))
for variable in var:
# Add to the context and text area AST entries
context.variables[variable] = (asn1_sort, def_value)
ta_ast.variables[variable] = (asn1_sort, def_value)
if variable.lower() in context.variables \
or variable.lower() in ta_ast.variables:
errors.append('Variable "{}" is declared more than once'
.format(variable))
else:
context.variables[variable.lower()] = (asn1_sort, def_value)
ta_ast.variables[variable.lower()] = (asn1_sort, def_value)
if not DV:
errors.append('Cannot do semantic checks on variable declarations')
return errors, warnings
......@@ -2543,7 +2543,7 @@ def state(root, parent, context):
state_def.charPositionInLine = child.getCharPositionInLine()
exceptions = [c.toString() for c in child.getChildren()]
for st in context.mapping:
if st not in (exceptions, 'START'):
if st not in exceptions + ['START']:
state_def.statelist.append(st)
elif child.type == lexer.INPUT:
# A transition triggered by an INPUT
......@@ -2553,6 +2553,19 @@ def state(root, parent, context):
warnings.extend(warn)
try:
for statename in state_def.statelist:
# check that input is not already defined
existing = context.mapping.get(statename.lower(), [])
dupl = set()
for each in inp.inputlist:
for ex_input in (name for i in existing
for name in i.inputlist):
if unicode(each) == unicode(ex_input):
dupl.add(each)
for each in dupl:
errors.append('Input "{}" is defined more '
'than once for state "{}"'
.format(each, statename.lower()))
# then update the mapping state-input
context.mapping[statename.lower()].append(inp)
except KeyError:
warnings.append('State definition missing')
......@@ -2564,9 +2577,9 @@ def state(root, parent, context):
else:
asterisk_input = inp
elif child.type == lexer.CONNECT:
comp_states = (comp.statename for comp in context.composite_states)
if asterisk_state or len(state_def.statelist) != 1 \
or (state_def.statelist[0].lower()
not in (comp.statename for comp in context.composite_states)):
or state_def.statelist[0].lower() not in comp_states:
errors.append('State {} is not a composite state and cannot '
'be followed by a connect statement'
.format(state_def.statelist[0]))
......@@ -2960,6 +2973,7 @@ def decision(root, parent, context):
covered_ranges = defaultdict(list)
qmin, qmax = 0, 0
need_else = False
is_enum = False
for ans in dec.answers:
if ans.kind in ('constant', 'open_range'):
expr = ans.openRangeOp()
......@@ -2972,6 +2986,13 @@ def decision(root, parent, context):
ans.constant = expr.right
q_basic = find_basic_type(dec.question.exprType)
a_basic = find_basic_type(ans.constant.exprType)
if q_basic.kind.endswith('EnumeratedType'):
if not ans.constant.is_raw:
# Ref to a variable -> can't guarantee coverage
need_else = True
continue
covered_ranges[ans].append(ans.inputString)
is_enum = True
if not q_basic.kind.startswith('Integer'):
continue
# numeric type -> find the range covered by this answer
......@@ -3065,7 +3086,7 @@ def decision(root, parent, context):
qmin, qmax = int(float(q_basic.Min)), int(float(q_basic.Max))
a0_val = int(float(a0_basic.Min))
a1_val = int(float(a1_basic.Max))
if a0_val < qmin:
if a0_val < qmin:
warnings.append('Decision "{dec}": '
'Range [{a0} .. {qmin}] is unreachable'
.format(a0=a0_val, qmin=qmin - 1,
......@@ -3082,14 +3103,17 @@ def decision(root, parent, context):
l=a0_val, h=a1_val))
covered_ranges[ans].append((int(float(a0_basic.Min)),
int(float(a1_basic.Max))))
# Check the following:
# Check the following
# (1) no overlap between covered ranges in decision answers
# (2) no gap in the coverage of the decision possible values
# (3) ELSE branch, if present, can be reached
# (4) if an answer uses a non-ground expression an ELSE is there
# (5) present() operator and enumerated question are fully covered
q_ranges = [(qmin, qmax)] if is_numeric(dec.question.exprType) else []
for each in combinations(covered_ranges.viewitems(), 2):
if not q_ranges:
continue
for comb in combinations(
chain.from_iterable(val[1] for val in each), 2):
comb_overlap = (max(comb[0][0], comb[1][0]),
......@@ -3104,9 +3128,10 @@ def decision(root, parent, context):
o1=comb_overlap[0],
o2=comb_overlap[1]))
new_q_ranges = []
# for minq, maxq in q_ranges:
# (2) Check that decision range is fully covered
for ans_ref, ranges in covered_ranges.viewitems():
if is_enum:
continue
for mina, maxa in ranges:
for minq, maxq in q_ranges:
left = (minq, min(maxq, mina - 1))
......@@ -3134,6 +3159,21 @@ def decision(root, parent, context):
warnings.append('Decision "{}": Missing ELSE branch'
.format(dec.inputString))
# (5) check coverage of enumerated types
if is_enum:
# check duplicate answers
answers = list(chain.from_iterable(covered_ranges.viewvalues()))
dupl = [a for a, v in Counter(answers).items() if v > 1]
if dupl:
errors.append('Decision "{}": duplicate answers "{}"'
.format(dec.inputString, '", "'.join(dupl)))
enumerants = [en.replace('-', '_') for en in q_basic.EnumValues.keys()]
# check for missing answers
if set(answers) != set(enumerants) and not has_else:
errors.append('Decision "{}": Missing branches for answer(s) "{}"'
.format(dec.inputString,
'", "'.join(set(enumerants) - set(answers))))
return dec, errors, warnings
......
......@@ -95,7 +95,8 @@ try:
import LlvmGenerator
except ImportError:
# not sure if LLVM is available on the Windows platform
pass
LlvmGenerator = None
print 'LLVM is not available'
__all__ = ['opengeode']
__version__ = '0.994'
......@@ -1799,9 +1800,9 @@ def init_logging(options):
for module in modules:
module.LOG.addHandler(handler_console)
module.LOG.setLevel(level)
except NameError:
except (NameError, AttributeError) as err:
# Some modules may not be loaded (like llvm on Windows)
pass;
LOG.info(str(err))
def parse(files):
......
......@@ -33,6 +33,7 @@ tokens {
CLOSED_RANGE;
COMMENT;
COMPOSITE_STATE;
CONDITIONAL;
CONNECT;
CONNECTION;
CONSTANT;
......@@ -836,7 +837,7 @@ postfix_expression
primary_expression
: primary -> ^(PRIMARY primary)
| '(' expression ')' -> ^(PAREN expression)
| conditional_ground_expression
| conditional_expression
;
......@@ -966,31 +967,19 @@ operator_application
active_expression_list
: active_expression (',' expression_list)?;/* |
ground_expression ',' active_expression_list;*/ // Will not work (recursion)
/*
conditional_expression
: IF boolean_active_expression THEN consequence_expression ELSE alternative_expression FI |
IF boolean_expression THEN active_consequence_expression ELSE alternative_expression FI |
IF boolean_expression THEN consequence_expression ELSE active_alternative_expression FI;
*/
// Simpler version, the rest will is checked by semantic analysis
conditional_expression
: IF expression THEN expression ELSE expression FI;
//synonym : ID; // synonym_id | external_synonym;
external_synonym
: external_synonym_id;
conditional_ground_expression
conditional_expression
: IF ifexpr=expression
THEN thenexpr=expression
ELSE elseexpr=expression FI
-> ^(IFTHENELSE $ifexpr $thenexpr $elseexpr);
-> ^(CONDITIONAL $ifexpr $thenexpr $elseexpr);
expression_list
......
This diff is collapsed.
This diff is collapsed.
EXAMPLES=test1 test2 test3 test4 test5 test6 test7 test8 test9 test10 test11 test12 test13 test-substrings
EXAMPLES=test1 test2 test3 test4 test5 test6 test7 test8 test9 test10 \
test11 test12 test-substrings test-expressions
coverage:
for v in $(EXAMPLES) ; do make -C $$v coverage && mv $$v/.coverage* . || exit 1 ; done ; coverage combine
for v in $(EXAMPLES) ; do make -C $$v coverage && mv $$v/.coverage* . \
|| exit 1 ; done ; coverage combine
clean:
for v in $(EXAMPLES); do make -C $$v clean ; done
......@@ -10,7 +10,7 @@ test-parse:
../../../opengeode.py myfunction.pr system_structure.pr --check
test-ada:
../../../opengeode.py --toAda myfunction.pr system_structure.pr 2>&1 | diff expected - || exit 0
../../../opengeode.py --toAda myfunction.pr system_structure.pr 2>&1 | sort | diff expected - || exit 0
coverage:
coverage run -p ../../../opengeode.py myfunction.pr system_structure.pr --toAda
......
[ERROR] Decision "var1": answers -500:500 and /=1 are overlapping in range [-500 .. 0]
[ERROR] Decision "var1": answers -500:500 and /=1 are overlapping in range [2 .. 500]
[ERROR] Decision "var1": answers -500:500 and =0 are overlapping in range [0 .. 0]
[ERROR] Decision "var1": answers -500:500 and >0 are overlapping in range [1 .. 500]
[ERROR] Decision "var1": answers =0 and /=1 are overlapping in range [0 .. 0]
[ERROR] Decision "var1": answers >0 and /=1 are overlapping in range [2 .. 2147483647]
[ERROR] Decision "var2": No answer to cover range [0 .. 255]
[ERROR] Decision "var3": No answer to cover range [50 .. 50]
[ERROR] Decision "var3": answers /=50 and 10:20 are overlapping in range [10 .. 20]
[ERROR] Too many errors, cannot generate code
[INFO] Checking ['myfunction.pr', 'system_structure.pr']
[INFO] Parsing complete. Summary, found 6 warnings and 6 errors
[WARNING] Decision "var2": Unreachable branch "<0"
[WARNING] Decision "var2": Unreachable branch ">300"
[INFO] Parsing complete. Summary, found 6 warnings and 9 errors
[WARNING] Decision "var2": Range [-10 .. -1] is unreachable
[WARNING] Decision "var2": Unreachable branch
[WARNING] Decision "var2": Range [256 .. 300] is unreachable
[WARNING] Decision "var2": Unreachable branch
[ERROR] Non-determinism in decision "var1": answers -500:500 and >0 are overlapping in range [1 .. 500]
[ERROR] Non-determinism in decision "var1": answers -500:500 and /=1 are overlapping in range [-500 .. 0]
[ERROR] Non-determinism in decision "var1": answers -500:500 and /=1 are overlapping in range [2 .. 500]
[ERROR] Non-determinism in decision "var1": answers -500:500 and =0 are overlapping in range [0 .. 0]
[ERROR] Non-determinism in decision "var1": answers >0 and /=1 are overlapping in range [2 .. 2147483647]
[ERROR] Non-determinism in decision "var1": answers /=1 and =0 are overlapping in range [0 .. 0]
[ERROR] Too many errors, cannot generate code
[WARNING] Decision "var2": Unreachable branch "<0"
[WARNING] Decision "var2": Unreachable branch ">300"
[WARNING] Decision "var2": Unreachable branch -10:-5
[WARNING] Decision "var2": Unreachable branch 256:300
/* CIF PROCESS (200, 143), (150, 75) */
PROCESS myfunction;
/* CIF TEXT (0, 142), (282, 128) */
/* CIF TEXT (0, 43), (360, 41) */
-- Test the branch coverage checker in decision answers
/* CIF ENDTEXT */
/* CIF TEXT (78, 142), (282, 184) */
dcl var1 t_Int32 := 0;
dcl var2 t_uint8 := 0;
dcl var3 t_uInt8 := 0;
dcl var4 mychoice := a: false;
dcl var5 myenum := hello;
dcl VAR5 mychoice;
/* CIF ENDTEXT */
/* CIF TEXT (36, 43), (360, 41) */
-- Test the branch coverage checker in decision answers
/* CIF ENDTEXT */
/* CIF START (243, 292), (70, 35) */
/* CIF START (525, 71), (70, 35) */
START;
/* CIF DECISION (243, 342), (70, 50) */
/* CIF DECISION (525, 121), (70, 50) */
DECISION var1
/* CIF COMMENT (333, 349), (191, 35) */
/* CIF COMMENT (615, 128), (191, 35) */
COMMENT 'Check range overlapping';
/* CIF ANSWER (96, 412), (90, 24) */
/* CIF ANSWER (370, 191), (90, 24) */
(-500:500):
/* CIF ANSWER (198, 412), (70, 24) */
/* CIF ANSWER (480, 191), (70, 24) */