This chapter describes how data types are handled in SDT. An overview of all supported SDL data types is given, including examples and guidelines. It is also explained how to use C and ASN.1 in combination with SDT.
An important and often difficult aspect of system design and implementation is how to handle data in the system.
SDT offers several ways to use data:
This chapter gives an overview of all available data types, together with some guidelines of how to use these different data types, illustrated with a number of examples.
In this section, an overview is given of the data types that are available in SDL. SDL contains a number of predefined data types. Based on these predefined types it is possible to define user-specific data types. Types, or according to SDL terminology, "sorts", are defined using the keywords newtype
and endnewtype
.
newtype example1 struct a integer; b character; endnewtype;
A newtype definition introduces a new distinct type, which is not compatible with any other type. So if we would have another newtype otherexample
with exactly the same definition as example1
above, it would not be possible to assign a value of example1
to a variable of otherexample
.
It is also possible to introduce types, syntypes, that are compatible with their base type, but contain restrictions on the allowed value set for the type. Syntypes are defined using the keywords syntype
and endsyntype
.
syntype example2 = integer constants 0:10; endsyntype;
The syntype example2
is an integer type, but a variable of this type is only allowed to contain values in the specified range 0 to 10. Such a constant clause is called a range condition. The range check is performed when the SDL system is interpreted. Without a range condition a syntype definition just introduces a new name for the same sort.
For every sort or syntype defined in SDL, the following operators are always defined:
These operators are not mentioned among the available operators in the rest of this section. Operators are defined in SDL by a type of algebra according to the following example:
"+" : Integer, Integer -> Integer; num : Character -> Integer;
The double quotes around the +
indicate that this is an infix operator. The above "+
" takes two integer parameters and returns an integer value. The second operator, num
, is a prefix operator taking one Character and returning an Integer value. The operators above can be called within expressions in, for example, task statements:
task i := i+1; task n := num(`X');
where it is assumed that i
and n
are integer variables. It is also allowed to call an infix operator as a prefix operator:
task i := "+"(i, 1);
This means the same as i:= i+1
.
The predefined sorts in SDL are defined in an appendix to the SDL Recommendation Z100. Some more predefined sorts are introduced in the Recommendation Z105, where it is specified how ASN.1 is to be used in SDL. These types should not be used if the SDL system must conform to Z.100. SDT also offers Telelogic-specific operators for some types. These operators should not either be used if your SDL system must be Z.100 compliant. The rest of this chapter describes all predefined sorts. Unless stated otherwise, the sort is part of recommendation Z.100.
The predefined Bit
can only take two values, 0
and 1
. Bit is defined in Z.105 for the definition of bit strings, and is not part of Z.100. The operators that are available for Bit values are:
"NOT" : Bit -> Bit "AND" : Bit, Bit -> Bit "OR" : Bit, Bit -> Bit "XOR" : Bit, Bit -> Bit "=>" : Bit, Bit -> Bit
These operators are defined according to the following:
NOT
: NOT 0
gives 1, NOT 1
gives 0
AND
: 0 AND 0
gives 0, 0 AND 1
gives 0, 1 AND 1
gives 1
OR
: 0 OR 0
gives 0, 0 OR 1
gives 1, 1 OR 1
gives 1
XOR
: 0 XOR 0
gives 0, 0 XOR 1
gives 1, 1 XOR 1
gives 0
=>
(implication) : 0 => 0
gives 1, 1 => 0
gives 0, 0 => 1
gives 1, 1 => 1
gives 1
The Bit type has most of its properties in common with the Boolean type, which is discussed below. By replacing 0
with False
and 1
with True
the sorts are identical.
Bit and Boolean should be used to represent properties in a system that can only take two values, like on - off. In the choice between Bit and Boolean, Boolean is recommended except if the property to be represented is about bits and the literals 0
and 1
are more adequate than False
and True
.
The predefined sort Bit_String
is used to represent a string or sequence of Bits. Bit_String is defined in Z.105 to support the ASN.1 BIT STRING
type, and is not part of Z.100. There is no limit on the number of elements in the Bit_String.
The following operators are defined in Bit_String:
MkString : Bit -> Bit_String Length : Bit_String -> Integer First : Bit_String -> Bit Last : Bit_String -> Bit "//" : Bit_String, Bit_String -> Bit_String SubString : Bit_String, Integer, Integer -> Bit_String BitStr : Charstring -> Bit_String HexStr : Charstring -> Bit_String "NOT" : Bit_String -> Bit_String "AND" : Bit_String, Bit_String -> Bit_String "OR" : Bit_String, Bit_String -> Bit_String "XOR" : Bit_String, Bit_String -> Bit_String "=>" : Bit_String, Bit_String -> Bit_String
These operators are defined as follows:
MkString
: MkString (0)
gives a Bit_String of one element, i.e. 0
Length
: Length (BitStr(`0110')) = 4
First
: First (BitStr (`10')) = 1
Last
: Last (BitStr (`10')) = 0
//
(concatenation) : BitStr(`01')//BitStr(`10') = BitStr(`0110')
Substring
: Substring (BitStr(`0110'), 1, 2) = Bitstr(`11')
BitStr
: HexStr
: HexStr(`a') = BitStr(`1010'),
HexStr(`8f') = BitStr(`10001111')
NOT
: NOT BitStr (`0110') = BitStr (`1001')
AND
: BitStr(`01101') AND BitStr(`101') = BitStr(`00100')
OR
: BitStr(`0110') OR BitStr(`00110') = BitStr(`01111')
XOR
: BitStr(`10100') XOR BitStr(`1001') = BitStr(`00111')
=>
(implication) : BitStr (`1100') => BitStr (`0101') = BitStr (`0111')
It is also possible to access Bit elements in a Bit_String by indexing a Bit_String variable. Assume that B
is a Bit_String variable. Then it is possible to write:
task B(2) := B(3);
This would mean that Bit number 2 is assigned the value of Bit number 3 in the variable B
. Is is an error to index a Bit_String outside of its length.
Note: The first Bit in a Bit_String has index 0, whereas most other string types in SDL start with index 1! |
The newtype Boolean
can only take two values, False
and True
. The operators that are available for Boolean values are:
"NOT" : Boolean -> Boolean "AND" : Boolean, Boolean -> Boolean "OR" : Boolean, Boolean -> Boolean "XOR" : Boolean, Boolean -> Boolean "=>" : Boolean, Boolean -> Boolean
These operators are defined according to the following:
NOT
: NOT False = True NOT True = False
AND
: False AND False = False False AND True = False True AND False = False True AND True = True
OR
: False OR False = False False OR True = True True OR False = True True OR True = True
XOR
: False XOR False = False False XOR True = True True XOR False = True True XOR True = False
=>
(implication) :False => False = True False => True = True True => False = False True => True = True
The Bit sort, discussed above, has most of its properties in common with the Boolean sort. By replacing 0
with False
and 1
with True
the sorts are identical. Normally it is recommended to use Boolean instead of Bit; for a more detailed discussion see Bit.
The character
sort is used to represent the ASCII characters. The printable characters have literals according to the following example:
`a' `-' `?' `2' `P' `'''
Note that the character ` is written twice in the literal. For the non-printable characters, specific literal names have been included in the Character sort. The following:
NUL, SOH, STX, ETX, EOT, ENQ, ACK, BEL, BS, HT, LF, VT, FF, CR, SO, SI, DLE, DC1, DC2, DC3, DC4, NAK, SYN, ETB, CAN, EM, SUB, ESC, IS4, IS3, IS2, IS1
correspond to the characters with number 0 to 31, while the literal
DEL
corresponds to the character number 127.
The operators available in the Character sort are:
"<" : Character, Character -> Boolean; "<=" : Character, Character -> Boolean; ">" : Character, Character -> Boolean; ">=" : Character, Character -> Boolean; Num : Character -> Integer; Chr : Integer -> Character;
The interpretation of these operators are:
<
, <=
, >
, >=
: Num
: Num(`A') = 65
Chr
: Chr(65) = `A'
The Charstring
sort is used to represent strings or sequences of characters. There is no limit for the length of a Charstring value. Charstring literals are written as a sequence of characters enclosed between two single quotes: `. If the Charstring should contain a quote (`) it must be written twice.
`abcdef 0123' `$%@^&' `1''2''3' /* denotes 1'2'3 */ `' /* empty Charstring */
Note the somewhat strange rule in SDL that several consecutive spaces are treated as one space. This means that the two literals below are identical:
`aaa aaa aaa' = `aaa aaa aaa'
The following operators are available for Charstrings:
MkString : Character -> Charstring; Length : Charstring -> Integer; First : Charstring -> Character; Last : Charstring -> Character; "//" : Charstring, Charstring -> Charstring; SubString : Charstring, Integer, Integer -> Charstring;
These operators are defined as follows:
MkString
: c
is a variable of type Character, then MkString(c)
is a Charstring containing character c
.Length
: Length (`hello') = 5
First
: First (`hello') = `h'
Last
: Last (`hello') = `o'
//
(concatenation) : `he' // `llo' = `hello'
.Substring
: Substring (`hello', 3, 2) = `ll'
It is also possible to access Character elements in a Charstring by indexing a Charstring variable. Assume that C
is a Charstring variable. Then it is possible to write:
task C(2) := C(3);
This would mean that Character number 2 is assigned the value of Character number 3 in the variable C
.
Note: The first Character in a Charstring has index 1. |
These Z.105 specific character string types are all syntypes of Charstring with restrictions on the allowed Characters that may be contained in a value. These sorts are mainly used as a counterpart of the ASN.1 types with the same names. The restrictions are:
IA5String
: NumericString
: `0':`9'
and ` `
PrintableString
and VisibleString
: `A':`Z' `a':`z' `0':`9' `''' `(` `)' `+' `,' `-' `.' `/' `:' `=' `?'
It is recommended to use these types only in relation with ASN.1 or TTCN. In other cases use Charstring.
The Time
and Duration
sorts have their major application area in connection with timers. The first parameter in a Set
statement is the time when the timer should expire. This value should be of sort Time.
Both Time and Duration have literals with the same syntax as real values. Example:
245.72 0.0032 43
The following operators are available in the Duration sort:
"+" : Duration, Duration -> Duration; "-" : Duration -> Duration; "-" : Duration, Duration -> Duration; "*" : Duration, Real -> Duration; "*" : Real, Duration -> Duration; "/" : Duration, Real -> Duration; ">" : Duration, Duration -> Boolean; "<" : Duration, Duration -> Boolean; ">=" : Duration, Duration -> Boolean; "<=" : Duration, Duration -> Boolean;
The following operators are available in the Time sort:
"+" : Time, Duration -> Time; "+" : Duration, Time -> Time; "-" : Time, Duration -> Time; "-" : Time, Time -> Duration; "<" : Time, Time -> Boolean; "<=" : Time, Time -> Boolean; ">" : Time, Time -> Boolean; ">=" : Time, Time -> Boolean;
The interpretation of these operators are rather straightforward, as they correspond directly to the ordinary mathematical operators for real numbers. There is one "operator" in SDL that returns a Time value; Now
which returns the current global system time.
Time should be used to denote `a point in time', while Duration should be used to denote a `time interval'. SDL does not specify what the unit of time is. In SDT, the time unit is usually 1 second.
SET (now + 2.5, MyTimer)
After the above statement, SDL timer MyTimer
will expire after 2.5 time units (usually seconds) from now.
You should note that according to SDL, Time and Duration (and Real) possess the true mathematical properties of real numbers. In an implementation, however, there are of course limits on the range and precision of these values.
The Integer
sort in SDL is used to represent the mathematical integers. Natural
is a syntype of Integer, allowing only integers greater than or equal to zero.
Integer literals are defined using the ordinary integer syntax. Example:
0 5 173 1000000
Negative integers are obtained by using the unary - operator given below. The following operators are defined in the Integer sort:
"-" : Integer -> Integer; "+" : Integer, Integer -> Integer; "-" : Integer, Integer -> Integer; "*" : Integer, Integer -> Integer; "/" : Integer, Integer -> Integer; "mod" : Integer, Integer -> Integer; "rem" : Integer, Integer -> Integer; "<" : Integer, Integer -> Boolean; ">" : Integer, Integer -> Boolean; "<=" : Integer, Integer -> Boolean; ">=" : Integer, Integer -> Boolean; Float : Integer -> Real; Fix : Real -> Integer;
The interpretation of these operators are given below:
-
(unary, i.e. one parameter) : +
, -
, *
: /
: 10/5 = 2, 14/5 = 2, -8/5 = -1
mod
, rem
: Mod
always returns a positive value, while rem
may return negative values, e.g.14 mod 5 = 4, 14 rem 5 = 4, -14 mod 5 = 1, -14 rem 5 = -4
<
, <=
, >
, >=
: Float
: Float (3) = 3.0
Fix
: Fix(3.65) = 3, Fix(-3.65) = -3
Null
is a sort coming from ASN.1, defined in Z.105. Null does occur rather frequently in older protocols specified with ASN.1. ASN.1 has later been extended with better alternatives, so Null should normally not be used. The sort Null only contains one value, Null.
The Z.105-specific sort Object_Identifier
also comes from ASN.1. Object identifiers usually identify some globally well-known definition, for example a protocol, or an encoding algorithm. Object identifiers are often used in open-ended applications, for example in a protocol where one party could say to the other `I support protocol version X'. `Protocol version X' could be identified by means of an object identifier.
An Object_Identifier value is a sequence of Natural values. This sort contains one literal, EmptyString
, that is used to represent an Object_Identifier with length 0. The operators defined in this sort are:
MkString : Natural -> Object_Identifier Length : Object_Identifier -> Integer First : Object_Identifier -> Natural Last : Object_Identifier -> Natural "//" : Object_Identifier, Object_Identifier -> Object_Identifier SubString : Object_Identifier, Integer, Integer -> Object_Identifier
These operators are defined as follows:
MkString
: MkString (8)
gives an Object_Identifier consisting of one element, i.e. 8.Length
: Length (MkString (8)//MkString(6)) = 2
Length (EmptyString) = 0
First
: First (MkString (8)//MkString(6)) = 8
Last
: Last (MkString (8)//MkString(6)) = 6
//
(concatenation) : MkString (8) // MkString (6)
gives an Object_Identifier of two elements, 8 followed by 6.Substring
: Substring(MkString(8)//MkString(6),2,1) =MkString(6)
It is also possible to access the Natural elements in an Object_Identifier by indexing an Object_Identifier variable. Assume that C
is a Object_Identifier variable. Then it is possible to write:
task C(2) := C(3);
This would mean that the Natural at index 2 is assigned the value of the Natural at index 3 in the variable C
. Note that the first Natural in an Object_Identifier has index 1. It is an error to index an Object_Identifier outside of its length.
The Z.105-specific sort Octet
is used to represent eight-bit values, i.e. values between 0 and 255. In C this would correspond to unsigned char. There are no explicit literals for the Octet sort. Values can, however, easily be constructed using the conversion operators I2O
and O2I
discussed below.
The following operators are defined in Octet:
"NOT" : Octet -> Octet; "AND" : Octet, Octet -> Octet; "OR" : Octet, Octet -> Octet; "XOR" : Octet, Octet -> Octet; "=>" : Octet, Octet -> Octet; "<" : Octet, Octet -> Boolean; "<=" : Octet, Octet -> Boolean; ">" : Octet, Octet -> Boolean; ">=" : Octet, Octet -> Boolean; ShiftL : Octet, Integer -> Octet; ShiftR : Octet, Integer -> Octet; "+" : Octet, Octet -> Octet; "-" : Octet, Octet -> Octet; "*" : Octet, Octet -> Octet; "/" : Octet, Octet -> Octet; "mod" : Octet, Octet -> Octet; "rem" : Octet, Octet -> Octet; I2O : Integer -> Octet; O2I : Octet -> Integer; BitStr : Charstring -> Octet; HexStr : Charstring -> Octet;
The interpretation of these operators is as follows:
NOT
, AND
, OR
, XOR
, =>
: NOT BitStr (`00110101') = BitStr (`11001010')
<
, <=
, >
, >=
: ShiftL
, ShiftR
: ShiftL(a,b)
is defined as a<<b
in C.ShiftL (BitStr(`1'), 4) = BitStr(`10000')
ShiftR (BitStr(`1010'), 2) = BitStr (`10')
+
, -
, *
, /
, mod
, rem
: I2O(250) + I2O(10) = I2O(4), O2I(I2O(4)-I2O(6)) = 254
I2O
: I2O (128) = HexStr (`80')
O2I
: O2I (HexStr (`80')) = 128
BitStr
: BitStr(`00000011') = I2O(3)
HexStr
: HexStr(`01') = I2O(1), HexStr(`ff') = I2O(255)
It is also possible to access the individual bits in an Octet value by indexing an Octet variable. The index should be in the range 0 to 7.
Octet should be used to represent bytes. Octet replaces the SDT supplied Byte ADT.
The Z.105-specific sort Octet_String
represents a sequence of Octet
values. There is no limit on the length of the sequence. The operators defined in the Octet_String sort are:
MkString : Octet -> Octet_String; Length : Octet_String -> Integer; First : Octet_String -> Octet; Last : Octet_String -> Octet; "//" : Octet_String, Octet_String -> Octet_String; SubString : Octet_String, Integer, Integer -> Octet_String; BitStr : Charstring -> Octet_String; HexStr : Charstring -> Octet_String; Bit_String : Octet_String -> Bit_String; Octet_String : Bit_String -> Octet_String;
These operators are defined as follows:
MkString
: MkString (I2O(10))
gives an Octet_String containing one element.Length
: Length (I2O (8)//I2O (6)) = 2
Length ( HexStr (`0f3d88')) = 3
Length ( BitStr (`')) = 0
First
:First ( HexStr (`0f3d88')) = HexStr(`0f') (= I2O(15))
Last
: Last ( HexStr (`0f3d88')) = HexStr(`88') (= I2O(136))
//
(concatenation) : HexStr(`0f3d')//HexStr(`884F') = HexStr(`0f3d884f')
Substring
: Substring(HexStr(`0f3d889C'), 3, 2) = HexStr(`889c')
BitStr
: BitStr (`101') = BitStr (`10100000')
HexStr
: HexStr (`f') = HexStr (`f0')
Bit_String
and Octet_String
: It is also possible to access the Octet elements in an Octet_String by indexing an Octet_String variable. Assume that C is an Octet_String variable. Then it is possible to write:
task C(2) := C(3);
This would mean that the Octet at index 2 is assigned the value of Octet at index 3 in the variable C. It is an error to index an Octet_String outside of its length.
Note: The first Octet in an Octet_String has index 0, whereas most other string types in SDL start with index 1! |
The sort PId
is used as a reference to process instances. PId has only one literal, Null
. All other values are obtained from the SDL predefined variables Self
, Sender
, Parent
, and Offspring
.
Real
is used to represent the mathematical real values. In an implementation there are of course always restrictions in size and precision of such values. Examples of Real literals:
2.354 0.9834 23 1000023.001
The operators defined in the Real sort are:
"-" : Real -> Real; "+" : Real, Real -> Real; "-" : Real, Real -> Real; "*" : Real, Real -> Real; "/" : Real, Real -> Real; "<" : Real, Real -> Boolean; ">" : Real, Real -> Boolean; "<=" : Real, Real -> Boolean; ">=" : Real, Real -> Boolean;
All these operators have their ordinary mathematical meaning.
All the predefined sorts and syntypes discussed in the previous section can be directly used in, for example, variable declarations. In many circumstances it is however suitable to introduce new sorts and syntypes into a system to describe certain properties of the system. A user-defined sort or syntype can be used in the unit where it is defined, and also in all its subunits.
A syntype definition introduces a new type name which is fully compatible with the base type. This means that a variable of the syntype may be used in any position where a variable of the base type may be used. The only difference is the range check in the syntype.
Syntypes are useful for:
syntype smallint = integer constants 0:10 endsyntype;
In this example smallint
is the new type name, integer
is the base type, and 0:10
is the range condition. Range conditions can be more complex than the one above. It may consist of a list of conditions, where each condition can be (assume X
to be a suitable value):
=X
a single value X
is allowedX
same as =X
/=X
all values except X
are allowed>X
all values >X
are allowed>=X
all values >=X
are allowed<X
all values <X
are allowed<=X
all values <=X
are allowedX:Y
all values >=X
and <=Y
are allowedsyntype strangeint = integer constants <-5, 0:3, 5, 8, >=13 endsyntype;
In this example all values <-5, 0, 1, 2, 3, 5, 8, >=13
are allowed.
The range check introduced in a syntype is tested in the following cases (assuming that the variable, signal parameter, formal parameter involved is defined as a syntype):
IN
parameter in a procedure callAn enumeration sort is a sort containing only the values enumerated in the sort. If some property of the system can take a relatively small number of distinct values and each value has a name, an enumeration sort is probably suitable to describe this property. Assume for example a key with three positions; off, stand-by, and service-mode. A suitable sort to describe this would be:
newtype KeyPosition literals Off, Stand_by, Service_mode endnewtype;
A variable of sort KeyPosition
can take any of the three values in the literal list, but no other.
The struct concept in SDL can be used to make an aggregate of data that belong together. Similar features can be found in most programming languages. In C, for example, it is also called struct, while in Pascal it is the record concept that has these properties. If, for example, we would like to describe a person and would like to give him a number of properties or attributes, such as name, address, and phone number, we can write:
newtype Person struct Name Charstring; Address Charstring; PhoneNumber Charstring; endnewtype;
A struct contains a number of components, each with a name and a type. If we now define variables of this struct type,
dcl p1, p2 Person;
it is possible to work directly with complete struct values, like in assignments, or in tests for equality. Also individual components in the struct variable can be selected or changed.
task p1 := (. `Peter', `Main Road, Smalltown', `+46 40 174700' .); task BoolVar := p1 = p2; task p2 ! Name := `John'; task CharstringVar := p2 ! Name;
The first task is an assignment on the struct level. The right hand side, i.e. the (. .) expression, is an application of the implicit Make operator, that is present in all structs. The Make operator takes a value of the first component sort, followed by a value of the second component sort, and so on, and returns a struct value where the components are given the corresponding values. In the example above, the component Name
in variable p1
is given the value `Peter'
. The second task shows a test for equality between two struct expressions. The third and fourth task shows how to access a component in a struct. A component is selected by writing:
VariableName ! ComponentName
Such component selection can be performed both in a expression (then usually called Extract) and in the left hand side of an assignment (then usually called Modify).
A bit field defines the size in bits for a struct component. This feature is not part of the SDL Recommendation, but rather introduced by Telelogic to enable the generation of C bit fields from SDL. This means that the syntax and semantics of bit fields follow the C counterpart very much.
newtype example struct a Integer : 4; b UnsignedInt : 2; c UnsignedInt : 1; : 0; d Integer : 4; e Integer; endnewtype;
The following rules apply to bit fields:
: X
(where X
is an integer number) is the same as in C. When generating C code from SDL, the : X
is just copied to the C struct that is generated from the SDL struct.: 0
in SDL is translated to int : 0
in C.int
and unsigned int
for bit field components the same rule is valid in SDL: only Integer
and UnsignedInt
(from package ctypes
) may be used.Bit fields should only be used when it is necessary to generate C bit fields from SDL. Bit fields should not be used as an alternative to syntypes with a constants clause; SDT does not check violations to the size of the bit fields.
To simplify the translation of ASN.1 types to SDL sorts, two new features have been introduced into structs. Struct components can be optional and they can have default values. Note that these features have their major application area in connection with ASN.1 data types and applying them in other situations is probably not a good idea, as they are not standard SDL-96.
newtype example struct a Integer optional; b Charstring; c Boolean := true; d Integer := 4; e Integer optional; endnewtype;
The default values for component c
and d
, means that these components are initialized to the given values.
An optional component may or may not be present in a struct value. Initially an optional component is not present. It becomes present when it is assigned a value. It is an error to access a component that is not present. It is possible to test if an optional component is present or not by calling an implicit operator called
ComponentNamePresent
In the example above aPresent(v)
and ePresent(v)
can be used to test whether components a
and e
are present or not in the value stored in variable v
.
Components with default values also have Present
operators. They however always return True
! This somewhat strange semantics is according to the definition in Z105.
The new concept choice is introduced into SDL as a means to represent the ASN.1 concept CHOICE. This concept can also be very useful while developing pure SDL data types. The choice in SDL can be seen as a C union with an implicit tag field.
newtype C1 choice a Integer; b Charstring; c Boolean; endnewtype;
The example above shows a choice with three components. The interpretation is that a variable of a choice type can only contain one of the components at a time, so in the example above a value of C1
either contains an Integer value, a Charstring value, or a Boolean value.
DCL var C1, charstr Charstring; TASK var := a : 5; /* assign component a */ TASK var!b := `hello'; /* assign component b (a becomes absent) */ TASK charstr := var!b; /* get component b */
The above example shows how to modify and extract components of a choice type. In this respect, choice types are identical to struct types, except the a:5
notation to denote choice values, whereas struct values are described using (. ... .).
Extracting a component of a choice type that is not present results in a dynamic error. Therefore it is necessary to be able to determine which component is active in a particular value. For that purpose there are a number of implicit operators defined for a choice.
v!Present
where v
is a variable of a choice type, returns a value which is the name of the active component. This is made possible by introducing an implicit enumeration type with literals with the same names as the choice components. Note that this enumeration type is implicit and should not be inserted by you. Given the example above, it is allowed to test:
v!Present = b
This is illustrated in Figure 26.
Figure 26 : Check which component of a choice is present
|
It is also possible to test if a certain component is active or not, by using the implicit boolean operators ComponentNamePresent
. To check if component b
in the example above is present it is thus possible to write:
bPresent(v)
The information about which component that is active can be accessed using the Present
operators, but it is not possible to change it. This information is automatically updated when a component in a choice variable is assigned a value.
The purpose of choice is to save memory or bandwidth. As it is known that only one component at a time can contain a value, the compiler can use overlay techniques to reduce the total memory for the type. Also sending a choice value over a physical connection saves time, compared to sending a corresponding struct.
The choice construct is Telelogic-specific, and not part of recommendation Z.105, so if you want to write portable SDL, you should not use choice. Choice replaces the SDT #UNION code generator directive. It is recommended to replace #UNION directives by choice, as SDT has better tool support for the latter.
It is possible to create a new sort by inheriting information from another sort. It is possible to specify which operators and literals that should be inherited and it is then possible to add new operators and literals in the new type.
Note that it is not really possible to change the type in itself by using inheritance. It is, for example, not possible to add a new component to a struct when the struct is inherited.
Our experience with inheritance so far has been that it is not as useful as it might seem in the beginning, and that sometimes the use of inheritance leads to the need of qualifiers in a lot of places, as many expressions are no longer semantically valid.
newtype NewInteger inherits Integer operators all; endnewtype;
In the example above a new type NewInteger
is introduced. This type is distinct from Integer, i.e. an Integer expression or variable is not allowed where a NewInteger
is expected, and a NewInteger
expression or variable is not allowed where an Integer is expected. Since in the example all literals and operators are inherited, all the integer literals 0, 1, 2, ..., are also available as NewInteger
literals. For operators it means that all operators having Integer as parameter or result type are copied, with the Integer parameter replaced with a NewInteger
parameter. This is true for all operators, not only those defined in the Integer sort, which may give unexpected effects, which will be illustrated below.
The following operators are some of the operators having Integer as parameter or result type:
"+" : Integer, Integer -> Integer; "-" : Integer -> Integer; "mod" : Integer, Integer -> Integer; Length : Charstring -> Integer;
The type NewInteger
defined above will inherit these and all the others having integer as parameter or result type. Note that Length is defined in the Charstring sort.
"+" : NewInteger, NewInteger -> NewInteger; "-" : NewInteger -> NewInteger; "mod" : NewInteger, NewInteger -> NewInteger; Length : Charstring -> NewInteger;
With this NewInteger
declaration, statements like
decision Length(Charstring_Var) > 5;
are no longer correct in the SDL system. It is no longer possible to determine the types in the expression above. It can either be the Length returning integer that is tested against an integer literal, or the Length returning a NewInteger
value that is tested against a NewInteger
literal.
It is possible to avoid this kind of problem by specifying explicitly the operators that should be inherited.
newtype NewInteger inherits Integer operators ("+", "-", "*", "/") endnewtype;
Now only the enumerated operators are inherited and the problem with Length
that was discussed above will not occur.
The predefined generator Array
takes two generator parameters, an index sort and a component sort. There are no restrictions in SDL on the index and component sort.
newtype A1 Array(Character, Integer) endnewtype;
The example above shows an instantiation of the Array generator with Character as index sort and Integer as component sort. This means that we now have created a data structure that contains one Integer value for each possible Character value. To obtain the component value connected to a certain index value it is possible to index the array.
dcl Var_A1 A1; /* Assume sort in example above */ task Var_A1 := (. 3 .); task Var_Integer := Var_A1(`a'); task Var_A1(`x') := 11; decision Var_A1 = (. 11 .); (true) : ... ... enddecision;
The example above shows how to work with arrays. First we have the expression (. 3 .)
. This is an application of the Make! operator defined in all array instantiations. The purpose is to return an array value with all components set to the value specified in Make. The first task above thus assigns the value 3 to all array components. Note that this is an assignment of a complete array value.
In the second task the value of the array component at index `a
' is extracted and assigned to the integer variable Var_Integer
. In the third task the value of the array component at index `x
' is modified and given the new value 11. The second and third task shows applications of the operators Extract! and Modify! which are present in all array instantiations. Note that the operators Extract!, Modify!, and Make! can only be used in the way shown in the example above. It is not allowed to directly use the name of these operators.
In the last statement, the decision, an equal test for two array values is performed. Equal and not equal are, as well as assignment, defined for all sorts in SDL.
The typical usage of arrays is to define a fixed number of elements of the same sort. Often a syntype of Integer is used for the index sort, as in the following example, where an array of 11 PIds is defined with indices 0 to 10.
syntype indexsort = Integer constants 0:10 endsyntype; newtype PIdArray array (indexsort, PId) endnewtype;
Unlike most ordinary programming languages, there are no restrictions on the index sort in SDL. In most programming languages the index type must define a finite range of values possible to enumerate. In C, for example, the size of an array is specified as an integer constant, and the indices in the array range from 0 to the (size-1). In SDL, however, there are no such limits.
newtype RealArr array (Real, Real) endnewtype;
Having Real as index type means that there is an infinite number of elements in the array above. It has, however, the same properties as all other arrays discussed above. This kind of more advanced arrays sometimes can be a very powerful concept that can be used for implementing, for example, a mapping table between different entities.
newtype CharstringToPId array (Charstring, PId) endnewtype;
The above type can be used to map a Charstring representing a name to a PId value representing the corresponding process instance.
The String
generator takes two generator parameters, the component sort and the name of an empty string value. A value of a String type is a sequence of component sort values. There is no restriction on the length of the sequence. The predefined sort Charstring, for example, is defined as an application of the String generator.
newtype S1 String(Integer, Empty) endnewtype;
Above, a String with Integer components is defined. An empty string, i.e. a string with the length zero, is represented by the literal Empty
.
The following operators are available in instantiations of String.
MkString : Itemsort -> String Length : String -> Integer First : String -> Itemsort Last : String -> Itemsort "//" : String, String -> String SubString : String, Integer, Integer -> String
In this enumeration of operators, String should be replaced by the string newtype (S1
in the example above) and Itemsort should be replaced by the component sort parameter (Integer in the example above). The operators have the following behavior, with the examples based on type String (Integer, Empty
):
MkString
: MkString (-3)
gives a string of one integer with value -3.Length
: Length (Empty) = 0, Length(MkString (2)) = 1
First
: First (MkString (8) // MkString (2)) = 8
Last
: Last (MkString (8) // MkString (2)) = 2
//
(concatenation) : MkString (8) // MkString(2)
gives a string of two elements: 8 followed by 2.Substring
: Substring (MkString (8) // MkString(2), 2, 1)
= MkString(2)
It is also possible to access Itemsort elements in a String by indexing a String variable. Assume that C is a String instantiation variable. Then it is possible to write:
task C(2) := C(3);
This would mean that Itemsort element number 2 is assigned the value of Itemsort element number 3 in the variable C. NOTE that the first element in a String has index 1. It is an error to index a String outside of its length.
The String generator can be used to build lists of items of the same type, although some typical list operations are computationally quite expensive, like inserting a new element in the middle of the list.
The Powerset
generator takes one generator parameter, the item sort, and implements a powerset over that sort. A Powerset value can be seen as: for each possible value of the item sort it indicates whether that value is member of the Powerset or not.
Powersets can often be used as an abstraction of other, more simple data types. A 32-bit word seen as a bit pattern can be modeled as a Powerset over a syntype of Integer with the range 0:31. If, for example, 7 is member of the powerset this means that bit number 7 is set.
syntype SmallInteger = Integer constants 0:31 endsyntype; newtype P1 Powerset(SmallInteger) endnewtype;
The only literal for a powerset sort is Empty
, which represents a powerset containing no elements. The following operators are available for a powerset sort (replace Powerset
with the name of the newtype, P1
in the example above, and ItemSort
with the ItemSort parameter, SmallInteger
in the example):
"IN" : Itemsort, Powerset -> Boolean Incl : Itemsort, Powerset -> Powerset Del : Itemsort, Powerset -> Powerset Length : Powerset -> Integer Take : Powerset -> Itemsort Take : Powerset, Integer -> Itemsort "<" : Powerset, Powerset -> Boolean ">" : Powerset, Powerset -> Boolean "<=" : Powerset, Powerset -> Boolean ">=" : Powerset, Powerset -> Boolean "AND" : Powerset, Powerset -> Powerset "OR" : Powerset, Powerset -> Powerset
These operators have the following interpretation (the examples are based on newtype P1
of the above example, and it is supposed that variable v0_1_2
of P1
contains elements 0, 1, and 2):
IN
: 3 IN Incl (3, Empty)
gives True
;3 IN v0_1_2
gives False
, 0 IN v0_1_2
gives True
.Incl
: Incl (3, empty)
gives a set with one element, 3,Incl (3, v0_1_2)
gives a set with elements, 0, 1, 2, and 3.Del
: Del (0, v0_1_2)
gives a set with element 1 and 2; Del (30, v0_1_2) = v0_1_2
Length
: Length (v0_1_2) = 3, Length (Empty) = 0
Take
(one parameter) : Take (v0_1_2)
gives 0, 1, or 2 (unspecified which of these three)Take
(two parameters) :
Figure 27 : Computing the sum of all elements in a Powerset
|
<
: Incl (2, empty) < v0_1_2 = True,
Incl (30, empty) < v0_1_2 = False
>
: <=
: >=
: AND
: Incl (2, Incl (4, empty)) AND v0_1_2
gives a set with one element, visually 2.OR
: Incl (2, Incl (4, empty)) OR v0_1_2
gives a set with elements, 0, 1, 2, and 4.Powerset resembles the Bag operator, and normally it is better to use Powerset. See also the discussion in Bag.
The Z.105-specific generator Bag
is almost the same as Powerset. The only difference is that a bag can contain the same value several times. In a Powerset a certain value is either member or not member of the set. A Bag instantiation contains the literal Empty
and the same operators, with the same behavior, as a Powerset instantiation. For details please see Powerset.
A Bag contains one additional operator:
Makebag : Itemsort -> Bag
Makebag
: It is recommended to use Powerset instead of Bag, except in cases where the number of instances of a value is important. Powerset is defined in Z.100, and is therefore more portable. Bag is mainly part of the predefined data types in order to support the ASN.1 SET OF
construct.
Literals, i.e. named values, can be included in newtypes.
newtype Coordinates struct x integer; y integer; adding literals Origo, One; endnewtype;
In this struct there are two named values (literals); Origo
and One
. The only way in SDL to specify the values these literals represent is to use axioms. Axioms can be given in a section in a newtype. This is not further discussed here. The SDT code generators provide other ways to insert the values of the literals. Please see the documentation in The Cadvanced/Cbasic Code Generator.
The literals can be used in SDL actions in the same way as expressions.
dcl C1 Coordinates; task C1 := Origo; decision C1 /= One; ...
Operators can be added to a newtype in the same way as literals.
newtype Coordinates struct x integer; y integer; adding operators "+" : Coordinates, Coordinates -> Coordinates; Length : Coordinates -> Real; endnewtype;
The behavior of operators can either be defined in axioms (as the literal values) or in operator diagrams. An operator diagram is almost identical to a value returning procedure (without states). In the SDT Code Generators there is also the possibility to include implementations in the target language.
In a newtype or syntype it is possible to insert a default clause stating the default value to be given to all variables of this type.
newtype Coordinates struct x integer; y integer; default (. 0, 0 .); endnewtype;
All variables of sort Coordinates
will be given the initial value
, except if an explicit default value is given for the variable in the variable declaration.
(. 0, 0 .)
dcl C1 Coordinates := (. 1, 1 .), C2 Coordinates;
Here C1
has an explicit default value that is assigned at start-up. C2
will have the default value specified in the newtype.
It is possible in SDL to define generators with the same kind of properties as the pre-defined generators Array, String, Powerset, and Bag. As this is a difficult task and the support from the SDT Code Generators is limited (for C) or non-existing (in CHILL), it is not recommended for a non-specialist to try to define a generator.
The possibility to use user defined generators in the C Code Generator is described in more detail in Generators.
SDT offers support for integrating C code with SDL. This makes it possible to re-use existing software, written in C, in SDL, which enables a smoother transition from C to SDL.
The following mechanisms in SDT enable integration between SDL and C code:
This section describes how C data types and functions are represented in SDL. This is explained by means of tables that represent a mapping from C data types to SDL and back. The tables can be regarded as a two-way mapping:
The table below lists the C types for which a direct equivalent in SDL is available, either as a predefined SDL sort, or as a sort in the SDL package ctypes
(see C Specific Package ctypes).
The C types long long int
and unsigned long long int
are mapped to the SDL sorts LongLongInt
and UnsignedLongLongInt
respectively. However, these double-word integral types are extensions to the ANSI C standard and are therefore not supported by all C compilers. For this reason the corresponding SDL sorts are not present in the ctypes
package. If you wish to use the long long types you may write these SDL sort definitions yourself (they are very similar to LongInt
and UnsignedLongInt
) or retrieve them from Telelogic Support.
For other C types there is no directly corresponding SDL sort available, and a syntype or newtype must be constructed. The following table gives a basic mapping of how other C types can be mapped to SDL syntypes or newtypes. In the below table, Ref
and CArray
are generators that are defined in package ctypes
, see C Specific Package ctypes for a description of these generators.
C function prototypes are mapped to external procedures in SDL. External procedures are a new feature in SDL-96. An external procedure is called in the same way as a normal procedure. The table below shows the basic mapping scheme of function prototypes to SDL.
Pointer parameters of functions can be handled in two different ways. As an example we take the char *
parameter in the function declaration
void fn (char *par1);
The two ways to translate this are:
procedure fn; fpar in/out par1 Character; external;The function is called with a non-pointer parameter.
call fn (c); /* c variable of type Character */
procedure fn; fpar par1 CharStar; external;The function is called with a pointer parameter.
call fn (&c); /* c variable of type Character */ call fn (cst); /* cst variable of type CharStar */
In the above example, &c
denotes the address of variable c
. With in/out parameters, only variables are allowed as argument. Therefore, the second way is a bit more general. H2SDL uses the second alternative.
Constants in C are often expressed using macros. They can be mapped to `normal' synonyms or to external synonyms. As an example we consider:
#define MAX 1000
In normal cases, this can be mapped to a normal synonym:
synonym MAX Integer = 1000;
If the above C constant has been defined in C code that is included in the generated SDL code, it should be mapped to an external synonym:
synonym MAX Integer = external;
The same mappings are valid for C constants defined with const. For example the C constant below is from an SDL point of view the same as the macro above:
const int MAX = 1000;
H2SDL cannot handle macros, but it can handle const variables, and will map these to external synonyms. The C Code Generator maps synonyms to C macros, while no C code at all is generated for external synonyms.
Also note that it is possible to access C macro values from SDL using the implicit #CODE operator. See Constant Variable Declarations.
If we have a C header, and we mapped C types to corresponding SDL sorts according to the above mapping rules, how do we know that the C Code Generator will generate the right C code back? This can be accomplished by putting the SDL sorts in a package that is provided with code generator directive #C `filename.h'
. The C code generator will handle such a package in the following way:
#include `filename.h'
will be generated). filename.h
(exceptions to this may occur as a result of special code generator directives being used). However, the code generator will generate other code for the package, like read/write functions (used in the simulator/validator), `=
' operators, etc./* myheader.h */ typedef struct { int a:4; int b:3; } s; const int MAX = 1000; void fn (char *p1);
With the mapping described in this section, we would get the following package:
use ctypes; package mypackage; /*#C 'myheader.h' */ newtype s struct a Integer:4; b Integer:3; endnewtype s; synonym MAX Integer = external; procedure fn; fpar p1 CharStar; external; endpackage mypackage;
The C code generated for this package will use the original definitions in myheader.h
. By using package mypackage
, an SDL system has access to s
, MAX
, and fn
.
Telelogic offers a special package ctypes
that contains data types and generators that match C. It is described in detail in The ADT Library. The ctypes
package should be used in the following cases:
ctypes
must be used.The tables below list the data types and generators in ctypes
and their C counterparts.
SDL Generator | Corresponding C Declarator |
---|---|
CArray |
C array, i.e. [] |
Ref |
C pointer, i.e. * |
The rest of this section explains how these data types and generators can be used in SDL.
ShortInt
, LongInt
, UnsignedShortInt
, UnsignedInt
, UnsignedLongInt
are all defined as syntypes of Integer, so from an SDL point of view, these data types are really the same, and the normal Integer operators can be used on these types. The only difference is that the code that is generated for these types is different. Float
is defined as a syntype of Real.
CharStar
represents character strings (i.e. char *
) in C. CharStar is not the same as the SDL predefined type Charstring! CharStar is useful when accessing C functions and data types that use char *
. In other cases it is better to use Charstring instead (see also Charstring). Conversion operators between CharStar and Charstring are available (see below).
VoidStar
corresponds to void *
in C. This type should only be used when accessing C functions that have void *
parameters, or that return void *
(in which case it is advised to `cast' the result directly to another type).
VoidStarStar
corresponds to void **
in C. This type is used in combination with the Free
procedure described in Using Pointers in SDL. In rare cases this type is also needed to access C functions.
The following conversion operators in ctypes
are useful:
CStar2CString : CharStar -> CharString; CString2CStar : CharString -> CharStar; CStar2VStar : CharStar -> VoidStar; VStar2CStar : VoidStar -> CharStar;
These operators have the following behavior:
CStar2CString
: v
of type CharStar contains the C string "hello world"
, then CStar2CString(v) = `hello world'
.CString2CStar
: CStar2CString
.CStar2VStar
: void *
parameters.VStar2CStar
: void *
, but the result should be `casted' to a char *
.The generator CArray
in package ctypes
is useful to define arrays that have the same properties as C arrays. CArray takes two generator parameters; an integer value and a component sort.
newtype IntArr CArray(10, Integer) endnewtype;
The defined type IntArr
is an array of 10 integers with indices 0 to 9, corresponding to the C type
typedef int IntArr[10];
Two operators are available on instantiations of CArray; Modify! to change one element of the array, and Extract! to get the value of one element in the array. These operators are used in the same way as in normal SDL arrays, see Array. There is no (. ... .) notation provided for denoting values of whole CArrays.
Modify! : CArray, Integer, Itemsort -> CArray; Extract! : CArray, Integer -> Itemsort;
DCL v IntArr, i Integer; TASK v(0) := 3; /* modifies one element */ TASK i := v(9); /* extracts one element */
If a C array is used as parameter of an operator, it will be passed by address, just as in C. This makes it possible to write operators that change the contents of the actual parameters. In standard SDL this would not be possible.
The generator Ref
in package ctypes
is used to define pointer types. The following example illustrates how to use this generator.
newtype ptr Ref(Integer) endnewtype;
The sort ptr
is a pointer to Integer.
Standard SDL has no pointer types. Pointers have properties that cannot be defined in normal SDL. Therefore they should be used very carefully. Before explaining how to use the Ref generator, it is worthwhile to list some of the dangers of using pointers in SDL.
If more than one process can read/write to the same memory location by means of pointers, data inconsistency can and will occur! Some examples:
Even though tools such as the Simulator and Validator will be able to detect a number of errors regarding pointers, there are situations that cannot be detected with these tools! This is because the Validator and Simulator assume a scheduling atomicity of at best one SDL symbol at a time. This may not hold in target operating systems where one process can be interrupted at any time (pre-emptive scheduling). If pointers are used, data is totally unprotected, and data inconsistency may occur, even though the Validator did not discover any problems! All these problems can be avoided by using SDL constructs for accessing data, like remote procedures and signal exchange.
For the above stated reasons, NEVER PASS POINTERS TO ANOTHER PROCESS!!! Not in an output, not in a remote procedure call, not in a create, not by exported/revealed variables, never! |
And if you do not obey this rule anyway: after passing a pointer, release immediately the `old' pointer to prevent having several pointers to the same data area. For example (for some pointer p
):
OUTPUT Sig(p) TO ...; TASK p := Null;
If you have an SDL system that always works except during demonstrations, then you have used pointers! Bugs with pointers may be very hard to discover, as a system may (accidentally) behave correctly for a long time, but then suddenly strange things may happen. Finding such bugs may take very long time; in rare cases you might not find them at all!
BUGS CAUSED BY POINTERS MAY BE HARD TO FIND!!! |
If an SDL system is `really' distributed, i.e. where processes have their own memory space, it makes no sense to send a pointer to another process, as the receiving process will not be able to do anything with it. Therefore, by communicating pointers to other processes, limitations are posed on the architecture of the target implementation.
The Ref generator and its operators are completely Telelogic-specific. It is highly unlikely that SDL systems using pointers will run on other SDL tools.
If you still want to use pointers in SDL after all these warnings, this section explains how to do this. A pointer type created by the Ref generator always has a literal value null
(corresponds to NULL
in C), which is also the default value. The literal Alloc
is used for the dynamic creation of new memory. Examples are given later.
Note: It is up to the user to keep track of all dynamically allocated data areas and to free them when they are no longer needed. |
The following operators are available for Ref types:
"*>" : Ref, ItemSort -> Ref; "*>" : Ref -> ItemSort; "&" : ItemSort -> Ref; "+" : Ref, Integer -> Ref; "-" : Ref, Integer -> Ref; VStar2Ref : VoidStar -> Ref; Ref2VStar : Ref -> VoidStar; Ref2VStarStar : Ref -> VoidStarStar;
Furthermore, the following procedure is defined:
procedure Free; fpar p VoidStarStar; external;
These operators can be used in the following way:
*>
(postfix operator): p*>
returns the contents of pointer p.&
(prefix operator): &var
returns a pointer to variable var
.+
, -
: p
is a pointer to some struct, then p+1
points to the next struct (not to byte p+1
).VStar2Ref
: void *
.Ref2VStar
: void *
parameters.Ref2VStarStar
: void **
. This operator is needed when calling the Free
procedure.Free
: alloc
. NEWTYPE ptr Ref(Integer) ENDNEWTYPE; DCL p ptr, i, j Integer; TASK p := alloc; /* creates dynamically a new integer; p points at it */ /* here it should be checked that p != null */ TASK p*> := 10; /* changes contents of p */ CALL Free(Ref2VStarStar(p)); /* releases the integer again */ TASK p := &i; /* p now points to i */ TASK p*> := 5; /* changes contents of p, i.e. also i is changed! */ TASK j := p*>; /* gets contents of p (=5) */
Pointers are useful when defining linked structures like lists or trees. In this section we give an example of a linked list containing integer values. Figure 28 shows an SDL fragment with data type definitions for a linked list, and part of a transition that actually builds a linked list. A list is represented by a pointer to an Item
. Every Item
contains a pointer next
to the next item in the list. In the last item of the list, next
= Null
.
Figure 28 : Building a linked list
|
Figure 29 shows an SDL fragment where the sum of all elements in a list is computed. Note that this computation would never stop if there would be an element that points back in the list, just to illustrate how easy it is to make errors with pointers.
Figure 29 : Going through the list
|
In SDT, it is possible to have C header files as diagrams in the Organizer. These headers can then be used in an SDL system as if they were packages, thus offering a smooth way to integrate SDL and C. This section describes how to organize C headers in SDT, how to use the C data types in SDL, and how to overcome some of the restrictions imposed by SDT.
For a good understanding of how to use C headers in SDL, you should have carefully studied the section Mapping C to SDL that deals with how C types are converted to SDL.
The use of C headers will be illustrated with an example based on the AccessControl system described in Object Oriented Design Using SDL. In this chapter we will assume that the information about cards used to enter a door is maintained by an external database. There are two C headers:
access.h
declares data types related to database accessvalid.h
declares functions to get access to the database.The two headers are listed below.
/* access.h * * type definitions in C for access control system */ typedef char CodeArray[5]; typedef struct { char *CardData; CodeArray Code; } CardType; typedef struct { CardType *Cards; int NrOfCards; } CardDbType; /* valid.h * * function declarations in C for access control system */ int OpenCardDb (CardDbType *db); /* opens the card database. If the database could not be opened, 0 is returned */ int ValidateCard (char *CardId, CardDbType *db); /* checks whether CardId is present in db; returns 0 if not present */ int ValidateCode (CardType *card, CardDbType *db); /* checks that card is present in db with the correct code; returns 0 if the code is not valid */ int RegisterCardAndCode (CardType *, CardDbType *); /* registers a new card in the data base, returns 0 if not successful */
We recommend that you create a chapter in the Organizer (for example called C Headers) that contains all C headers used by the SDL system. If there are many C headers in use (which normally should be avoided, see Problems with Type Conflicts), it is a good idea to group related C headers together in Organizer Modules.
In this chapter we could add new C header diagrams and connect them to access.h
and valid.h
respectively. A difficulty in our example case is that the function declarations in valid.h
use data types defined in access.h. For example OpenCardDb
in valid.h
uses CardDbType
, but this type is defined in acce
s
s.h
.
In C this will not cause conflicts if C modules include access.h
before valid.h
. We could try to imitate this by "using in" SDL package access
before valid
, but unfortunately, packages in SDL and C headers do not behave the same way in this respect, and we would get semantic errors when analyzing package valid
.
We can solve this case by creating a new header file cardbase.h
that only consists of #include
's to access.h
and valid.h
in the right order.
/* cardbase.h * * includes all C headers in right order */ #include "access.h" #include "valid.h"
This is the header that will be used by SDL. So we can add a new diagram `C Header' and connect it (using Edit/Connect/To an existing file) to cardbase.h
.
In order to use the file in the SDL system, we can add the following use clauses to the package reference clause text symbol SDL system diagram:
use ctypes; use cardbase;
This is also illustrated in Figure 30.
Figure 30 : Using cardbase.h in SDL
|
Package ctypes
is a special package supplied with SDT that contains the SDL definitions of predefined C data types. ctypes
is described in detail in C Specific Package ctypes. Every SDL system that uses C headers must also include this package. You do not have to worry where to find this package: when analyzing the system, the Organizer will automatically detect that ctypes
is used, and connect it to the right file (if not already present). Package ctypes
will then automatically pop up in the Organizer structure.
Figure 31 below shows the Organizer view after analyzing the system. The symbol below the AccessControl system symbol is a dependency link that indicates that the SDL system depends on an external C header. Dependency links for C headers that are used were previously required by the Analyzer, but now only serve as comments and are optional.
Figure 31 : Organizer View of AccessControl with C Headers
|
Now that we have included C headers as part of the SDL system, we can use the data types and functions defined in these headers. Figure 32 below shows the graph of an SDL process that uses functions and data types from the Access Control example.
Figure 32 : Using C types and functions in SDL
|
In order to clearly understand the restrictions of using C headers with SDL, it is important to have some understanding of how this is implemented in SDT.
When an SDL system using C headers is analyzed, the C headers will be translated by the H2SDL tool. H2SDL will first call a C preprocessor to resolve all macros, #ifdef
s, etc. This also means that macros are not translated to SDL! The section When the Built-in Translation Rules Fail describes some ways to circumvent this restriction.
The resulting C code is converted to an SDL package according to translation rules described in The H2SDL Utility. This package is then included in the final SDL-PR file that is the input to the Analyzer.
Special attention is needed for generated "in-line types". As an example, consider the following function declaration:
int fn (int *);
In SDL, there is no type that corresponds directly to int *
, so H2SDL will generate a type with a dummy name that corresponds to int *
. The generated SDL package will use this type for all int *
occurrences in the same package. However, an int *
in another C header diagram would translate to another dummy type with another name. Assume for example that another C header diagram contains the following declaration:
int *fn2 (void);
The return type of fn2
would then in SDL not be considered to be the same as the parameter type of fn1
. The SDL task below would therefore cause a type conflict:
TASK x := call fn (call fn2); /* INCORRECT!!! */
In order to prevent unexpected problems with type compatibility of in-line types, it is recommended to use only one C diagram per SDL system that includes all used C headers.
But even if this recommendation is followed, problems with type conflicts may arise in some cases, as shown in Example 31 below.
Assume that an SDL system needs to use the definitions from two separate C headers H1.h
and H2.h
. Following the recommendation above we make a new C header wrapper.h
which includes H1.h
and H2.h
in the correct order. But now suppose that one of these headers contains the following declaration:
typedef struct duration{ int hours; int mins; int secs; } Duration;
H2SDL will translate this to an SDL struct with the name Duration. But a type with that name already exists in the predefined SDL package predef
which is automatically included in all SDL systems. Although it is legal to have types with the same name in different packages, we would need to use qualifiers when referring to the different Duration types in the SDL system. However, by placing a #define
in wrapper.h
we can "rename" the Duration that comes from the C header. Thereby we can distinguish between the types without bothering about qualifiers. Thus the final wrapper.h
should look something like:
#define Duration CDuration
#include "H1.h"
#include "H2.h"
Now, the C header type Duration can be referred to as CDuration while the predefined type Duration keeps its name.
The translation rules implemented in H2SDL may not always be the most suitable for a specific implementation. For example, the C type char
is mapped to the SDL sort C
haracter
, but in some cases a mapping to the SDL sort O
ctet
may be more appropriate. Another restriction is that H2SDL relies on a C preprocessor to resolve #ifdef
s, #define
s, etc. This means that C macros cannot be translated to C.
The following actions can be taken to overcome such restrictions:
#ifdef __H2SDL__
(for example for C "function"-macros).Each of these possibilities is discussed in the sections below.
The best way to change the default behavior of H2SDL is to use its options. These options are described in Command-Line Options and in Setting Options for the Preprocessor and H2SDL. Unfortunately there are currently only a few options available for controlling the translation rules used by H2SDL.
The smoothest way to change the C header, is to edit the C header itself. This may, for example, be used to replace macro definitions for types or functions. For this purpose the macro __H2SDL__
can be used. This macro is only defined during C to SDL translation (so it is not defined when C code generated from SDL is compiled by the C compiler). This property of __H2SDL__
can be exploited by "cheating" the C Code Generator.
The following examples illustrate how __H2SDL__
can be exploited to change C headers to make macros and functions with a variable number of parameters available in SDL.
#define BYTE unsigned char
In the C fragment above, the macro BYTE
is used as if it were a type. The preprocessor will resolve all BYTE
occurrences, so BYTE
is not available in SDL. This can be circumvented, changing this definition to the following:
#ifndef __H2SDL__ #define BYTE unsigned char #else typedef unsigned char BYTE; #endif
Now BYTE
is available as a type in SDL (because during C to SDL translation, __H2SDL__
will be defined), but in the generated C code, BYTE
is a macro (because then, __H2SDL__
will be undefined).
#define putchar(c) .... /* some C definition */
putchar
is defined as a macro, that is used as if it were a function. The following change makes putchar
available in SDL.
#ifndef __H2SDL__ #define putchar(c) .... /* some C definition */ #else int putchar (char); #endif
With the above definition, putchar
will be regarded as an external procedure by the SDL system (because then, __H2SDL__
will be defined). When the C code generated from SDL is compiled, the C preprocessor will resolve the `function calls' to putchar
(because then, __H2SDL__
will be undefined).
#define max(a, b) (((a) > (b)) ? (a) : (b))
max
is used as a macro that could be used for any type for which >
is defined. In SDL such a `typeless' definition would not be possible. However, we could introduce specific definitions for specific types. The following definition makes a floatmax
available in SDL for float
, and an intmax
for int
.
#ifndef __H2SDL__ #define max(a, b) (((a) > (b)) ? (a) : (b)) #define intmax max #define floatmax max #else int intmax(int, int); float floatmax(float, float); #endif
int fprintf( FILE *, const char *, ...);
fprintf
is a function with an unknown number of parameters. In the SDL translation, fprintf
will be available as a procedure with two parameters (pointer to FILE
and pointer to Character
). Just as in Example 34, we have added specific functions in order to make specific parameter combinations available in SDL.
int fprintf( FILE *, const char *, ...); #ifndef __H2SDL__ #define fprintf1 fprintf #define fprintf2 fprintf #else int fprintf1(FILE *, const char *, int); int fprintf2(FILE *, const char *, int, int); #endif
Constants defined in C headers as macros are not translated to SDL because the C preprocessor will filter them out. Such constants can be added as external synonyms in a separate package.
Suppose the C header myheader.h
contains the following definitions:
#define MAXLEN 1000 #define VERSION "version 0.0"
To make MAXLEN
and VERSION
available as synonyms in SDL, the following definitions can be put in a separate package:
synonym MAXLEN Integer = external; synonym VERSION CharStar = external;
This package should "use myheader;
". The code generator will then use the definitions from myheader.h
.
An alternative solution is to convert the macros to const definitions:
const int MAXLEN = 1000; const char *VERSION = "version 0.0"
Such const definitions can be handled by H2SDL.
Normally it is not recommended to edit SDL packages that have been generated from a C header. But if the header is very stable (for example for system header files) and the translation to SDL of larger parts of a C header is not suitable for some application, it is possible to convert the C header to an SDL package using the command-line interface of H2SDL. Then the generated package can be edited with a text editor and imported into SDL with the PR to GR converter described in Convert to GR (SDL).
Example 37 illustrates how to change a generated SDL package.
Suppose the C header stdio.h
contains the following definitions:
#define FILE struct _iobuf #define stdin (&_iob[0]) #define stdout (&_iob[1])
As the preprocessor resolves these macros, they are "lost" in the C to SDL translation. We will change the generated PR file so that FILE
, stdin
, and stdout
can be used in SDL. First we convert the C header to an SDL package by running H2SDL from the command-line:
sdth2sdl -n ChangedPackage -r stdio.h
This creates a file stdio_SDL.pr
. Note that H2SDL has an option (-n
) for setting the package name. See Command-Line Options for more information.
The package header is now:
package ChangedPackage; /*#C `stdio.h' */
We can start with FILE
, which in C is used as a shorthand for the type struct _iobuf
. In SDL terms, this would mean that we define FILE
as a syntype of the type that was generated for struct _iobuf
. In stdio_SDL.pr
we see that this is the following.
newtype Struct_zz_UScr_1_iobuf /*#SYNT*/ struct ...
Below this type, we add the following line:
syntype FILE = Struct_zz_UScr_1_iobuf endsyntype;
stdin
and stdout
are shorthands for constants. This corresponds in SDL to synonyms. After some thinking, we can conclude that stdin
and stdout
must be of type struct _iobuf *
. In the generated SDL, we can look up this type and add two definitions:
synonym stdin /*#NAME `stdin' */ Ref_stdio_Struct_zz_UScr_1_iobuf = external; synonym stdout /*#NAME `stdout' */ Ref_stdio_Struct_zz_UScr_1_iobuf = external;
The next step is to convert the package to GR from the Organizer, using Generate/Convert to GR. We add a new package to the Organizer and connect it to the existing filestdio_SDL.
sun
(when connecting, deselect Expand substructure in the Connect dialog).
This package can now be used as any other SDL package.
External procedures, implemented as C functions, can be used as an alternative to implementing operators in C using the #ADT
code generator directive (see Implementation of User Defined Operators). Use of external procedures has the following advantages:
#CODE
sections.As an example, we consider an SDL sort P
oint
:
newtype Point struct x, y Integer; endnewtype;
We need an operation AddPoints
to add two points and we want to implement this operation in C. The traditional way in SDT to implement this operation is by means of an SDL operator that has a code generator directive attached. The implementation could look something like:
newtype Point struct x, y Integer; operators AddPoints: Point/*#REF*/, Point/*#REF*/ -> Point; /*#OP(B)*/ /*#ADT () #BODY #(Point) #(AddPoints) (#(Point) *p1, #(Point) *p2) { #(Point) result; result.x = p1->x + p2->x; result.y = p1->y + p2->y; return result; } */ endnewtype;
An alternative is to define AddPoints
as an external procedure, and implement it as a C function. In order to get this working, a number of small problems have to be solved.
Let us start with the C implementation of AddPoints
. For this purpose we create a new file points.c
. The first problem is that AddPoints
uses the SDL data type Point
. How do we make this type visible in C? One alternative is to define Point
in a package or in the system diagram and make use of environment header files (.ifc
files). In this example we choose to make use of normal C headers that the C code generator produces for SDL diagrams for which full separation is specified. Full separation can be selected in the Organizer Generate/Make dialog.
Suppose that Point
is defined in process P
. When code for P
is generated, a header p.h
will be created that can be included in points.c
. Since p.h
makes use of SDL predefined types, we also include scttypes.h
, which is normally located in the directory $sdtdir/INCLUDE
(on UNIX), or %SDTDIR%\include
(in Windows). Assuming that the target directory is target
, and points.c
is in the same directory as the SDL system, points.c
can be implemented as:
/* points.c */ #include "scttypes.h" #include "target/p.h" Point AddPoints (Point *p1, Point *p2) { Point result; result.x = p1->x + p2->x; result.y = p1->y + p2->y; return result; }
Furthermore, we create a C header points.h
that contains the declaration of AddPoints
:
/* points.h */ Point AddPoints (Point *p1, Point *p2);
The C function uses the name Point
, without any prefix. To make this possible, we supply the data type Point
with a #NAME
directive (see Specifying Names in Generated Code -- Directive #NAME). So the changed SDL definition of Point
is as follows:
newtype Point /*#NAME `Point'*/ struct x, y Integer endnewtype;
An external procedure AddPoints
must also be declared. A #CODE
directive to include points.h
accompanies the declaration, otherwise the C compiler will complain that C function AddPoints
has not been declared.
procedure AddPoints; fpar in/out p1 Point, in/out p2 Point; returns Point; external; /*#CODE #HEADING #include "points.h" */
The last remaining problem is that the SDL system must be linked together with points.c
. The recommended way to do this is to create a makefile template points.tpm
(in Windows adopted to suit either the Borland or Microsoft compiler) that contains the following lines:
On UNIX:
USERTARGET = points$(sctOEXTENSION) points$(sctOEXTENSION): ../points.c $(sctCC) $(sctCPPFLAGS) $(sctCCFLAGS) \ ../points.c \ $(sctIFDEF) -o points$(sctOEXTENSION)
In Windows, Borland (bcc32) Version:
USERTARGET = points$(sctOEXTENSION) points$(sctOEXTENSION): ../points.c $(sctCC) $(sctCPPFLAGS) $(sctCCFLAGS) \ ../points.c \ $(sctIFDEF) -opoints$(sctOEXTENSION)
In Windows, Microsoft (cl) Version:
USERTARGET = points$(sctOEXTENSION) points$(sctOEXTENSION): ../points.c $(sctCC) $(sctCPPFLAGS) $(sctCCFLAGS) \ $(sctIFDEF) /Fopoints$(sctOEXTENSION) \ ../points.c
This template is used if you select the radio button Generate makefile and use template in the Organizer Generate/Make dialog and select the file points.tpm
.
ASN.1 is a language for defining data types that is frequently used in the specification and implementation of telecommunication systems. ASN.1 is defined in ITU-T Recommendations X.680-X.683. Recommendation Z.105 defines how ASN.1 can be used together with SDL. A subset of Z.105 is implemented in SDT.
This chapter explains how ASN.1 data types can be used in SDL systems. The following items will be discussed:
SDT treats ASN.1 modules in a very similar way as C headers, see Organizing C Headers in SDT. It is recommended to have a special chapter for ASN.1 modules (for example called ASN.1 Modules). If many ASN.1 modules are used, they may be grouped into Organizer modules (which is not the same as ASN.1 modules!), see Module.
Figure 33 shows an example of the Organizer look of a chapter with two Organizer modules containing ASN.1 modules.
Figure 33 : Example of ASN.1 modules in the Organizer
|
We will show with an example how to use an ASN.1 module in an SDL system. Suppose we have an ASN.1 module MyModule
in file mymodule.asn
:
MyModule DEFINITIONS ::= BEGIN Color ::= ENUMERATED { red(0), yellow(1), blue(2) } END
This module contains one type definition, Color
, that has three values, red
, yellow
, and blue
.
We first add a new diagram of type Text ASN.1 to the Organizer using Edit/Add New (without showing it in the Editor) and we connect it to the file mymodule.asn
(using Edit/Connect). In order to use the ASN.1 module in SDL, we edit the system diagram and add `use MyModule;
' in the package reference clause, as is illustrated in Figure 34 below.
Figure 34 : Using an ASN.1 Module in SDL
|
Figure 35 shows the resulting Organizer view. The symbol below the MySDLSys system symbol is a dependency link that indicates that the SDL system depends on an external ASN.1 module. Dependency links for ASN.1 modules that are used by an SDL system were previously required by the Analyzer, but now only serve as comments and are optional.
Figure 35 : Organizer View of SDL System Using ASN.1 Module
|
If ASN.1 modules use other ASN.1 modules, dependency links between the ASN.1 modules should be created.
After the above preparations, the data types in MyModule
can be used in SDL. As an example, we will make an SDL system that converts a character string to the corresponding color. This is done by two signals:
GetColor
has ASN.1 type IA5String as a parameter. ReturnColor
, that has a BOOLEAN parameter indicating whether there is a color that matches the string, and a Color
parameter.The system diagram including these signal definitions is shown in Figure 36 below.
Figure 36 : SDL system diagram
|
The MSC below illustrates how the system is intended to be used.
Figure 37 : MSC illustrating GetColor
|
In order to know which values and which operators can be used on ASN.1 types, it is necessary to look in Translation of ASN.1 to SDL.
For example, Color
is defined as an ENUMERATED
type. By looking at the mapping rules in Enumerated Types, we see the list of operators that can be used on Color
. These are in this case Num
, `<
`, `<=
', `>
', `>=
', Pred
, Succ
, First
, Last
, and also `=
' and `/=
', which are always available.
Figure 38 : Using the Color type in SDL
|
Figure 38 shows a fragment of an SDL process that uses Color
. It contains a loop over all values of Color
, and illustrates how to declare variables of Color
, how to use Color
in new SDL sort definitions, and how to use the operators First
, Last
, and Succ
. Some notes on the fragment:
ColorToString
is used to convert a color to an IA5String. In the fragment we do actually the opposite. An alternative solution would be to have a S
tringToColor array (IA5String, Color)
because then no loop would have been needed (see also Array). However, the purpose of the example was to illustrate how to loop through all elements.first
in c := first(red)
returns the element with the lowest associated number. This ensures that we really get all elements when using the Succ
operator. In this case we could just as well have written c := red
.IA5String
, which is in fact a syntype of the predefined SDL sort C
harstring
.The predefined simple ASN.1 types can be used directly in SDL. In most cases, the ASN.1 type has the same name in SDL, for example ASN.1's type NumericString
is also called NumericString
in SDL. However, some predefined ASN.1 types contain white-space, like BIT STRING
. In SDL, the white-space is replaced with an underscore, so the corresponding SDL sort is called BIT_STRING
.
The operators on these predefined ASN.1 types are described in detail in section Using SDL Data Types.
The ASN.1 Utilities do not yet support encoding rules. To use encoding rules in SDT (like, for example, the Basic Encoding Rules defined in X.690), it is recommended to use ASN.1 in combination with SDL exactly as described above.
The encoding and decoding between ASN.1 data types and bit streams is done in the environment functions. For doing the actual encoding/decoding you need some other ASN.1 tool that can generate C or C++ and that has support for the encoding rules that you need. The code generated by this other tool can then be used in the environment functions xInEnv
and xOutEnv
(which are explained in The Environment Functions).
A manual job is needed if the C data types that are generated by SDT are different from the C/C++ data types that are generated from the other ASN.1 tool, because such data types have to be converted from the one tool's format to the other and vice versa.
For example, suppose that we have a signal S
that is sent from the SDL system to the environment. The signal has one parameter of ASN.1 type A
that has to be encoded according to BER. We use some ASN.1 tool X to generate the encoding/decoding functions. Suppose X generates C type A_X
and C function BEREncodeA
to encode A
to bytes. Suppose furthermore that SDT generates C type A_SDT
from A.
In the environment function xOutEnv
, where we do the encoding, we do the following: we get the signal parameter (which is of type A_SDT
), we convert it to data type A_X
, and we encode the parameter by calling BEREncodeA
after which we finally can send the data. This is illustrated in the code sketch below.
/* incomplete code that sketches how to encode * SDT generated ASN.1 types to BER using an * encoder that is generated by another * ASN.1 tool X */ A_X convertAtoX (A_SDT a) { /* help function that converts type A from SDT's format to X's format */ } xOutEnv (xSignalNode *s) { ... A_SDT par1; /* variable for type A generated from SDT */ A_X convertedPar1; /* variable for type A generated from X */ ... if ((*S)->NameNode == S) { /* get parameter */ par1 = ((yPDP_Message) (*s))->Param1; convertedPar1 = convertAtoX (par1); bits = BEREncodeA (convertedPar1); /* send bits over line */ }
Decoding is done in the environment function xInEnv
, following the same principles, but now the order is the other way around; received bytes are decoded to A_X
, converted by a help function to A_SDT
, assigned to an SDL parameter of a signal, and finally sent as a signal into the SDL system.
One more advantage of ASN.1 is that TTCN is also based on this language. By specifying the parameters of signals to and from the environment of the SDL system with ASN.1 data types, this information can be re-used in ITEX for the specification of test cases for the system.
Figure 39 : Sharing ASN.1 definitions between SDL and TTCN
|
This has the big advantage of making it easier to keep the SDL specification consistent with the TTCN test specification.
The use of external ASN.1 in TTCN is covered in more detail in the ITEX manual. In this section we will briefly illustrate how to share data between SDL and TTCN using TTCN Link.
Supposing we have to write a test suite for the SDL system with the Colors
example, we would add a new diagram -- a TTCN Test Suite, for example called ColorTest
-- to the Organizer. In this test suite we want to use definitions from the ASN.1 module MyModule
that contains the Colors
data type. For this purpose we need to set a dependency link between the ASN.1 module and the test suite. We do this by selecting the ASN.1 module in the Organizer. By using Generate/Dependencies we connect it to the TTCN test suite ColorTest
.
We can also use TTCN Link to generate declarations from SDL system MySDLSys
. For this purpose, it is easiest to associate the SDL system with the TTCN test suite. This is done by selecting the SDL system diagram in the Organizer and associate it with the TTCN test suite using Edit/Associate. The Organizer View for the test suite now looks as in Figure 40 below:
Figure 40 : Organizer view of a test suite that uses ASN.1
|
We can generate a TTCN Link executable for the SDL system by selecting the SDL system in the Organizer and using Generate/Make select standard kernel TTCN Link. Now we can start ITEX by double-clicking on test suite ColorTest
. By using TTCN Link/Generate Declarations, we can automatically generate the PCOs, ASP type definitions and ASN.1 type definitions. If we look at the result, we can see that Color
is present as an ASN.1 Type Definition by Reference. This table is shown in below.
Figure 41 : Resulting table in ITEX
|
Now this data type can be used in creating test cases, in constraints, etc. If at some point in time the definition of Color
would be changed (for example if we would add a new color), then, in order to update the test suite accordingly, we can select the TTCN table for Color
. In the Analyzer dialog, we should select both Enable Forced Analysis and Retrieve ASN.1 Definitions. Now the TTCN test suite will be updated with the new definition for Color
.