О чём не пишут в книгах по Delphi
Шрифт:
Таким образом, наш калькулятор будет распознавать и вычислять цепочки чисел, между которыми стоят знаки операции, которые над этими числами выполняются. В вырожденном случае выражение может состоять из одного числа и, соответственно, не содержать ни одного знака операции. Опишем эти правила с помощью БНФ и ранее определенного символа
<Number>
. <Expr> ::= <Number> {<Operation> <Number>}
<Operation> ::= '+' | '-' | '*' | '/'
Примечание
В нашей грамматике не предусмотрено, что между оператором и его операндами может
Для написания калькулятора нам понадобятся две новых функции —
IsOperator
, которая проверяет, является ли следующий символ оператором, и Expr
, которая получает на входе строку, анализирует ее в соответствии с указанными правилами и вычисляет результат. Кроме того, функция IsNumber
сама по себе нам тоже больше не нужна — мы создадим на ее основе функцию Number
, которая получает на входе строку и номер позиции, начиная с которой в этой строке должно быть расположено число, проверяет, так ли это, и возвращает это число. Кроме того, функция Number
должна перемещать указатель на следующий после числа символ строки, чтобы функция Expr
, вызвавшая Number
, могла узнать, с какого символа продолжать анализ. Если последовательность символов не является корректным числом, функция Number
возбуждает исключение ESyntaxError
, определенное специально для указания на ошибку в записи выражения. Сама по себе задача преобразования строки в вещественное число достаточно сложна, и чтобы не отвлекаться на ее решение, мы воспользуемся функцией
StrToFloat
из модуля SysUtils
. Когда функция Number
выделит из строки последовательность символов, являющуюся числом, эта последовательность передается функции StrToFloat
, и преобразованием занимается она. Здесь следует учесть два момента. Во-первых, в нашей грамматике разделителем целой и дробной части является точка, a StrToFloat
использует системные настройки, т.е. разделителем может быть и запятая. Чтобы обойти эту проблему, слегка изменим синтаксис и будем сравнивать аргумент функции IsSeparator
не с символом ".", а с DecimalSeparator
(таким образом, наш калькулятор тоже станет чувствителен к системным настройкам). Во-вторых, не всякое выражение, соответствующее нашей грамматике, будет допустимым числом с точки зрения StrToFloat
, т.к. эта функция учитывает диапазон типа Extended
. Например, синтаксически верное выражение "2е5000" даст исключение EConvertError
, т.к. данное число выходит за пределы этого диапазона. Но пока мы остаемся в рамках типа Extended
, мы вынуждены мириться с этим. Новые функции приведены в листинге 4.3.
Листинг 4.3. Реализация простейшего калькулятора
// Выделение из строки подстроки, соответствующей
// определению <Number>, и вычисление этого числа
// S — строка, из которой выделяется подстрока
//
Р — номер позиции в строке, с которой должно
// начинаться число. После завершения работы функции
// этот параметр содержит номер первого после числа
function Number(const S: string; var P: Integer): Extended;
var
InitPos: Integer;
begin
// InitPos нам понадобится для выделения подстроки,
// которая будет передана в StrToFloat
InitPos := Р;
if (Р <= Length(S)) and IsSign(S[P]) then Inc(P);
if (P > Length(S)) or not IsDigit(S[P]) then
raise ESyntaxError.Create(
'Ожидается цифра в позиции ' + IntToStr(Р));
repeat
Inc(P);
until (P > Length(S)) or not IsDigit(S[P]);
if (P <= Length(S)) and IsSeparator(S[P]) then begin
Inc(P);
if (P > Length(S)) or not IsDigit(S[P]) then
raise ESyntaxError.Create(
'Ожидается цифра в позиции ' + IntToStr(Р));
repeat
Inc(P);
until (P > Length(S)) or not IsDigit(S[P]);
end;
if (P <= Length(S)) and IsExponent(S[P]) then
begin
Inc(P);
if Р > Length(S) then
raise ESyntaxError.Create('Неожиданный конец строки');
if IsSign(S[P]) then Inc(P);
if (P > Length(S)) or not IsDigit(S[P]) then
raise ESyntaxError.Create(
'Ожидается цифра в позиции ' + IntToStr(Р));
repeat
Inc(P);
until (P > Length(S)) or not IsDigit(S[P]);
end;
Result := StrToFloat(Copy(S, InitPos, P - InitPos));
end;
// Проверка символа на соответствие <Operator>
function IsOperator(Ch: Char): Boolean;
begin
Result := Ch in ['+', '-', '*', '/'];
end;
// Проверка строки на соответствие <Expr>
// и вычисление выражения
function Expr(const S: string): Extended;
var
P: Integer;
OpSymb: Char;
begin
P := 1;
Result := Number(S, P);
while (P <= Length(S)) and IsOperator(S[P]) do
begin
Поделиться:
Популярные книги
Жена проклятого некроманта
Фантастика:
фэнтези
6.60
рейтинг книги
Сын Тишайшего
1. Царь Федя
Фантастика:
попаданцы
альтернативная история
фэнтези
5.20
рейтинг книги
Демон
2. История одного эволюционера
Фантастика:
рпг
постапокалипсис
5.00
рейтинг книги
30 сребреников
1. 30 сребреников
Фантастика:
попаданцы
альтернативная история
фэнтези
фантастика: прочее
5.00
рейтинг книги
Небо в огне. Штурмовик из будущего
Военно-историческая фантастика
Фантастика:
боевая фантастика
7.42
рейтинг книги
Осознание. Пятый пояс
14. Путь
Фантастика:
героическая фантастика
5.00
рейтинг книги
Камень
1. Камень
Фантастика:
боевая фантастика
6.80
рейтинг книги
Блокада. Знаменитый роман-эпопея в одном томе
Проза:
военная проза
7.00
рейтинг книги
Цикл "Отмороженный". Компиляция. Книги 1-14
Отмороженный
Фантастика:
боевая фантастика
рпг
постапокалипсис
5.00
рейтинг книги
Книга 4. Игра Кота
4. ОДИН ИЗ СЕМИ
Фантастика:
фэнтези
боевая фантастика
рпг
6.68
рейтинг книги
Мастер 2
2. Мастер
Фантастика:
фэнтези
городское фэнтези
попаданцы
технофэнтези
4.50
рейтинг книги
Новый Рал 2
2. Рал!
Фантастика:
фэнтези
7.62
рейтинг книги
Низший 2
2. Низший!
Фантастика:
боевая фантастика
7.07
рейтинг книги
Под маской, или Страшилка в академии магии
Фантастика:
юмористическая фантастика
7.78