WHILE.
С этим оператором пришлось много
повозиться. Ввел даже еще одну отключающую реальные действия (то-есть
вычисления) переменную ToDoW. Кроме того я пришел к выводу, что внутри
функции WhileOp лучше
вызывать сразу поток операторов, а не перебирать операторы в цикле. Потом в
этом же ключе переделал и IfOp.
Вот отрывок и с WHILE и
с IF:
/*** If
**/
void
IfOp()
{
int Result;
int ToDoif1;
char Lex2[20];
NextLexema();
/***************/
if(*Lex!=40) /* Если скобка */
{
*Res=0;
ErrorLex(18);
/*WrStr("Error.txt",End,"ERROR - No ( after
IF",1);*/
return;
}
ToDoif1=ToDoif;
NextLexema();
expr2();
if(ToDo!=0&&ToDoif!=0&&ToDoW!=0)
{
PostFix(Res,dest);
Result=atoi(dest);
}
if(*Lex!=41)
{
*Res=0;
ErrorLex(19);
/*WrStr("Error.txt",End,"ERROR - No 2-nd bracket in
IF",1);*/
return;
}
if(ToDo!=0&&ToDoif!=0&&ToDoW!=0)
{
if(Result!=0)
{
ToDoif=1;
}
else
{
ToDoif=0;
}
}
NextLexema();
StreamOp();
/*WrStr("Lex0.txt",End,Lex,1);*/
ToDoif=ToDoif1;
/****ELSE******/
if(COMPstr(Lex,"ELSE")==0)
{
if(ToDo!=0&&ToDoif!=0&&ToDoW!=0)
{
if(Result!=0)
{
ToDoif=0;
}
else
{
ToDoif=1;
}
}
NextLexema();
StreamOp();
ToDoif=ToDoif1;
return;
}
/*****конец обр-ки ELSE*******/
else
{
ToDoif=ToDoif1;
return;
}
/***************/
}
/*** End of If
**/
/*** While
**/
void
WhileOp()
{
int Result1=0;
int ToDoW1;
int iAdr;
char sAdr[100];
int iSTR1;
int ToExit=0; /* Если 0, то возвращаться к началу цикла */
int
iAdrExit;
char sAdrExit[100];
int iSTR2;
int res;
ToDoW1=ToDoW;
while(ToExit==0)
{
NextLexema();
/***************/
if(*Lex!=40) /* Если не скобка */
{
*Res=0;
WrStr("Error.txt",End,"ERROR - No ( after WHILE",1);
WrStr("Error.txt",End,Lex,1);
return;
}
NextLexema();
expr2();
if(ToDo!=0&&ToDoif!=0&&ToDoW!=0)
{
PostFix(Res,dest);
Result1=atoi(dest);
*Res=0;
*dest=0;
}
else
{
*Res=0;
*dest=0;
Result1=0;
}
if(*Lex!=41)
{
*Res=0;
WrStr("Error.txt",End,"ERROR - No 2-nd bracket in
WHILE",1);
return;
}
if(Result1!=0)
{
ToDoW=1;
ToExit=0;
}
else
{
ToDoW=0;
ToExit=1;
}
NextLexema();
StreamOp();
if(ToDoW==0)
{
ToExit=1;
}
if(!(ToDo!=0&&ToDoif!=0&&ToDoW!=0))
{
ToExit=1;
}
res=GetArrayFromStack(pStW,sAdr,100);
if(res!=100)
{
ErrorStack(-7);
}
iAdr=atoi(sAdr);
if(ToExit==0)
{
res=AddArrayToStackNoPart(pStW,sAdr,100);
if(res!=100)
{
ErrorStack(-3);
}
pProg=Prog+iAdr;
continue;
}
else
{
ToDoW=ToDoW1;
break;
}
};
/*pProg=pProgLast;*/
NextLexema();
/***************/
}
/*** End of While **/
void
Break()
{
if(ToDo!=0&&ToDoif!=0&&ToDoW!=0)
{
iBreak=1;
}
return;
}
void Operators() /* Это - собственно оператор (присваивания и др) */
{
int iAdr0;
char sAdr0[102];
int res;
if(COMPstr(Lex,"PRINT")==0)
{
OutPut();
return;
}
if(COMPstr(Lex,"GOTO")==0)
{
GoTo();
return;
}
if(COMPstr(Lex,";")==0) /*
Обработка пустого оп-ра */
{
return;
}
if(COMPstr(Lex,"IF")==0)
{
IfOp();
return;
}
if(COMPstr(Lex,"WHILE")==0)
{
iAdr0=(int)(pProg-Prog); /* Вычисляем, куда возвращаться */
sprintf(sAdr0,"%d",iAdr0);
res=AddArrayToStackNoPart(pStW,sAdr0,100);
if(res!=100)
{
ErrorStack(-3);
}
WhileOp();
return;
}
if(COMPstr(Lex,"ENDWHILE")==0)
{
/*iAdr0=(int)(pProg-Prog);
sprintf(sAdr0,"%d",iAdr0);
res=AddArrayToStackNoPart(pStW,sAdr0,100);
if(res!=100)
{
ErrorStack(-3);
}*/
return;
}
Assignment();
}
Обратим внимание, что создан еще один стек – для адресов возврата в цикле WHILE. И функция operators при нахождении ключевого слова WHILE вычисляет и заносит в стек адрес возврата.
Здесь мне пришлось столкнуться с весьма неприятным эффектом – безусловные переходы плохо живут с оператором WHILE. Если есть два вложенных цикла, и из внутреннего пытаемся выйти вперед с помощью GOTO, результат окажется совсем не таким, на какой мы рассчитывали. Пример программы (на разрабатываемом нами языке):
M=5;
WHILE(M>0)
M=M-1;
PRINT(M);
J=5;
WHILE(1>0)
J=J-1;
IF(J<2)
GOTO L1;
ENDIF;
PRINT("SSSSSS");
ENDWHILE;
L1: H=10;
ENDWHILE;
L2:PRINT("End"+" !!!");
В принципе, в этом нет ничего необычного. Сошлюсь на литературный источник – книгу «Программирование в среде Turbo Pascal 7.0» А.М.Епанешникова и В.А.Епанешникова:
«Безусловный переход может осуществляться далеко не из каждого места программы и не в любое место программы. Так, нельзя с помощью этого оператора перейти из основной программы в подпрограмму или выйти из подпрограммы, не рекомендуется осуществлять переход внутрь структурированного оператора, т.к. он может дать неправильный результат и т.д..». Добавлю, что в языке Паскаль в число структурированных операторов входят IF,CASE,REPEAT,WHILE,FOR и др.
Есть еще одна тонкость. В существующем варианте после ENDWHILE и ENDIF не обязательно проставлять точку с запятой. Но вот пример (опять с двумя вложенными WHILE), который отрабатывает немного не так, как бы хотелось (в каждом цикле захватывется оператор PRINT и печатается End!).
V=3;
WHILE(V>0)
V=V-1;
PRINT("AAAAAAA "+DblToStr(V));
H=6;
WHILE(H>0)
PRINT("BB"+DblToStr(H));
H=H-1;
ENDWHILE
ENDWHILE
;;;
L1:PRINT("End!");
;;;;
Если же поставить ; после ENDWHILE (первого), то все будет как надо. Таким образом, лучше предусмотреть проверку на наличие ; после ENDWHILE и ENDIF – так будет надежнее.
В папке Gramm95_016 есть уже не только BREAK, но и оператор CONTINUE. Из циклов WHILE нужно выходить с их помощью (или естественным образом – когда выражение в заголовке цикла станет нулевым).
Вот нормально отрабатываемый пример (внешний цикл завершается естественным образом, а внутренний – по Break):
V=3;
WHILE(V>0)
V=V-1;
PRINT("AAAAAAA "+DblToStr(V));
H=6;
WHILE(1>0)
PRINT("BB"+DblToStr(H));
H=H-1;
IF(H<3)
BREAK;
ENDIF;
ENDWHILE;
ENDWHILE;
;;;
L1:PRINT("End!");
;;;;
Также отмечу, что во всех предыдущих проектах я забыл ввести проверку деления на 0. Но это легко добавить.
В заключение этой главы – форма
Бэкуса-Наура:
<Программа>:=<Поток операторов>
<Поток операторов>:=[<Общий оператор>]
<Общий оператор>:=<Помеч.оператор>|<Оператор>
<Помеч.оператор>:=<Метка><:><Оператор>
<Оператор>:=<Присваивание>|<Вывод>|<jump>|<пустой>|<IfOp>
|<WhileOp>|<Break>|<Continue>
<IfOp>:=IF(<Вычисленное выражение>)<Поток операторов>[
ELSE<Поток операторов>]ENDIF
<WhileOp>:=WHILE(<Вычисленное выражение>)<Поток операторов>ENDWHILE
<jump>:=<GOTO><Метка><;>
<Пустой>:=<;>
<Присваивание>:=<Переменная>=<Вычисленное выражение><;>
<Вывод>:=PRINT(<Вычисленное выражение>)<;>
<Вычисленное выражение>:=<Expr2>
<expr2> := <expr1> {<бинарная, логическая><expr1>}
<expr1> := <expr> {<операция сравнения><expr>}
<expr> := <term>
{<add_op><term>}
<term> :=
<signed_factor>{<mul_op><signed_factor>}
<signed_factor> := [<add_op или лог-е отрицание>]<factor>
<factor> :=
<float>|”<string>”|(<expr2>)|<funcname>([<expr2>{,<expr2>}])
|<Переменная>
Здесь после ENDIF и ENDWHILE нет <;>, но, как я уже сказал выше, лучше ввести это требование. В дальнейшем это будет сделано.