Думал вбросить это еще в теме "форыч зло"
http://oberspace.dyndns.org/index.php/topic,591.0.html, но там был уклон в структурность continue-break в циклах. А здесь будет самое то.
Оператор FOR зло уже потому что модифицирует пространство имен, превращая свой счетчик из VAR-переменной в IN-переменную в своем операторном параметре StatementSeq (даже при вызове в StatementSeq других процедур, счетчик не должен менятся), а после вызова FOR-оператора значение счетчика не определено языком и как бы не имеет права использоваться. А хороший оператор не должен никак менять пространство имен, и зря Вирт вернул этот нехороший FOR в Оберон-07. Поэтому у меня давно есть решение: FOR если и можно оставить, то только как спецпроцедуру, в чьем пространстве имен декларируется одно имя read only целого типа (а не импортируется как сейчас), а остальные идентификаторы globalIdent`i (i=1..N) импортируются в операторное тело StatementSeq как обычно (глобально).
То есть прежний вызов оператора FOR
ForStatement =
FOR ident ":=" Expr1 TO Expr2 [BY ConstExpr] DO
StatementSeq <* "(" ident {"," globalIdent`i} ")" *>
END.
превращается теперь в спецдекларацию процедуры identForProc:
ForProcDecl =
PROCEDURE identForProc FOR ident ":" identType [BY ConstExpr] DO
StatementSeq <* "(" ident {"," globalIdent`i} ")" *>
END identForProc.
а вызывается как обычная процедура:
identForProc(Expr1, Expr2 (* : <= identType *))
где идентификатор identType - целый тип.
Хотя под вызовы FOR с разными (ident, ConstExpr, StatementSeq) придется декларировать как отдельные процедуры, но это будет правильно.
Делать ConstExpr внешним фактическим параметром, подобно Expr1 и Expr2, не следует, так как в Паскаль-языках нет объявления процедур с константными фактическими параметрами (будет нестандартная сигнатура), и как фактический параметр ConstExpr не мог бы тогда опускаться, подразумевая стандартное значение 1, а его значение 0 не давало бы ошибку при компиляции.
Но при желании, выражения Expr1 и Expr2, служащие фактическими безымянными параметрами, также можно убрать в декларацию:
PROCEDURE identForProc FOR<*IN*> ident ":" identType "=" Expr1 TO Expr2 [BY ConstExpr] DO
StatementSeq <* "(" ident {, globalIdent`i} ")" *>
END identForProc
Будет процедура без фактических параметров:
identForProc()
Помимо синтаксиса декларации, отличие FOR-процедуры от обычной процедуры состоит в многократном выполнении операторного тела StatementSeq при каждом вызове FOR-процедуры. При этом IN-переменная-счетчик ident в начале вызова инициируется значением Expr1, а между повторными выполнениями тела процедуры значение счетчика приращается на константную величину ConstExpr либо 1.
Все это можно описать и с помощью обычных процедур:
CONST
h = ConstExpr;
Check = 1 DIV h; (* h # 0 *)
...
PROCEDURE identForProc ((*IN*) i0, i1: identType);
VAR ident: identType;
PROCEDURE Stats (IN i: LONGINT);
BEGIN
StatementSeq (* (i, ... (* без h, i0 и i1 *)) *)
END;
BEGIN
ident := i0;
IF h>0 THEN
WHILE ident <= i1 DO
Stats(ident);
INC(ident, h)
END
ELSE
WHILE ident >= i1 DO
Stats(ident);
INC(ident, h)
END
END
END identForProc;
...
identForProc(Expr1, Expr2);
Приходиться делать два уровня вложенности, один для локальной переменной-счетчика, а второй для фиксации ее значения. Довольно громозко, и корректность конструкции должен проверять программист. Но поскольку, кроме Expr1, Expr2, ConstExpr и StatementSeq, прочее содержимое конструкции одинаково для всех FOR-процедур, то эти декларации можно сократить до предложеннего мною варианта спецпроцедуры.
Поскольку старый оператор FOR импортировал только те имена, что имеются в области видимости его вызова, вызов FOR-процедуры также можно разрешить только в той области видимости, где задекларирована сама эта FOR-процедура, т.е. идентификатор identForProc FOR-процедуры
не должен экспортироваться во вложенные области видимости. Тогда мы получим почти аналог INLINE. Любой вызов FOR-процедуры можно будет заменить обратно на оператор FOR. При этом правда потребуется декларацию процедуры заменить на декларацию переменной-счетчика, и для каждого вызова FOR-оператора переменная-счетчик должна быть взята своя. Чистый INLINE был бы, если бы процедура не объявляла внутри себя никаких идентификаторов (кроме других вложенных INLINE процедур) и не имела бы именованных параметров (тогда ее декларация при INLINE просто отбрасывается), опять же при условии что вызовы такой процедуры идут только в той области видимости, где она была продекларирована.
Это условие необходимо хотя бы потому, что еще нужно учесть случай вложенных FOR-операторов. Ведь их операторные параметры StatementSeq имеют
совпадающие области видимости за исключением того, что счетчик вложенного FOR как бы не виден в объемлющем его FOR (за вычетом области вложенного FOR). Значит FOR-процедуры, заменяющие эти вложенные операторы, дожны быть продекларированы внутри той же области видимости, но аналогично вложенными одна в другую. Декларативную область FOR-процедур для учета этой вложенности следует дополнить процедурным сектором для FOR-подпроцедур:
ForProcDecl =
PROCEDURE identForProc FOR ident ":" identType [BY ConstExpr]
{";" ForProcDecl`i}
DO
StatementSeq <* "(" ident {"," identForProc`i} {"," globalIdent`j} ")" *>
END identForProc.
У хорошего оператора должны быть только три синтаксических вида параметров: Designator, Expression и Statement (StatementSeq). Переменная-счетчик FOR-оператора теоретически тоже может быть любым составным именем, т.е. компонентой переменной, даже динамической, и даже импортироваться из другого модуля, но тогда ее изменения трудно контролировать. Поэтому счетчик FOR-оператора сделан строго ident. Я считаю, что таким (по синтаксису) параметрам место только в декларативной области.
Даже ConstExpression параметры в операторах идеологически подозрительны. Кроме FOR-оператора (необязательная секция BY), они были еще только в метках оператора CASE. Но уже в 07 версии Вирт их выпилил, заменив правда на более худший (усеченный) вариант: label = integer | string | ident. ConstExpression тоже должны быть только в декларациях.