function Base(const S: string; var P: Integer): Extended;
begin
if P > Length(S) then
raise ESyntaxError.Create('Неожиданный конец строки');
// По первому символу подстроки определяем,
// какое это основание
case S[P] of
'(': // выражение в скобках
begin
Inc(Р);
Result := Expr(S, Р);
// Проверяем, что скобка закрыта
if (Р > Length(S)) or (S[P) <> ')') then
raise ESyntaxError.Create(
'Ожидается ")" в позиции ' + IntToStr(Р));
Inc(Р);
end;
'0'..'9': // Числовая константа
Result := Number(S, P);
'A'..'Z', 'a'..'z', '_': // Идентификатор (переменная или функция)
Result := Identifier(S, P);
else
raise ESyntaxError.Create(
'Некорректный символ в позиции ' + IntToStr(Р));
end;
end;
// Выделение подстроки, соответствующей <Factor>,
// и ее вычисление
function Factor(const S: string; var P: Integer): Extended;
begin
if P > Length(S) then
raise ESyntaxError.Create('Неожиданный конец строки');
// По первому символу подстроки определяем,
// какой это множитель
case S[P] of
'+'; // унарный "+"
begin
Inc(Р);
Result := Factor(S, P);
end;
'-': // унарный "-"
begin
Inc(P);
Result := -Factor(S, P);
end;
else
begin
Result := Base(S, P);
if (P <= Length(S)) and (S[P] = '^') then
begin
Inc(P);
Result := Power(Result, Factor(S, P));
end;
end;
end;
end;
Пример
калькулятора называется FullCalcSample. Его интерфейс (рис. 4.2) содержит новые элементы, с помощью которых пользователь может задавать значения переменных. В левой нижней части окна находится список переменных с их значениями (при запуске программы этот список пустой). Правее расположены поля ввода Имя переменной и Значение переменной, а также кнопка Установить. В первое поле следует ввести имя переменной, во второе — ее значение. При нажатии на кнопку Установить переменная будет внесена в список, а если переменная с таким именем уже есть в списке, то ее значение будет обновлено. Все переменные, которые есть в списке, могут использоваться в выражении. Если требуемая переменная в списке не найдена, попытка вычислить выражение приводит к ошибке.
Рис. 4.2. Главное окно программы FullCalcSample
Заметим, что символ
<Factor>
можно было бы определить несколько иначе:
<Factor> ::= [<UnaryOp>] <Base> ['^' <Factor>]
В нашем случае, когда есть только два унарных оператора и применение срезу двух (разных или одинаковых) практически бессмысленно, такой синтаксис реализовать было бы проще (пример реализации такого синтаксиса дан в программе FullCalcSample в виде комментария). При этом исчезла бы возможность ставить несколько знаков унарных операций подряд. В общем случае такой подход неверен, т.к. при большем количестве унарных операций это может пригодиться, да и выглядит естественно. Поэтому в качестве основного был выбран несколько более сложный, но и более функциональный вариант.
4.8. Калькулятор с лексическим анализатором
Прежде чем двигаться дальше, рассмотрим недостатки последней версии нашего калькулятора. Во-первых, бросается в глаза некоторое дублирование функций. Действительно, с одной стороны, выделением числа из подстроки занимается функция
Number
, но в функции
Base
также содержится проверка первого символа числа. Функция
Identifier
тоже частично дублируется функцией
Base
.
Второй недостаток — нельзя вставлять разделители, облегчающие чтение выражения. Например, строка "2 + 2" не является допустимым выражением — следует писать "2+2" (без пробелов). Если же попытаться учесть возможность вставки пробелов, придется в разные функции добавлять много однотипного рутинного кода, который существенно усложнит восприятие программы.
Третий недостаток — сложность введения новых операторов, которые обозначаются не одним символом, а несколькими, например,
>=
,
and
,
div
. Если посмотреть функции
Expr
и
Term
, которые придется в этом случае модифицировать, видно, что переделка будет достаточно сложной.
Решить все эти проблемы позволяет лексический анализатор, который выделяет из строки все лексемы, пропуская пробелы и иные разделители, и определяет тип каждой лексемы, не заботясь о том, насколько уместно ее появление в данной позиции выражения. А после лексического анализа начинает работать анализатор синтаксический, который будет иметь дело не с отдельными символами строки, а с целыми лексемами