February 13, 2018

ExST: расширенный ST

Язык ST (Structured Text) получил много полезных расширений, что в последствии назвали Extended ST. И это не только модное ООП. Я свалил все в кучу: и расширения, и просто малоизвестные вещи, к тому же, всё это подходит не только для TwinCAT, но и для CoDeSys-мира вообще. Начнем с мальчика для битья — оператора GOTO...

Язык ST все еще поддерживает оператор GOTO. Не прямо так гоу-ту, а как в ассемблере — JMP куда-то там на метку в коде. Выглядит это так:

label_infinitum:
(* полезный код *)
JMP myLabel_4321;

(* бесполезный кот *)

myLabel_4321:    // это метка для перехода
(* еще полезный код *)
JMP label_infinitum;

Избегайте бесконечно-вечных циклов.


CONTINUE


CONTINUE относится к расширению языка, а EXIT нет. Если вы не знали, то EXIT немедленно прекращает выполнение текущего цикла и выходит из него (текущая итерации FOR, WHILE или REPEAT не будет выполнена до конца). CONTINUE же прекращает текущую итерацию и немедленно переходит к началу цикла, выполнять следующую итерацию:

FOR i := 0 TO 100 BY 2 DO
    IF i MOD 3 = 0 THEN
        CONTINUE; // пропускаем инкремент переменной a и переходим к следующей итерации
    END_IF
    a := a + 1;
END_FOR

Осторожнее с вложенными циклами: EXIT выходит только из одного вложения. Для выхода из нескольких вложенных циклов, помогает команда JMP.

FOR j := 0 TO 100 DO
    FOR i := 0 TO 100 DO
        IF ??? THEN
            EXIT; // завершит цикл FOR i ...
        ELSE
            JMP finita_incantata;
        END_IF
    END_FOR
    // EXIT приведет сюда
END_FOR

finita_incantata:

Оператор MOD находит остаток от деления, как оператор % в C++.


Оператор установки S=


Запись A S= X; аналогична:

IF X THEN
    A := TRUE;
END_IF

Как только A станет равным TRUE, то никакое значение X уже не изменит A. Оператор S= никогда не сможет сбросить A в FALSE.

A        X       A
FALSE S= FALSE → FALSE
FALSE S= TRUE  → TRUE
TRUE  S= FALSE → TRUE
TRUE  S= TRUE  → TRUE


Оператор сброса R=


Запись F R= X; аналогична:

IF X THEN
    F := FALSE;
END_IF

Как только F станет равным FALSE, то никакое значение X уже не изменит F. Оператор R= никогда не сможет установить F в TRUE.

F        X       F
FALSE R= FALSE → FALSE
FALSE R= TRUE  → FALSE
TRUE  R= FALSE → TRUE
TRUE  R= TRUE  → FALSE


Присваивание


Присваивание как выражение (assignment as expression) можно делать так:

IF bVar := (i = 2) THEN
    i := i + 1;
END_IF

bVar — булева переменная, получает значение от выражения (i = 2), то есть будет равна TRUE когда (i = 2). Затем bVar оценивается в IF. Лучше не мудрить и не пихать в одну строку несколько команд сразу. Пишите так, чтобы было проще читать впоследствии. Компилятору все-равно как  вы написали, работать это будет одинаково, а вероятность ошибиться намного выше в сложносочиненной записи:

bVar := (i = 2);

IF bVar THEN
    i := i + 1;
END_IF


Можно присваивать нескольких переменных одновременно:

a:= b:= c:= k + 12;

Но не дайте обмануть себя видом и формой записи — здесь нет присваивания, передающегося справа-налево по цепочке! Аналогичная, но более длинная запись, выглядит так:

a := k + 12;
b := k + 12;
c := k + 12;

Выражение (k + 12) будет вычисляться три раза, поэтому выгоднее записать:

c := k + 12;
a:= b:= c;

Теперь вспомним операторы установки/сброса. Как будет работать следующая строка? Ответ в конце поста.

A S= F R= X;


Типизированные литералы


Звучит жутко умно, но это всего-лишь способ задать точный тип числа.

A := 123; — не говорит нам какого типа число 123 (литерал). Это точно не BOOL и скорее всего не REAL / LREAL (нет десятичной запятой). Возможны SINT, USINT, BYTE, INT, UINT, WORD, DINT, UDINT, DWORD. Так кто же из них?

Обычно тип литерала будет зависеть от типа переменной A. Поэтому можно просто уточнить запись литерала: A := 123.0; и тогда мы получим REAL. Но что если хочется BYTE, а переменная типа UINT?

Для этого тип можно обозначить точно: A := BYTE#127;

Такую запись можно использовать везде, где допустимо использование констант. Если TwinCAT обнаружит потенциальную потерю данных, то выдаст сообщение об ошибке или просто предупреждение, если всё не так страшно.


CASE ... ELSE


Если конкретный шаг CASE для какого-то значения переменной не существует, то CASE игнорируется целиком, за исключением если... Блок ELSE в CASE позволяет задать действие по умолчанию, если номер шага в CASE не задан:

CASE state OF
0:
    DoSomething();
100:
    DoSomething2();
...
2000:
    DoSomethingAgainNAgain();
ELSE
    DontKnowWhatHappenedLastTime();
END_CASE

Если вы знакомы с C++, то это аналогично: switch case ... default.


OR_ELSE


Работает аналогично OR, но если левая часть OR_ELSE уже равна TRUE, то правая часть не проверяется и не выполняется:

VAR
    a, z, b : BOOL;
    i : INT;
END_VAR

z := a OR_ELSE (b := (i = 2));

Если А равно TRUE, то правая часть OR_ELSE не будет выполняться и B никогда не сможет стать TRUE. Можно оптимизировать производительность, исключив лишний вызов функций. И теперь мы знаем, что для "обычного" OR, независимо от значения операндов, вызываются и проверяются обе части: и левая, и правая.


AND_THEN


Как AND, но правая часть AND_THEN обрабатывается только при условии, что левая часть  равна TRUE. Например, это удобно для проверок указателей:

IF (ptr <> 0 AND_THEN ptr ^= 99) THEN ...

Когда указатель не равен нулю и_тогда выполнить сравнение адреса указателя. Это позволяет избежать проблем с нулевым указателем. В обычном AND всегда обрабатываются обе части независимо от их значения.


Ответ


A S= F R= X;

// работает как

A S= X;
F R= X;

3 comments

  1. В заголовке и тексте указана функция - AND_ELSE, а в листинге AND_THEN. Правильный вариант: AND_THEN

    ReplyDelete

Note: Only a member of this blog may post a comment.