О чём не пишут в книгах по Delphi
Шрифт:
Побочным эффектом нашей грамматики стало то, что, например,
– 5
воспринимается как множитель, а потому перед ним допустимо поставить унарный оператор, т. е. выражение – -5
также является корректным множителем и трактуется как – (-5)
. А перед – -5
, в свою очередь, можно поставить еще один унарный оператор. И так — до бесконечности. Это может показаться не совсем правильным, но, тем не менее, такая грамматика широко распространена. Легко, например, убедиться, что компилятор Delphi считает допустимым выражение 2+-+-2
, трактуя его как 2+(-(+(-2)))
. Листинг 4.8 иллюстрирует
Листинг 4.8. Реализация калькулятора со скобками
// Так как грамматика рекурсивна, функция Expr
// должна быть объявлена заранее
function Expr(const S: string; var Р: Integer): Extended; forward;
// Выделение подстроки, соответствующей <Factor>,
// и ее вычисление
function Factor(const S: string; var P: Integer): Extended;
begin
if P > Length(S) then
raise ESyntaxError.Create('Неожиданный конец строки');
// По первому символу подстроки определяем,
// какой это множитель
case S[Р] of
'+': // унарный "+"
begin
Inc(Р);
Result := Factor(S, P);
end;
'-': // унарный "-"
begin
Inc(P);
Result := -Factor(S, P);
end;
'(': // выражение в скобках
begin
Inc(P);
Result := Expr(S, P);
// Проверяем, что скобка закрыта
if (Р > Length(S)) or (S[P] <> ')') then
raise ESyntaxError.Create(
'Ожидается ")" в позиции ' + IntToStr(P));
Inc(P);
end;
'0'..'9': // Числовая константа
Result := Number(S, P);
else
raise ESyntaxError.Create(
'Некорректный символ в позиции ' + IntToStr(Р));
end;
end;
// Выделение подстроки, соответствующей <Term>,
// и ее вычисление
function Term(const S: string; var P: Integer): Extended;
var
OpSymb: Char;
begin
Result := Factor(S, P);
while (P <= Length(S)) and IsOperator2(S[P]) do
begin
OpSymb := S[P];
Inc(P);
case OpSymb of
'*': Result := Result * Factor(S, P);
'/': Result := Result / Factor(S, P);
end;
end;
end;
//
Выделение подстроки, соответствующей <Expr>,
// и ее вычисление
function Expr(const S: string; var Р: Integer): Extended;
var
OpSymb: Char;
begin
Result := Term(S, P);
while (P <= Length(S)) and IsOperator1(S[P]) do
begin
OpSymb := S[P];
Inc(P);
case OpSymb of
'+': Result := Result + Term(S, P);
'-': Result := Result - Term(S, P);
end;
end;
end;
// Вычисление выражения
function Calculate(const S: string): Extended;
var
P: Integer;
begin
P := 1;
Result := Expr(S, P);
if P <= Length(S) then
raise ESyntaxError.Create(
'Некорректный символ в позиции ' + IntToStr(Р));
end;
По сравнению с предыдущим примером функция
Term
осталась такой же с точностью до замены вызовов Number
на новую функцию Factor
. Функция Factor
выделяет подстроку, отвечающую отдельному множителю. Множители, напомним, могут быть трех типов: число, выражение в скобках, множитель с унарным оператором. Различить их можно по первому символу подстроки. Функция Factor
распознает тип множителя и вызывает соответствующую функцию для его вычисления. Функция
Expr
теперь может применяться не только к выражению в целом, но и к отдельной подстроке. Поэтому она, как и все остальные функции, теперь имеет параметр-переменную P
, через который передается начало и конец этой подстроки. Из функции убрана проверка того, что в результате ее использования строка проанализирована полностью, т.к. теперь допустим анализ части строки. Функция
Expr
в своем новом виде стала не очень удобной для конечного пользователя, поэтому была описана еще одна функция — Calculate
. Это вспомогательная функция, которая избавляет пользователя от вникания в детали "внутренней кухни" калькулятора, т.е. использования переменной P
и проверки того, что строка проанализирована до конца. Пример калькулятора со скобками записан на компакт-диске под названием BracketsCalcSample. Анализируя его код, можно заметить, что по сравнению с предыдущим примером незначительно изменена функция
Number
— из нее в соответствии с новой грамматикой убрана проверка знака в начале выражения. 4.7. Полноценный калькулятор
Последняя версия нашего калькулятора может считать сложные выражения, но чтобы он имел практическую ценность, этого мало. В этом разделе мы научим наш калькулятор использовать функции и переменные. Также будет введена операция возведения в степень, обозначающаяся значком "
^
".
Поделиться с друзьями: