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 *.pyc
*.autosave *.autosave
tests/**/*.ads antlr-3.1.3.tar.bz2
tests/**/*.adb antlr-3.1.3/
tests/**/*.ali
tests/**/*.ll
tests/**/*.s
tests/**/*.o
tests/**/*.idx
tests/**/*.wrn
tests/**/*.gpr
tests/**/*.sh
tests/**/*.cfg
tests/**/result
tests/**/testcase
tests/**/python.stg tests/**/*.stg
...@@ -1183,17 +1183,21 @@ def _mantissa_base_exp(primary): ...@@ -1183,17 +1183,21 @@ def _mantissa_base_exp(primary):
return [], u'', [] return [], u'', []
@expression.register(ogAST.PrimIfThenElse) @expression.register(ogAST.PrimConditional)
def _if_then_else(ifThenElse): def _conditional(cond):
''' Return string and statements for ternary operator ''' ''' Return string and statements for conditional expressions '''
resType = ifThenElse.exprType resType = cond.exprType
stmts = [] stmts = []
if resType.kind.startswith('Integer'):
if resType.kind in ('IntegerType', 'Integer32Type'):
tmp_type = 'Asn1Int' tmp_type = 'Asn1Int'
elif resType.kind == 'StandardStringType': elif resType.kind == 'RealType':
print ifThenElse.value['then'].value tmp_type = 'Asn1Real'
then_str = ifThenElse.value['then'].value.replace("'", '"') elif resType.kind == 'BooleanType':
else_str = ifThenElse.value['else'].value.replace("'", '"') 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)] lens = [len(then_str), len(else_str)]
tmp_type = 'String(1 .. {})'.format(max(lens) - 2) tmp_type = 'String(1 .. {})'.format(max(lens) - 2)
# Ada require fixed-length strings, adjust with spaces # Ada require fixed-length strings, adjust with spaces
...@@ -1203,29 +1207,30 @@ def _if_then_else(ifThenElse): ...@@ -1203,29 +1207,30 @@ def _if_then_else(ifThenElse):
else_str = else_str[0:-1] + ' ' * (lens[0] - lens[1]) + '"' else_str = else_str[0:-1] + ' ' * (lens[0] - lens[1]) + '"'
else: else:
tmp_type = 'asn1Scc' + resType.ReferencedTypeName.replace('-', '_') tmp_type = 'asn1Scc' + resType.ReferencedTypeName.replace('-', '_')
local_decl = ['tmp{idx} : {tmpType};'.format( local_decl = ['tmp{idx} : {tmpType};'.format(
idx=ifThenElse.value['tmpVar'], idx=cond.value['tmpVar'],
tmpType=tmp_type)] 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) stmts.extend(if_stmts)
local_decl.extend(if_local) local_decl.extend(if_local)
if resType.kind != 'StandardStringType': if resType.kind != 'StringType':
then_stmts, then_str, then_local = expression(ifThenElse.value['then']) then_stmts, then_str, then_local = expression(cond.value['then'])
else_stmts, else_str, else_local = expression(ifThenElse.value['else']) else_stmts, else_str, else_local = expression(cond.value['else'])
stmts.extend(then_stmts) stmts.extend(then_stmts)
stmts.extend(else_stmts) stmts.extend(else_stmts)
local_decl.extend(then_local) local_decl.extend(then_local)
local_decl.extend(else_local) local_decl.extend(else_local)
stmts.append(u'if {if_str} then'.format(if_str=if_str)) stmts.append(u'if {if_str} then'.format(if_str=if_str))
stmts.append(u'tmp{idx} := {then_str};'.format( stmts.append(u'tmp{idx} := {then_str};'.format(
idx=ifThenElse.value['tmpVar'], idx=cond.value['tmpVar'],
then_str=then_str)) then_str=then_str))
stmts.append('else') stmts.append('else')
stmts.append(u'tmp{idx} := {else_str};'.format( stmts.append(u'tmp{idx} := {else_str};'.format(
idx=ifThenElse.value['tmpVar'], idx=cond.value['tmpVar'],
else_str=else_str)) else_str=else_str))
stmts.append('end if;') 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 return stmts, unicode(ada_string), local_decl
......
...@@ -387,9 +387,9 @@ def _rename_path(ast, from_name, to_name): ...@@ -387,9 +387,9 @@ def _rename_path(ast, from_name, to_name):
ast.value[0] = 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): 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'): for expr in ('if', 'then', 'else'):
rename_everything(ast.value[expr], from_name, to_name) rename_everything(ast.value[expr], from_name, to_name)
......
...@@ -1430,7 +1430,7 @@ def _mantissa_base_exp(primary): ...@@ -1430,7 +1430,7 @@ def _mantissa_base_exp(primary):
raise NotImplementedError raise NotImplementedError
@expression.register(ogAST.PrimIfThenElse) @expression.register(ogAST.PrimConditional)
def _if_then_else(ifthen): def _if_then_else(ifthen):
''' Generate the code for ternary operator ''' ''' Generate the code for ternary operator '''
func = ctx.builder.basic_block.function func = ctx.builder.basic_block.function
......
...@@ -30,5 +30,6 @@ publish: install ...@@ -30,5 +30,6 @@ publish: install
python setup.py sdist upload python setup.py sdist upload
clean: clean:
make -C tests/regression clean
find . -name '*~' | xargs rm -f find . -name '*~' | xargs rm -f
find . -name '*.o' | xargs rm -f find . -name '*.o' | xargs rm -f
...@@ -232,7 +232,7 @@ class PrimOctetStringLiteral(Primary): ...@@ -232,7 +232,7 @@ class PrimOctetStringLiteral(Primary):
pass pass
class PrimIfThenElse(Primary): class PrimConditional(Primary):
''' value is a dictionnary: ''' value is a dictionnary:
{ 'if': Expression, 'then': Expression, { 'if': Expression, 'then': Expression,
'else': Expression, 'tmpVar': integer} 'else': Expression, 'tmpVar': integer}
......
...@@ -28,12 +28,10 @@ __author__ = 'Maxime Perrotin' ...@@ -28,12 +28,10 @@ __author__ = 'Maxime Perrotin'
import sys import sys
import os import os
import re
import fnmatch
import logging import logging
import traceback import traceback
from itertools import chain, permutations, combinations from itertools import chain, permutations, combinations
from collections import defaultdict from collections import defaultdict, Counter
import antlr3 import antlr3
import antlr3.tree import antlr3.tree
...@@ -68,7 +66,6 @@ EXPR_NODE = { ...@@ -68,7 +66,6 @@ EXPR_NODE = {
lexer.NOT: ogAST.ExprNot, lexer.NOT: ogAST.ExprNot,
lexer.NEG: ogAST.ExprNeg, lexer.NEG: ogAST.ExprNeg,
lexer.PRIMARY: ogAST.Primary, lexer.PRIMARY: ogAST.Primary,
lexer.IFTHENELSE: ogAST.PrimIfThenElse,
} }
# Insert current path in the search list for importing modules # Insert current path in the search list for importing modules
...@@ -394,11 +391,7 @@ def fix_special_operators(op_name, expr_list, context): ...@@ -394,11 +391,7 @@ def fix_special_operators(op_name, expr_list, context):
for each in (INTEGER, REAL, BOOLEAN, RAWSTRING, OCTETSTRING): for each in (INTEGER, REAL, BOOLEAN, RAWSTRING, OCTETSTRING):
try: try:
check_type_compatibility(param, each, context) check_type_compatibility(param, each, context)
if each is OCTETSTRING and isinstance(param, param.exprType = each
ogAST.PrimIfThenElse):
param.exprType = param.value['then'].exprType
else:
param.exprType = each
break break
except TypeError: except TypeError:
continue continue
...@@ -551,28 +544,15 @@ def check_type_compatibility(primary, typeRef, context): ...@@ -551,28 +544,15 @@ def check_type_compatibility(primary, typeRef, context):
'" not in this enumeration: ' + '" not in this enumeration: ' +
str(actual_type.EnumValues.keys())) str(actual_type.EnumValues.keys()))
raise TypeError(err) raise TypeError(err)
elif isinstance(primary, ogAST.PrimIfThenElse): elif isinstance(primary, ogAST.PrimConditional):
# check that IF expr returns BOOL, and that Then and Else expressions
# are compatible with actual_type
if_expr = primary.value['if']
then_expr = primary.value['then'] then_expr = primary.value['then']
else_expr = primary.value['else'] else_expr = primary.value['else']
if if_expr.exprType.kind != 'BooleanType':
raise TypeError('IF expression does not return a boolean') for expr in (then_expr, else_expr):
else: if expr.is_raw:
for expr in (then_expr, else_expr): check_type_compatibility(expr, typeRef, context)
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)
return return
elif isinstance(primary, ogAST.PrimVariable): elif isinstance(primary, ogAST.PrimVariable):
try: try:
compare_types(primary.exprType, typeRef) compare_types(primary.exprType, typeRef)
...@@ -634,7 +614,7 @@ def check_type_compatibility(primary, typeRef, context): ...@@ -634,7 +614,7 @@ def check_type_compatibility(primary, typeRef, context):
# Compare the types for semantic equivalence # Compare the types for semantic equivalence
try: try:
compare_types( compare_types(
primary.value[ufield].exprType, fd_data.type) primary.value[ufield].exprType, fd_data.type)
except TypeError as err: except TypeError as err:
raise TypeError('Field ' + ufield + raise TypeError('Field ' + ufield +
' is not of the proper type, i.e. ' + ' is not of the proper type, i.e. ' +
...@@ -871,7 +851,7 @@ def fix_expression_types(expr, context): ...@@ -871,7 +851,7 @@ def fix_expression_types(expr, context):
raise TypeError('Cannot resolve type of "{}"' raise TypeError('Cannot resolve type of "{}"'
.format(unknown[0].inputString)) .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 # we must fix missing inner types
# (due to similarities, the following should be refactored FIXME) # (due to similarities, the following should be refactored FIXME)
if isinstance(expr.right, ogAST.PrimSequence): if isinstance(expr.right, ogAST.PrimSequence):
...@@ -913,7 +893,7 @@ def fix_expression_types(expr, context): ...@@ -913,7 +893,7 @@ def fix_expression_types(expr, context):
check_expr.right = expr.right.value['value'] check_expr.right = expr.right.value['value']
fix_expression_types(check_expr, context) fix_expression_types(check_expr, context)
expr.right.value['value'] = check_expr.right expr.right.value['value'] = check_expr.right
elif isinstance(expr.right, ogAST.PrimIfThenElse): elif isinstance(expr.right, ogAST.PrimConditional):
for det in ('then', 'else'): for det in ('then', 'else'):
# Recursively fix possibly missing types in the expression # Recursively fix possibly missing types in the expression
check_expr = ogAST.ExprAssign() check_expr = ogAST.ExprAssign()
...@@ -1040,8 +1020,8 @@ def expression(root, context): ...@@ -1040,8 +1020,8 @@ def expression(root, context):
return neg_expression(root, context) return neg_expression(root, context)
elif root.type == lexer.PAREN: elif root.type == lexer.PAREN:
return expression(root.children[0], context) return expression(root.children[0], context)
elif root.type == lexer.IFTHENELSE: elif root.type == lexer.CONDITIONAL:
return if_then_else_expression(root, context) return conditional_expression(root, context)
elif root.type == lexer.PRIMARY: elif root.type == lexer.PRIMARY:
return primary(root.children[0], context) return primary(root.children[0], context)
elif root.type == lexer.CALL: elif root.type == lexer.CALL:
...@@ -1076,13 +1056,16 @@ def logic_expression(root, context): ...@@ -1076,13 +1056,16 @@ def logic_expression(root, context):
errors.append(error(root, msg)) errors.append(error(root, msg))
break break
if bty.kind in ('BooleanType', 'BitStringType'): if bty.kind == 'BooleanType':
continue 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 continue
else: else:
msg = 'Bitwise operators only work with Booleans, ' \ msg = 'Bitwise operators only work with Booleans, ' \
'SequenceOf Booleans or BitStrings' 'fixed size SequenceOf Booleans or fixed size BitStrings'
errors.append(error(root, msg)) errors.append(error(root, msg))
break break
...@@ -1260,11 +1243,11 @@ def neg_expression(root, context): ...@@ -1260,11 +1243,11 @@ def neg_expression(root, context):
return expr, errors, warnings return expr, errors, warnings
def if_then_else_expression(root, context): def conditional_expression(root, context):
''' If Then Else expression analysis ''' ''' Conditional expression analysis '''
errors, warnings = [], [] errors, warnings = [], []
expr = ogAST.PrimIfThenElse( expr = ogAST.PrimConditional(
get_input_string(root), get_input_string(root),
root.getLine(), root.getLine(),
root.getCharPositionInLine() root.getCharPositionInLine()
...@@ -1286,7 +1269,20 @@ def if_then_else_expression(root, context): ...@@ -1286,7 +1269,20 @@ def if_then_else_expression(root, context):
errors.extend(err) errors.extend(err)
warnings.extend(warn) 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 # 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 = { expr.value = {
'if': if_expr, 'if': if_expr,
'then': then_expr, 'then': then_expr,
...@@ -1410,7 +1406,6 @@ def primary_call(root, context): ...@@ -1410,7 +1406,6 @@ def primary_call(root, context):
'Max': str(max(enum_values)) 'Max': str(max(enum_values))
}) })
except AttributeError: except AttributeError:
msg = 'Type Error, check the parameter'
errors.append(error(root, '"Num" parameter error')) errors.append(error(root, '"Num" parameter error'))
return node, errors, warnings return node, errors, warnings
...@@ -1620,7 +1615,7 @@ def primary(root, context): ...@@ -1620,7 +1615,7 @@ def primary(root, context):
elif root.type == lexer.OCTSTR: elif root.type == lexer.OCTSTR:
prim = ogAST.PrimOctetStringLiteral() prim = ogAST.PrimOctetStringLiteral()
warnings.append( warnings.append(
warning(root, 'Octet string literal not supported yet')) warning(root, 'Octet string literal not supported yet'))
else: else:
# TODO: return error message # TODO: return error message
raise NotImplementedError raise NotImplementedError
...@@ -1680,8 +1675,13 @@ def variables(root, ta_ast, context): ...@@ -1680,8 +1675,13 @@ def variables(root, ta_ast, context):
str(child.type)) str(child.type))
for variable in var: for variable in var:
# Add to the context and text area AST entries # Add to the context and text area AST entries
context.variables[variable] = (asn1_sort, def_value) if variable.lower() in context.variables \
ta_ast.variables[variable] = (asn1_sort, def_value) 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: if not DV:
errors.append('Cannot do semantic checks on variable declarations') errors.append('Cannot do semantic checks on variable declarations')
return errors, warnings return errors, warnings
...@@ -2543,7 +2543,7 @@ def state(root, parent, context): ...@@ -2543,7 +2543,7 @@ def state(root, parent, context):
state_def.charPositionInLine = child.getCharPositionInLine() state_def.charPositionInLine = child.getCharPositionInLine()
exceptions = [c.toString() for c in child.getChildren()] exceptions = [c.toString() for c in child.getChildren()]
for st in context.mapping: for st in context.mapping:
if st not in (exceptions, 'START'): if st not in exceptions + ['START']:
state_def.statelist.append(st) state_def.statelist.append(st)
elif child.type == lexer.INPUT: elif child.type == lexer.INPUT:
# A transition triggered by an INPUT # A transition triggered by an INPUT
...@@ -2553,6 +2553,19 @@ def state(root, parent, context): ...@@ -2553,6 +2553,19 @@ def state(root, parent, context):
warnings.extend(warn) warnings.extend(warn)
try: try:
for statename in state_def.statelist: 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) context.mapping[statename.lower()].append(inp)
except KeyError: except KeyError:
warnings.append('State definition missing') warnings.append('State definition missing')
...@@ -2564,9 +2577,9 @@ def state(root, parent, context): ...@@ -2564,9 +2577,9 @@ def state(root, parent, context):
else: else:
asterisk_input = inp asterisk_input = inp
elif child.type == lexer.CONNECT: elif child.type == lexer.CONNECT:
comp_states = (comp.statename for comp in context.composite_states)
if asterisk_state or len(state_def.statelist) != 1 \ if asterisk_state or len(state_def.statelist) != 1 \
or (state_def.statelist[0].lower() or state_def.statelist[0].lower() not in comp_states:
not in (comp.statename for comp in context.composite_states)):
errors.append('State {} is not a composite state and cannot ' errors.append('State {} is not a composite state and cannot '
'be followed by a connect statement' 'be followed by a connect statement'
.format(state_def.statelist[0])) .format(state_def.statelist[0]))
...@@ -2960,6 +2973,7 @@ def decision(root, parent, context): ...@@ -2960,6 +2973,7 @@ def decision(root, parent, context):
covered_ranges = defaultdict(list) covered_ranges = defaultdict(list)
qmin, qmax = 0, 0 qmin, qmax = 0, 0
need_else = False need_else = False
is_enum = False
for ans in dec.answers: for ans in dec.answers:
if ans.kind in ('constant', 'open_range'): if ans.kind in ('constant', 'open_range'):
expr = ans.openRangeOp() expr = ans.openRangeOp()
...@@ -2972,6 +2986,13 @@ def decision(root, parent, context): ...@@ -2972,6 +2986,13 @@ def decision(root, parent, context):
ans.constant = expr.right ans.constant = expr.right
q_basic = find_basic_type(dec.question.exprType) q_basic = find_basic_type(dec.question.exprType)
a_basic = find_basic_type(ans.constant.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'): if not q_basic.kind.startswith('Integer'):
continue continue
# numeric type -> find the range covered by this answer # numeric type -> find the range covered by this answer
...@@ -3065,7 +3086,7 @@ def decision(root, parent, context): ...@@ -3065,7 +3086,7 @@ def decision(root, parent, context):
qmin, qmax = int(float(q_basic.Min)), int(float(q_basic.Max)) qmin, qmax = int(float(q_basic.Min)), int(float(q_basic.Max))
a0_val = int(float(a0_basic.Min)) a0_val = int(float(a0_basic.Min))
a1_val = int(float(a1_basic.Max)) a1_val = int(float(a1_basic.Max))
if a0_val < qmin: if a0_val < qmin:
warnings.append('Decision "{dec}": ' warnings.append('Decision "{dec}": '
'Range [{a0} .. {qmin}] is unreachable' 'Range [{a0} .. {qmin}] is unreachable'
.format(a0=a0_val, qmin=qmin - 1, .format(a0=a0_val, qmin=qmin - 1,
...@@ -3082,14 +3103,17 @@ def decision(root, parent, context): ...@@ -3082,14 +3103,17 @@ def decision(root, parent, context):
l=a0_val, h=a1_val)) l=a0_val, h=a1_val))
covered_ranges[ans].append((int(float(a0_basic.Min)), covered_ranges[ans].append((int(float(a0_basic.Min)),
int(float(a1_basic.Max)))) int(float(a1_basic.Max))))
# Check the following: # Check the following
# (1) no overlap between covered ranges in decision answers # (1) no overlap between covered ranges in decision answers
# (2) no gap in the coverage of the decision possible values # (2) no gap in the coverage of the decision possible values
# (3) ELSE branch, if present, can be reached # (3) ELSE branch, if present, can be reached
# (4) if an answer uses a non-ground expression an ELSE is there # (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 [] q_ranges = [(qmin, qmax)] if is_numeric(dec.question.exprType) else []
for each in combinations(covered_ranges.viewitems(), 2): for each in combinations(covered_ranges.viewitems(), 2):
if not q_ranges:
continue
for comb in combinations( for comb in combinations(
chain.from_iterable(val[1] for val in each), 2): chain.from_iterable(val[1] for val in each), 2):
comb_overlap = (max(comb[0][0], comb[1][0]), comb_overlap = (max(comb[0][0], comb[1][0]),
...@@ -3104,9 +3128,10 @@ def decision(root, parent, context): ...@@ -3104,9 +3128,10 @@ def decision(root, parent, context):
o1=comb_overlap[0], o1=comb_overlap[0],
o2=comb_overlap[1])) o2=comb_overlap[1]))
new_q_ranges = [] new_q_ranges = []
# for minq, maxq in q_ranges:
# (2) Check that decision range is fully covered # (2) Check that decision range is fully covered
for ans_ref, ranges in covered_ranges.viewitems(): for ans_ref, ranges in covered_ranges.viewitems():
if is_enum:
continue
for mina, maxa in ranges: for mina, maxa in ranges:
for minq, maxq in q_ranges: for minq, maxq in q_ranges:
left = (minq, min(maxq, mina - 1)) left = (minq, min(maxq, mina - 1))
...@@ -3134,6 +3159,21 @@ def decision(root, parent, context): ...@@ -3134,6 +3159,21 @@ def decision(root, parent, context):
warnings.append('Decision "{}": Missing ELSE branch' warnings.append('Decision "{}": Missing ELSE branch'
.format(dec.inputString)) .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: