PERFORM: как в COBOL устроены циклы

В COBOL нет отдельных слов for и while — обе идеи циклов выражаются через одну конструкцию PERFORM с разными вариантами.

PERFORM — оператор, который повторяет блок кода заданное число раз или пока выполняется условие, объединяя в себе роли for-цикла и while-цикла из других языков.

Зачем один оператор вместо двух

В языках вроде Python или Java обычно есть отдельно for — для случая «повторить N раз» или «пройтись по списку», и отдельно while — для случая «повторять, пока верно условие». COBOL решает обе задачи одной конструкцией PERFORM, у которой есть несколько форм записи. Это не значит, что COBOL менее гибкий — просто вся логика повторения сосредоточена в одном ключевом слове, а разница выражается в дополнительных параметрах после него.

Разберём две главные формы: PERFORM VARYING (когда заранее известно, сколько раз и как меняется счётчик) и PERFORM UNTIL (когда повторяем, пока не выполнится условие, а сколько именно раз — заранее не известно).

PERFORM VARYING — аналог for

Представьте, что нужно распечатать таблицу умножения на 5, от 1 до 10. Синтаксис:

       PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 10
           COMPUTE WS-RESULT = WS-I * 5
           DISPLAY WS-I " x 5 = " WS-RESULT
       END-PERFORM.

Вывод (первые и последние строки):

1 x 5 = 5
2 x 5 = 10
3 x 5 = 15
...
10 x 5 = 50

Читаем построчно:

  • WS-I — переменная-счётчик, её нужно заранее объявить как числовое поле (например, PIC 9(2)).
  • FROM 1 — начальное значение счётчика.
  • BY 1 — шаг, на который счётчик увеличивается после каждого прохода. Можно поставить и BY 2, чтобы перебирать только чётные или только нечётные числа.
  • UNTIL WS-I > 10 — условие остановки. Важный момент: это условие ОКОНЧАНИЯ цикла, а не условие продолжения. Цикл работает, пока WS-I > 10 ложно, и останавливается, как только оно становится истинным.
  • END-PERFORM — обязательная граница тела цикла, как END-IF у условия.

Обратите внимание: увеличение счётчика (BY 1) и проверка условия происходят автоматически на каждом шаге — вам не нужно вручную писать ADD 1 TO WS-I внутри тела, как пришлось бы в некоторых других языках при ручной реализации цикла.

PERFORM UNTIL — аналог while

Если заранее неизвестно, сколько раз нужно повторить действие — например, читаем строки из файла, пока не наткнёмся на его конец — используется более простая форма без счётчика:

       MOVE 0 TO WS-SUM
       MOVE 1 TO WS-N
       PERFORM UNTIL WS-N > 5
           ADD WS-N TO WS-SUM
           ADD 1 TO WS-N
       END-PERFORM.

       DISPLAY "Сумма чисел от 1 до 5: " WS-SUM.

Вывод:

Сумма чисел от 1 до 5: 15

Здесь счётчик WS-N заводится и обновляется вручную двумя отдельными операторами ADD — PERFORM UNTIL сам по себе ничего не меняет, только проверяет условие перед каждым проходом. Это делает форму гибче: условие остановки может зависеть от чего угодно, а не только от простого счётчика — например, от флага, который выставляет операция чтения файла.

Как выглядит бесконечный цикл (и почему это баг)

Частая ошибка новичков — забыть обновить переменную, от которой зависит условие остановки:

       MOVE 1 TO WS-N
       PERFORM UNTIL WS-N > 5
           DISPLAY "Итерация"
       END-PERFORM.

В этом коде WS-N никогда не меняется внутри цикла, условие WS-N > 5 никогда не станет истинным, и программа будет печатать "Итерация" бесконечно, пока её принудительно не остановят. Это не какая-то особая для COBOL проблема — та же логическая ошибка возможна в любом языке с while-подобным циклом, но в COBOL её особенно легко допустить, потому что счётчик в PERFORM UNTIL нужно вести полностью вручную.

Частые ошибки

  • Перепутать UNTIL с условием продолжения. UNTIL WS-I > 10 значит «остановись, когда стало больше 10», а не «продолжай, пока больше 10» — это частая логическая путаница у тех, кто ожидает синтаксис вида «while условие верно».
  • Забыть обновить переменную в PERFORM UNTIL, что приводит к бесконечному циклу, как в примере выше.
  • Забыть END-PERFORM, из-за чего тело цикла становится неоднозначным — компилятор либо выдаст ошибку, либо (в старом синтаксисе с точкой) захватит в тело больше или меньше строк, чем задумывалось.

Итоги

  • PERFORM VARYING — цикл со счётчиком, аналог for: начальное значение, шаг, условие остановки задаются в одной строке.
  • PERFORM UNTIL — цикл по условию, аналог while: счётчик (если он нужен) ведётся вручную внутри тела.
  • Условие в UNTIL — это условие ОКОНЧАНИЯ, а не продолжения цикла.
  • END-PERFORM обязателен для многострочного тела цикла.
Проверьте себя
1. Что означает условие UNTIL WS-I > 10 в конструкции PERFORM VARYING?
AЦикл останавливается, когда WS-I становится больше 10
BЦикл продолжается, пока WS-I больше 10
CЦикл выполнится ровно 10 раз независимо от значения WS-I
DУсловие проверяется только один раз перед первым проходом
2. Почему цикл PERFORM UNTIL WS-N > 5 с телом DISPLAY "Итерация" (без изменения WS-N внутри) окажется бесконечным?
AПотому что переменная-счётчик WS-N никогда не обновляется внутри тела цикла
BПотому что PERFORM UNTIL в принципе не умеет останавливаться
CПотому что DISPLAY блокирует изменение переменных
DПотому что не хватает оператора BY 1