Difference between revisions of "Technical topic: OpenGEODE - SDL Operators: How to work with data"

From TASTE
Jump to: navigation, search
(SEQUENCE OF types (arrays))
(OCTET STRING)
Line 211: Line 211:
 
== OCTET STRING ==
 
== OCTET STRING ==
  
   OctStr ::= OCTET STRING (SIZE (0.255))
+
   OctStr ::= OCTET STRING (SIZE (0..255))
  
 
Octet strings can be assigned a text string, a hex string, or a bit string:
 
Octet strings can be assigned a text string, a hex string, or a bit string:
Line 219: Line 219:
 
   dcl myStr3 OctStr := 'hello world';
 
   dcl myStr3 OctStr := 'hello world';
  
It is '''not''' possible to iterate (with a '''for loop''') direectly on OCTET STRINGs. Only '''SEQUENCE OF''' types are iterable.
+
It is '''not''' possible to iterate (with a '''for loop''') directly on OCTET STRINGs. Only '''SEQUENCE OF''' types are iterable.
  
 
However you can do it using a range iteration:
 
However you can do it using a range iteration:
Line 225: Line 225:
 
   for each in range(length(myStr1)):
 
   for each in range(length(myStr1)):
 
       call writeln(myStr1(each));
 
       call writeln(myStr1(each));
 +
  endfor
 +
 +
To add an octet to a variable-size '''OCTET STRING''' you can use the append operator as described in the SEQUENCE OF type (see above). However note that to append a numerical variable, the variable has to be cast to an octet using the '''chr''' operator:
 +
 +
  dcl a SomeInteger := 42;
 +
  ...
 +
  myStr1 := myStr1 // mkstring(chr(a))
 +
 +
In reverse, if you want to convert an element of an OCTET STRING to an numerical variable, use the '''fix''' operator:
 +
 +
  for idx in range (length(myStr1)):
 +
      call crc (fix(tc(idx)));
 
   endfor
 
   endfor
  

Revision as of 13:55, 6 April 2022

Introduction

The SDL language primarily targets the description of a system's behaviour. It is however also possible to manipulate data using a collection of built-in operators. This can be convenient to avoid depending on external, manually-written code to process the parameters of messages during the execution of a state machine transition.

Variables are defined in the ASN.1 language and can express simple types (boolean, numbers and enumerated values) as well as complex data structures (records, arrays, and choices). Each type comes with some dedicated operators which are described in this page.


INTEGER types

  MyInt ::= INTEGER (-10 .. 255)
  dcl foo MyInt := 42;


The following can be done (in addition to the +, -, / and * operators):

  foo := -5               -- assign a value
  foo := abs (foo)        -- return 5
  foo := fix (5.7)        -- convert float to integer
  foo := power (10, 2)    -- 100
  foo := (foo + 1) mod 10 -- modulo
  write (foo)
  writeln (foo)           -- display the value (procedure call)

Moreover, integer can be assigned with a value expressed with an base 16 and base 2 representation, using the ASN.1 value notation:

  foo := '00FF'H;
  bar := '01100011'B;

If the range of the integer is positive (unsigned integer) then it is also possible to apply bitwise operators to it. In addition to and/or/xor the following operators are also available for unsigned integers:

  foo := Shift_Left (foo, 8)
  bar := Shift_Right (foo, 'FF'H)   -- Using the Hex or binary notation is possible too

REAL types

  MyReal ::= REAL (0.0 .. 10.0)
   
  dcl foo MyReal := 42.0;
  dcl bar MyInt  := 5;

The following can be done:

  foo := 0.0             -- assign a value
  foo := ceil (1.618)    -- ceiling
  foo := floor (1.618)   -- ceiling
  foo := float (bar)     -- convert integer to real
  foo := round (3.14159) -- round
  foo := sin (3.14)
  foo := cos (3.14)
  foo := sqrt (2)
  foo := trunc (7.77)    -- truncation
  write (foo)
  writeln (foo)


ENUMERATED types

  MyEnum ::= ENUMERATED { foo, bar }
  dcl var MyEnum := foo;
  dcl pos INTEGER (0..1);

Keep in mind that an enumerated is a state and not a number. You can get the value (as a number) of an enumerant like this:

  pos := num (var);

If the ENUMERATED type contains explicit values:

 MyOtherEnum ::= ENUMERATED {fourty (40), two (2)}
 dcl baz MyOtherEnum := fourty;

then applying the num operator will return either 40 or 2.

Additionally you can set the value of an enumerated from a position (or from the number set in the ENUMERATED type):

 var := val (1, MyEnum)       -- sets to bar
 baz := val (40, MyOtherEnum) -- sets to fourty

You can also print the enumerant name on the screen with the write or writeln operators.

CHOICE types

  MyChoice ::= CHOICE {
     foo BOOLEAN,
     bar INTEGER (0 .. 255)
  }

  dcl a MyChoice := foo : FALSE;   -- ASN.1 Value Notation to set the value

You can evaluate the current choice using the present operator in a decision:

   DECISION present (a)  -- test against foo and bar
   writeln (if present (a) = foo then a.foo else a.bar fi)

You can also use an implicit (local) type to store the choice determinant:

   dcl current_choice MyChoice_selection := present (a);

At system level you may also have defined a ASN.1 ENUMERATED type with the extact same enumerants as the CHOICE options. This can be useful if you want to send the choice determinant only over an interface, but without its actual value. In that case you can convert the current choice to this enumerated type using the To_Enum operator:

     MyDeterminants ::= ENUMERATED { foo, bar }
     dcl det MyDeterminants := To_Enum (current_choice, MyDeterminants);

In reverse, you can convert the enumerated to the implicit type storing the choice determinant (as returned by the present operator), using the To_Selector operator:

     current_choice := To_Selector (det, MyChoice);

Last, there is an internal operator named choice_to_int that can be used if the CHOICE options are (mostly) numerical. It allows to return the value correponding to the current choice item without specifying the field name. You must provide a default value that will be returned in case the current choice's type is not numerical:


   dcl someInt MyInt;
   dcl someChoice MyChoice := { bar : 42 }
   (...)
   someInt := choice_to_int (someChoice, 10)    -- will return 42
   someChoice := { foo : false }
   someInt := choice_to_int (someChoice, 10)    -- will return 10, the default value since the current choice is not "bar"

When combined with other operators, this can be used to make checks on the value of the choice without having to check manually all possible values.

For example you may want to write a generic function that can check a value against a threshold. The value itself could be in a CHOICE:

  ValueToMonitor ::= CHOICE {
     current Amp,
     voltage INTEGER (0..230)
     -- and a hundred more
  }
  Parameters ::= ENUMERATED { current, voltage, ..... }

but you don't want to write a function that does something like:

   if present (val) = current then
     if val.current < currentThreshold then
       return true
     end if
   else if present (val) = voltage then
      if val.voltage < voltageThreshold then
        return true
      end if
   else .....

Instead you can do in a single line:

   if choice_to_int(val, 0) < thresholds(num(to_enum(val), Parameters)) then
       return true
   end if

assuming that threshold is a table indexed in the order of the determinants.

SEQUENCE OF types (arrays)

  MySeqOf ::= SEQUENCE (SIZE (0..10)) OF BOOLEAN
  dcl foo MySeqOf := { true, false, false };  -- ASN.1 Value Notation
  dcl bar MyInt;
  bar := fix (length (foo)) -- 3
  for each in foo:
     call writeln (each);
  endfor


Bitwise operators (and/or/xor) can be applied to sequences of booleans.

When the array has a variable size (SEQUENCE (SIZE (1..10)) OF ...) the append operator can be applied to add elements:

  foo := foo // { true, false }

IMPORTANT

The ASN.1 value notation: { true, false } can only contain literal elements (ground expressions), but not results of a function call or indexed variables - this is NOT allowed:

  foo := foo // { foo(1), true }

The proper syntax in that case is to use the "mkstring" operator:

  foo := foo // mkstring(foo(1)) // { true }

SEQUENCE types (records)

  MySeq ::= SEQUENCE {
     a BOOLEAN OPTIONAL,
     b INTEGER (0 .. 255)
  }
  dcl seq MySeq := { a FALSE, b 10 };   -- ASN.1 Value Notation
  DECISION exist (seq.a)  -- test presence of optional field

STRING types

IA5String

This type is an ASCII string

 SomeString ::= IA5String (1..255)

It can be assigned a value:

 dcl myStr SomeString := 'hello';    -- at declaration
 TASK myStr := 'world';              -- or in a task
 TASK myStr := "World";              -- You can use double quotes
 TASK myStr := 'With \'Escaping\' ';  -- And possibly escape inner quotes

It can be used in write/writeln calls.

OCTET STRING

 OctStr ::= OCTET STRING (SIZE (0..255))

Octet strings can be assigned a text string, a hex string, or a bit string:

 dcl myStr1 OctStr := '68656c6c6f'H;
 dcl myStr2 OctStr := '01010110'B;
 dcl myStr3 OctStr := 'hello world';

It is not possible to iterate (with a for loop) directly on OCTET STRINGs. Only SEQUENCE OF types are iterable.

However you can do it using a range iteration:

  for each in range(length(myStr1)):
     call writeln(myStr1(each));
  endfor

To add an octet to a variable-size OCTET STRING you can use the append operator as described in the SEQUENCE OF type (see above). However note that to append a numerical variable, the variable has to be cast to an octet using the chr operator:

  dcl a SomeInteger := 42;
  ...
  myStr1 := myStr1 // mkstring(chr(a))

In reverse, if you want to convert an element of an OCTET STRING to an numerical variable, use the fix operator:

  for idx in range (length(myStr1)):
     call crc (fix(tc(idx)));
  endfor

TIMER types

  timer mytimer;

The SET and RESET operators from SDL are indirectly supported via procedure calls:

  set_timer (1000, mytimer);
  reset_timer (mytimer);