Commit 035847f2 authored by Maxime Perrotin's avatar Maxime Perrotin
Browse files

Work on the type checker

parent 4244eab6
......@@ -1539,10 +1539,10 @@ def _prim_call(prim):
local_decl.extend(local_var)
ada_string += '{op}({param})'.format(
param=param_str,
op='abs' if ident == 'abs'
op='abs' if ident == 'abs'
else 'Asn1Int' if (ident == 'fix' and not unsigned)
else 'Asn1UInt' if (ident == 'fix' and unsigned)
else 'Asn1Real' if ident == 'float' else 'ERROR')
else 'Asn1Real' if ident == 'float' else 'ERROR')
# if ident == 'abs':
# ada_string += ')'
elif ident == 'power':
......@@ -1935,11 +1935,12 @@ def _assign_expression(expr):
# Make sure that integers are cast to 64 bits
# It is possible that left and right are of different types
# (signed vs unsigned and/or 32bits vs 64 bits).
# (signed vs unsigned and/or 32 bits vs 64 bits).
# The parser should have ensured that the ranges are compatible.
# We can therefore safely cast to the left type
basic_right = find_basic_type (expr.right.exprType)
cast_left, cast_right = type_name(basic_left), type_name(basic_right)
print cast_left, cast_right, right_str
if cast_left != cast_right:
res = u'{cast}({val})'.format(cast=cast_left, val=right_str)
else:
......
......@@ -50,7 +50,7 @@ class Expression(object):
self.charPositionInLine = charPositionInLine
# Binary expressions have two sides
self.right = self.left = None
# Unary expressions are stored in "expr"
# Unary expressions (NOT and NEG) are stored in "expr"
self.expr = None
# exprType is an ASN.1 type (as exported by asn1scc)
......
......@@ -186,6 +186,11 @@ def substring_range(substring):
return left_bty.Min, right_bty.Max
def is_number(basic_ty):
''' Return true if basic type is a raw number (i.e. not a variable) '''
return basic_ty.__name__ in ('Universal_Integer', 'PrReal')
def is_integer(ty):
# type: (Any) -> bool
''' Return true if a type is an Integer Type '''
......@@ -526,10 +531,10 @@ def check_call(name, params, context):
# (3) Check each individual parameter type
for idx, param in enumerate(params):
warnings = []
expr = ogAST.ExprAssign()
expr.left = ogAST.PrimVariable()
expr = ogAST.ExprAssign()
expr.left = ogAST.PrimVariable()
expr.left.exprType = sign[idx]['type']
expr.right = param
expr.right = param
try:
basic_left = find_basic_type(expr.left.exprType)
......@@ -557,9 +562,11 @@ def check_call(name, params, context):
# (4) Compute the type of the result
param_btys = [find_basic_type(p.exprType) for p in params]
if name == 'abs':
# The implementation of abs in *all* progamming languages returns
# The implementation of abs in *all* programming languages returns
# a type that is the same as the type of the parameter. The returned
# value is *not* unsigned. abs(integer'Min) returns a NEGATIVE number
# this is an issue in an assign statement, if the recipient is
# unsigned .. A cast is necessary if the parameter of abs is negative
return type('Abs', (param_btys[0],), {})
# return type('Abs', (param_btys[0],), {
# 'Min': str(max(float(param_btys[0].Min), 0)),
......@@ -744,7 +751,7 @@ def check_type_compatibility(primary, type_ref, context): # type: -> [warnings]
err=str(err)))
return warnings
elif isinstance(primary, ogAST.PrimInteger) \
elif isinstance(primary, (ogAST.PrimInteger, ogAST.ExprMod)) \
and (is_integer(type_ref) or type_ref == NUMERICAL):
return warnings
......@@ -965,7 +972,8 @@ def compare_types(type_a, type_b): # type -> [warnings]
elif type_a.kind == 'IntegerType':
# Detect Signed/Unsigned type mismatch
min_a, min_b = float(type_a.Min), float(type_b.Min)
if (min_a >= 0) != (min_b >= 0):
if (min_a >= 0) != (min_b >= 0) \
and not (is_number(type_a) or is_number(type_b)):
raise TypeError("Signed vs Unsigned type mismatch " +
mismatch)
elif mismatch:
......@@ -1169,7 +1177,18 @@ def fix_expression_types(expr, context): # type: -> [warnings]
# removing SequenceOf and String from here should be OK.
raw_expr.exprType = ref_type
else:
warnings.extend(compare_types(expr.left.exprType, expr.right.exprType))
try:
warnings.extend(compare_types(expr.left.exprType, expr.right.exprType))
except TypeError as err:
print "here", expr.left.inputString, " and right = ", expr.right.inputString
print expr.left.exprType, expr.right.exprType
print "--> ", str(err)
#print traceback.format_stack (limit=2)
left_type = find_basic_type(expr.left.exprType)
right_type = find_basic_type(expr.right.exprType)
print " left min/max:", left_type.Min, left_type.Max
print " right min/max:", right_type.Min, right_type.Max
raise
return list(set(warnings))
......@@ -1254,8 +1273,9 @@ def binary_expression(root, context):
expr.tmpVar = tmp()
left, right = root.children
expr.left, err_left, warn_left = expression(left, context)
expr.left, err_left, warn_left = expression(left, context)
expr.right, err_right, warn_right = expression(right, context)
errors.extend(err_left)
warnings.extend(warn_left)
errors.extend(err_right)
......@@ -1270,7 +1290,8 @@ def binary_expression(root, context):
def unary_expression(root, context):
''' Unary expression analysis '''
''' Unary expression analysis
(NOT and NEG, i.e. "not bah" and "-4" or "-foo" '''
ExprNode = EXPR_NODE[root.type]
expr = ExprNode(
get_input_string(root),
......@@ -1278,7 +1299,7 @@ def unary_expression(root, context):
root.getCharPositionInLine()
)
expr.exprType = UNKNOWN_TYPE
expr.tmpVar = tmp()
expr.tmpVar = tmp()
expr.expr, errors, warnings = expression(root.children[0], context)
......@@ -1375,12 +1396,21 @@ def arithmetic_expression(root, context):
return { 'Min': str(min(candidates)),
'Max': str(max(candidates))}
# Call order is: expression -> arithmetic_expression -> binary_expression
# the latter calls fix_expression_types to determine the type of each side
# of the expression. The type of the expression should still be unknown
# at this point (expr.exprType).
print "[DEBUG] Arithmetic expression:", get_input_string(root)
expr, errors, warnings = binary_expression(root, context)
print "[DEBUG] Left:", expr.left.exprType, "Right:", expr.right.exprType
# Expressions returning a numerical type must have their range defined
# accordingly with the kind of operation used between operands:
left = find_basic_type(expr.left.exprType)
right = find_basic_type(expr.right.exprType)
# Get the basic types to have the ranges
basic_left = find_basic_type(expr.left.exprType)
basic_right = find_basic_type(expr.right.exprType)
#print "[DEBUG] Left Range:", basic_left.Min, basic_left.Max, basic_left.kind
#print "[DEBUG] Right Range:", basic_right.Min, basic_right.Max, basic_right.kind
def get_constant_value(const_val):
# value may be a reference to another constant. In that case we
......@@ -1403,49 +1433,91 @@ def arithmetic_expression(root, context):
raise ValueError(str(first_str) + " actual value not found" )
try:
minL = float(left.Min)
maxL = float(left.Max)
minR = float(right.Min)
maxR = float(right.Max)
# set Min and Max.. Note, for Int32 types (e.g. For loop index)
# the Min must be compatible with the other side...
minL = float(basic_left.Min) \
if basic_left.kind != "Integer32Type" \
else float(basic_right.Min)
maxL = float(basic_left.Max)
minR = float(basic_right.Min) \
if basic_right.kind != "Integer32Type" \
else float(basic_left.Min)
maxR = float(basic_right.Max)
# Constants defined in ASN.1 : take their value for the range
if isinstance(expr.left, ogAST.PrimConstant):
minL = maxL = get_constant_value(expr.left.constant_value)
if isinstance(expr.right, ogAST.PrimConstant):
minL = maxL = get_constant_value(expr.right.constant_value)
# Type of the resulting expression depends on whether there are raw
# numbers on one side of the expression (PrInt). By default when they
# are parsed, they are set to 64 bits integers ; but if they are in an
# expression where the other side is 32 bits (Length or for loop range)
# then the resulting expression is 32 bits.
basic = right if left.__name__ == 'PrInt' else left
# When one side of the expression is a raw (universal) number,
# in backends the type is inherited from the other side of the
# expression
# e.g. x - 1 is of type x. Keep track of this resulting type in
# "expected_type" to make sure that backends know if the type is signed
# or unsigned, even if the computed range is lower than 0
expr.expected_type = expr.left.exprType if right.__name__ == 'PrInt' \
else expr.right.exprType if left.__name__ == 'PrInt' \
else None
if isinstance(expr, ogAST.ExprPlus):
attrs = {'Min': str(minL + minR),
'Max': str(maxL + maxR)}
expr.exprType = type('Plus', (basic,), attrs)
elif isinstance(expr, ogAST.ExprMul):
attrs = find_bounds(operator.mul, minL, maxL, minR, maxR)
expr.exprType = type('Mul', (basic,), attrs)
elif isinstance(expr, ogAST.ExprMinus):
attrs = {'Min': str(minL - maxR),
'Max': str(maxL - minR)}
expr.exprType = type('Minus', (basic,), attrs)
elif isinstance(expr, ogAST.ExprDiv):
attrs = find_bounds(operator.truediv, minL, maxL, minR or 1,
maxR or 1)
expr.exprType = type('Div', (basic,), attrs)
elif isinstance(expr, (ogAST.ExprMod, ogAST.ExprRem)):
attrs = {'Min': '0', 'Max': right.Max}
expr.exprType = type('Mod', (basic,), attrs)
# numbers on one side of the expression (Universal_Integer):
#
# case 1:
# If there is a side with an ASN.1 type and a side with a raw number,
# the resulting type is the ASN.1 type
if is_number(basic_right) != is_number(basic_left):
expr.exprType = expr.left.exprType if is_number(basic_right) \
else expr.right.exprType
# case 2:
# two numbers, then the result is also a number with a range depending
# on the result of the expression
elif is_number(basic_right) == is_number(basic_left) == True:
if isinstance(expr, ogAST.ExprPlus):
attrs = {'Min': str(minL + minR),
'Max': str(maxL + maxR)}
expr.exprType = type('Plus', (basic_right,), attrs)
elif isinstance(expr, ogAST.ExprMul):
attrs = find_bounds(operator.mul, minL, maxL, minR, maxR)
expr.exprType = type('Mul', (basic_right,), attrs)
elif isinstance(expr, ogAST.ExprMinus):
attrs = {'Min': str(minL - maxR),
'Max': str(maxL - minR)}
expr.exprType = type('Minus', (basic_right,), attrs)
elif isinstance(expr, ogAST.ExprDiv):
attrs = find_bounds(operator.truediv, minL, maxL, minR or 1,
maxR or 1)
expr.exprType = type('Div', (basic_right,), attrs)
elif isinstance(expr, ogAST.ExprMod):
# modulo returns an positive number, however it may not be
# unsigned, it returns a number of the same sign as its
# parameter (i.e. the left side of the expression)
# unless the left side is a universal number, in which case
# the type has to be deduced from the user of the expression
# (e.g. the left side of an assignment)
if not is_number (basic_left):
attrs = {'Min': basic_left.Min, 'Max': basic_right.Max}
expr.exprType = type('Mod', (basic_right,), attrs)
else:
# set expression as raw, otherwise it will not be resolved
expr.is_raw = True
elif isinstance(expr, ogAST.ExprRem):
# rem returns an positive or negative number
attrs = {'Min': basic_left.Min, 'Max': basic_right.Max}
expr.exprType = type('Rem', (basic_right,), attrs)
# case 3:
# no numbers, two ASN.1 types. raise an error if there is a
# signed/unsigned mismatch
# otherwise (sign ok), set the type with the min of both Min
# and the max of both Max for the range
else:
if (minL < 0) != (minR < 0):
msg = '"' + expr.left.inputString + '" is ' + ("un"
if minL >= 0 else "") + 'signed while "' \
+ expr.right.inputString + '" is not'
errors.append(error(root, msg))
#print "[DEBUG] SIGNED/UNSIGNED MISMATCH", minL < 0, minR < 0
else: # sign is consistent on both sides
min_min = str(min(minL, minR))
max_max = str(max(maxL, maxR))
attrs = {'Min': min_min, 'Max': max_max}
expr.exprType = type('Number', (basic_right,), attrs)
if expr.exprType is not UNKNOWN_TYPE:
expr.expected_type = expr.exprType
except (ValueError, AttributeError):
msg = 'Check that all your numerical data types '\
'have a range constraint'
......@@ -1458,7 +1530,7 @@ def arithmetic_expression(root, context):
msg = 'Mod/Rem expressions can only applied to Integer types'
errors.append(error(root, msg))
break
print "[DEBUG] Done"
return expr, errors, warnings
......@@ -1588,7 +1660,7 @@ def not_expression(root, context):
def neg_expression(root, context):
''' Negative expression analysis '''
''' Negative expression analysis (root.type == lexer.NEG)'''
expr, errors, warnings = unary_expression(root, context)
basic = find_basic_type(expr.expr.exprType)
......@@ -1597,6 +1669,15 @@ def neg_expression(root, context):
errors.append(error(root, msg))
return expr, errors, warnings
if is_number(basic):
# If the parameter is a raw number, no need for an Neg expression
expr.expr.value[0] = u'-{}'.format(expr.expr.value[0])
attrs = {'Min' : str(-float(basic.Max)),
'Max' : str(-float(basic.Min)),
'kind': 'IntegerType'}
expr.expr.exprType = type('Universal_Integer', (object,), attrs)
return expr.expr, errors, warnings
try:
attrs = {'Min': str(-float(basic.Max)), 'Max': str(-float(basic.Min))}
expr.exprType = type('Neg', (basic,), attrs)
......@@ -1748,6 +1829,8 @@ def primary_index(root, context):
r_min, r_max = substring_range(receiver)
else:
r_min, r_max = receiver_bty.Min, receiver_bty.Max
# Is that correct for SEQOF ? the exprType of the node should be the
# type of the elements of the SEQOF, not the SEQOF type itself, no?
node.exprType = receiver_bty.type
idx_bty = find_basic_type(params[0].exprType)
if not is_integer(idx_bty):
......@@ -1874,14 +1957,14 @@ def primary(root, context):
elif root.type == lexer.INT:
prim = ogAST.PrimInteger()
prim.value = [root.text.lower()]
prim.exprType = type('PrInt', (object,), {
prim.exprType = type('Universal_Integer', (object,), {
'kind': 'IntegerType',
'Min': root.text,
'Max': root.text
'Min' : root.text,
'Max' : root.text
})
elif root.type in (lexer.TRUE, lexer.FALSE):
prim = ogAST.PrimBoolean()
prim.value = [root.text.lower()]
prim = ogAST.PrimBoolean()
prim.value = [root.text.lower()]
prim.exprType = type('PrBool', (object,), {'kind': 'BooleanType'})
# elif root.type == lexer.NULL:
# prim = ogAST.PrimNull()
......@@ -1892,8 +1975,8 @@ def primary(root, context):
prim.value = [root.text]
prim.exprType = type('PrReal', (object,), {
'kind': 'RealType',
'Min': prim.value[0],
'Max': prim.value[0]
'Min': prim.value[0],
'Max': prim.value[0]
})
elif root.type == lexer.STRING:
prim = ogAST.PrimStringLiteral()
......@@ -4382,9 +4465,6 @@ def for_loop(root, context):
basic = find_basic_type(stop_expr.exprType)
r_max = str(int(float(basic.Max) - 1)) \
if basic != UNKNOWN_TYPE else '4294967295'
# basic may be UNKNOWN_TYPE if the expression is a
# reference to an ASN.1 constant - their values are not
# currently visible to the SDL parser
result_type = type('for_range', (INT32,), {'Min': r_min,
'Max': r_max})
forloop['type'] = result_type
......
/* CIF PROCESS (151, 149), (150, 75) */
PROCESS controlflow;
/* CIF TEXT (376, 97), (303, 168) */
process controlflow;
/* CIF TEXT (663, 97), (303, 168) */
-- Declare your variables
-- Syntax: DCL <variable name> <type name>;
DCL n Integer;
DCL b Boolean;
DCL seq1 IntegerSeqOf;
DCL seq2 IntegerSeqOf;
-- Syntax: DCL <variable name> <type name>;
DCL n Integer;
DCL b Boolean;
DCL seq1 IntegerSeqOf;
DCL seq2 IntegerSeqOf;
/* CIF ENDTEXT */
/* CIF START (3, 0), (70, 35) */
/* CIF START (290, 0), (70, 35) */
START;
/* CIF NEXTSTATE (3, 50), (70, 35) */
/* CIF NEXTSTATE (290, 50), (70, 35) */
NEXTSTATE Wait;
/* CIF LABEL (2, 1904), (75, 35) */
CONNECTION label2:
/* CIF PROCEDURECALL (-28, 1954), (136, 35) */
CALL writeln('label2');
/* CIF LABEL (2, 2004), (75, 35) */
/* CIF label (31, 1747), (78, 35) */
connection label1:
/* CIF PROCEDURECALL (0, 1797), (141, 35) */
call writeln('label1');
/* CIF join (53, 1847), (35, 35) */
join label2;
/* CIF End Label */
endconnection;
/* CIF label (289, 1904), (75, 35) */
connection label2:
/* CIF PROCEDURECALL (259, 1954), (136, 35) */
call writeln('label2');
/* CIF label (289, 2004), (75, 35) */
label3:
/* CIF PROCEDURECALL (-28, 2054), (136, 35) */
CALL writeln('label3');
/* CIF NEXTSTATE (5, 2104), (70, 35) */
/* CIF PROCEDURECALL (259, 2054), (136, 35) */
call writeln('label3');
/* CIF NEXTSTATE (292, 2104), (70, 35) */
NEXTSTATE wait;
/* CIF End Label */
ENDCONNECTION;
/* CIF LABEL (0, 1747), (78, 35) */
CONNECTION label1:
/* CIF PROCEDURECALL (-31, 1797), (141, 35) */
CALL writeln('label1');
/* CIF JOIN (21, 1847), (35, 35) */
JOIN label2;
/* CIF End Label */
ENDCONNECTION;
/* CIF STATE (3, 50), (70, 35) */
STATE Wait;
/* CIF INPUT (3, 105), (70, 35) */
INPUT run;
/* CIF TASK (-133, 155), (343, 35) */
TASK '------------------------------- Decision -------------------------------';
/* CIF TASK (2, 205), (70, 35) */
TASK n := 2;
/* CIF DECISION (3, 255), (70, 50) */
DECISION n;
/* CIF ANSWER (-107, 325), (70, 24) */
(1):
/* CIF PROCEDURECALL (-148, 364), (153, 35) */
CALL fail('decision else');
/* CIF ANSWER (114, 325), (70, 24) */
else:
ENDDECISION;
/* CIF DECISION (3, 414), (70, 50) */
DECISION n;
/* CIF ANSWER (-143, 474), (70, 24) */
(1):
/* CIF PROCEDURECALL (-200, 513), (184, 35) */
CALL fail( 'decision constant');
/* CIF ANSWER (3, 474), (70, 24) */
(2):
/* CIF ANSWER (165, 474), (70, 24) */
else:
/* CIF PROCEDURECALL (107, 513), (184, 35) */
CALL fail( 'decision constant');
ENDDECISION;
/* CIF DECISION (3, 563), (70, 50) */
DECISION n;
/* CIF ANSWER (-79, 623), (70, 24) */
(<2):
/* CIF PROCEDURECALL (-142, 662), (197, 35) */
CALL fail('decision open range');
/* CIF ANSWER (95, 623), (70, 24) */
(>1):
ENDDECISION;
/* CIF DECISION (3, 712), (70, 50) */
DECISION n;
/* CIF ANSWER (-171, 772), (70, 24) */
(-1:1):
/* CIF PROCEDURECALL (-247, 811), (223, 35) */
CALL fail('decision closed range');
/* CIF ANSWER (3, 772), (70, 24) */
(2:5):
/* CIF ANSWER (177, 772), (70, 24) */
else:
/* CIF PROCEDURECALL (109, 811), (205, 35) */
CALL fail('decision closed range');
ENDDECISION;
/* CIF TASK (-136, 861), (348, 35) */
TASK '------------------------------- For loops -------------------------------';
/* CIF TASK (2, 911), (70, 35) */
TASK n := 0;
/* CIF TASK (-45, 961), (167, 56) */
TASK for x in range(10):
endconnection;
/* CIF state (290, 50), (70, 35) */
state Wait;
/* CIF input (290, 105), (70, 35) */
input run;
/* CIF task (154, 155), (343, 35) */
task '------------------------------- Decision -------------------------------';
/* CIF task (290, 205), (70, 35) */
task n := 2;
/* CIF decision (290, 255), (70, 50) */
decision n;
/* CIF ANSWER (216, 325), (70, 24) */
(1):
/* CIF PROCEDURECALL (175, 364), (153, 35) */
call fail('decision else');
/* CIF ANSWER (363, 325), (70, 24) */
else:
enddecision;
/* CIF decision (290, 414), (70, 50) */
decision n;
/* CIF ANSWER (190, 484), (70, 24) */
(1):
/* CIF PROCEDURECALL (133, 523), (184, 35) */
call fail( 'decision constant');
/* CIF ANSWER (327, 484), (70, 24) */
(2):
/* CIF ANSWER (507, 484), (70, 24) */
else:
/* CIF PROCEDURECALL (450, 523), (184, 35) */
call fail( 'decision constant');
enddecision;
/* CIF decision (290, 573), (70, 50) */
decision n;
/* CIF ANSWER (366, 643), (70, 24) */
(<2):
/* CIF PROCEDURECALL (303, 682), (197, 35) */
call fail('decision open range');
/* CIF ANSWER (133, 645), (70, 24) */
(>1):
enddecision;
/* CIF decision (290, 732), (70, 50) */
decision n;
/* CIF ANSWER (458, 802), (70, 24) */
(-1:1):
/* CIF PROCEDURECALL (382, 841), (223, 35) */
call fail('decision closed range');
/* CIF ANSWER (290, 802), (70, 24) */
(2:5):
/* CIF ANSWER (114, 802), (70, 24) */
else:
/* CIF PROCEDURECALL (47, 841), (205, 35) */
call fail('decision closed range');
enddecision;
/* CIF task (151, 891), (348, 35) */
task '------------------------------- For loops -------------------------------';
/* CIF task (290, 941), (70, 35) */
task n := 0;
/* CIF task (242, 991), (167, 56) */
task for x in range(10):
n := n + x mod 255
endfor;
/* CIF PROCEDURECALL (-80, 1032), (237, 35) */
CALL assert(n = 45, 'for x in range(10)');
/* CIF TASK (2, 1082), (70, 35) */
TASK n := 0;
/* CIF TASK (-45, 1132), (167, 56) */
TASK for x in range(4, 10):
/* CIF PROCEDURECALL (207, 1062), (237, 35) */
call assert(n = 45, 'for x in range(10)');
/* CIF task (290, 1112), (70, 35) */
task n := 0;
/* CIF task (242, 1162), (167, 56) */
task for x in range(4, 10):
n := n + x mod 255
endfor;
/* CIF PROCEDURECALL (-87, 1203), (251, 35) */
CALL assert(n = 39, 'for x in range(4, 10)');
/* CIF TASK (2, 1253), (70, 35) */
TASK n := 0;
/* CIF TASK (-66, 1303), (208, 56) */
TASK for x in range(4, 10, 2):
/* CIF PROCEDURECALL (200, 1233), (251, 35) */
call assert(n = 39, 'for x in range(4, 10)');
/* CIF task (290, 1283), (70, 35) */
task n := 0;
/* CIF task (221, 1333), (208, 56) */
task for x in range(4, 10, 2):
n := n + x mod 255
endfor;
/* CIF PROCEDURECALL (-96, 1374), (269, 35) */
CALL assert(n = 18, 'for x in range(4, 10, 2)');
/* CIF TASK (-37, 1424), (150, 35) */
TASK seq1 := {7, 8, 9, 10};
/* CIF TASK (2, 1474), (70, 35) */
TASK n := 0;
/* CIF TASK (-40, 1524), (157, 56) */
TASK for x in seq1:
/* CIF PROCEDURECALL (191, 1404), (269, 35) */
call assert(n = 18, 'for x in range(4, 10, 2)');
/* CIF task (250, 1454), (150, 35) */
task seq1 := {7, 8, 9, 10};
/* CIF task (290, 1504), (70, 35) */
task n := 0;
/* CIF task (245, 1554), (160, 56) */
task for x in seq1:
n := n + x mod 255
endfor;
/* CIF PROCEDURECALL (-67, 1595), (210, 35) */
CALL assert(n = 34, 'for x in seq1' );
/* CIF TASK (-124, 1645), (325, 35) */
TASK '------------------------------- Join ------------------------------- ';
/* CIF JOIN (20, 1695), (35, 35) */
JOIN label1;
ENDSTATE;
ENDPROCESS controlflow;
\ No newline at end of file