О чём не пишут в книгах по 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
Поделиться:
Популярные книги
Изгой. Трилогия
Изгой
Фантастика:
фэнтези
8.45
рейтинг книги
Старатель 2
2. Старатели
Фантастика:
боевая фантастика
космическая фантастика
5.00
рейтинг книги
Купеческая дочь замуж не желает
Фантастика:
фэнтези
6.89
рейтинг книги
Кодекс Охотника. Книга IX
9. Кодекс Охотника
Фантастика:
боевая фантастика
городское фэнтези
попаданцы
5.00
рейтинг книги
Школа Семи Камней
10. Real-Rpg
Фантастика:
фэнтези
рпг
5.00
рейтинг книги
Тайны ордена
6. Девятый
Фантастика:
боевая фантастика
попаданцы
7.48
рейтинг книги
Убивать чтобы жить 9
9. УЧЖ
Фантастика:
героическая фантастика
боевая фантастика
рпг
5.00
рейтинг книги
Мимик нового Мира 11
10. Мимик!
Фантастика:
юмористическое фэнтези
постапокалипсис
рпг
5.00
рейтинг книги
Пришествие бога смерти. Том 5
5. Ленивое божество
Фантастика:
юмористическое фэнтези
попаданцы
аниме
5.00
рейтинг книги
Аномальный наследник. Том 3
2. Аномальный наследник
Фантастика:
фэнтези
7.74
рейтинг книги
Опер. Девочка на спор
5. Опасная работа
Любовные романы:
современные любовные романы
эро литература
5.00
рейтинг книги
Имя нам Легион. Том 4
4. Меж двух миров
Фантастика:
боевая фантастика
рпг
аниме
5.00
рейтинг книги
Приручитель женщин-монстров. Том 8
8. Покемоны? Какие покемоны?
Фантастика:
юмористическое фэнтези
аниме
5.00
рейтинг книги
Личник
3. Ермак
Фантастика:
альтернативная история
6.33