Категории
Самые читаемые
PochitayKnigi » Компьютеры и Интернет » Программирование » О чём не пишут в книгах по Delphi - А. Григорьев

О чём не пишут в книгах по Delphi - А. Григорьев

Читать онлайн О чём не пишут в книгах по Delphi - А. Григорьев

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 116 117 118 119 120 121 122 123 124 ... 131
Перейти на страницу:

 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;

 if P <= Length(S) then

  raise ESyntaxError.Create(

   'Некорректный символ в позиции ' + IntToStr(Р));

end;

Если вы разобрались с предыдущими примерами, приведенный здесь код будет вам понятен. Некоторых комментариев требует только функция Term. Она выделяет, начиная с заданного символа, ту часть строки, которая соответствует определению <Term>. Вызвавшая ее функция Expr должна продолжить разбор выражения со следующего за этой подстрокой символа, поэтому функция Term, как и Number, имеет параметр-переменную P, которая на входе содержит номер первого символа слагаемого, а на выходе — номер первого после этого слагаемого символа.

Пример калькулятора, учитывающего приоритет операций, находится на компакт-диске под именем PrecedenceCalcSample. Поэкспериментировав с ним, легко убедиться, что теперь вычисление "2+2*2" дает правильное значение 6.

В заключение заметим, что язык, определяемый такой грамматикой, полностью совпадает с языком, определяемым грамматикой из предыдущего примера, т.е. любое выражение, принадлежащее первому языку, принадлежит и второму, и наоборот. Усложнение синтаксиса, которое мы здесь ввели, требуется именно для отражения семантики выражений, а не для расширения самого языка.

4.6. Выражения со скобками

Порядок выполнения операций в выражении может меняться с помощью скобок. Внутри них должно находиться выражение, которое, будучи выделенным в отдельную строку, само по себе отвечает требованиям синтаксиса к выражению в целом.

Выражение, заключенное в скобки, допустимо везде, где допускается появление отдельного числа (из этого, в частности, следует, что допускаются вложенные скобки). Таким образом, мы должны расширить нашу грамматику так, чтобы аргументом операций сложения и умножения могли служить не только числа, но и выражения, заключенные в скобки. Это автоматически позволит использовать такие выражения и в качестве слагаемых, потому что слагаемое — это последовательность из одного или нескольких множителей, разделенных знаками умножения и деления. На языке БНФ все сказанное иллюстрирует листинг 4.6.

Листинг 4.6. Грамматика выражения со скобками (первое приближение)

<Expr> ::= <Term> {<Operation1> <Term>}

<Term> ::= <Factor> {<Operation2> <Factor>}

<Factor> ::= <Number> | ' (' <Expr> ')'

В этих определениях появилась рекурсия, т.к. в определении <Expr> используется (через <Term>) символ <Factor>, а в определении <Factor> — <Term>. Соответственно, подобная грамматика будет реализовываться рекурсивными функциями.

Наша грамматика не учитывает, что перед скобками может стоять знак унарной операции "+" или "-", хотя общепринятые правила записи выражений вполне допускают выражения типа 3*-(2+4). Поэтому, прежде чем приступить к созданию нового калькулятора, введем правила, допускающие такой синтаксис. Можно было бы модифицировать определение <Factor> таким образом:

<Factor> ::= <Number> | [Sign] '(' <Expr> ')'

Однако такой подход страдает отсутствием общности. В дальнейшем мы усложним наш синтаксис, введя другие типы множителей (функции, переменные). Перед каждым из них, в принципе, может стоять знак унарной операции, поэтому логичнее определить синтаксис таким образом, чтобы унарная операция допускалась вообще перед любым множителем. В этом случае можно будет слегка упростить определение <Number>, т.к. знак "+" или "-" в начале числа можно будет трактовать не как часть числа, а как унарный оператор, стоящий перед множителем, представленным в виде числовой константы.

С учетом этого новая грамматика запишется следующим образом (листинг 4.7).

Листинг 4.7. Окончательный вариант грамматики выражения со скобками

<Expr> ::= <Term> {<Operation1> <Term>}

<Term> ::= <Factor> {<Operation2> <Factor>}

<Factor> ::= <UnaryOp> <Factor> | <Number> | '(' <Expr> ')'

<Number> ::= <Digit> {<Digit>} [<Separator> <Digit> {<Digit>}]

 [<Exponent> [<Sign>] <Digit> {<Digit>}]

<UnaryOp> ::= '+' | '-'

Здесь опущены определения некоторых вспомогательных символов, которые не изменились.

Мы видим, что грамматика стала "более рекурсивной", т.е. в определении символа <Factor> используется он сам. Соответственно, функция Factor будет вызывать саму себя.

Символ <UnaryOp>, определение которого совпадает с определениями <Operator1> и <Sign>, мы делаем независимым нетерминальным символом по тем же причинам, что и ранее: в принципе, синтаксис может допускать унарные операции (как, например, not в 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, через который передается начало и конец этой подстроки. Из функции убрана проверка того, что в результате ее использования строка проанализирована полностью, т.к. теперь допустим анализ части строки.

1 ... 116 117 118 119 120 121 122 123 124 ... 131
Перейти на страницу:
Тут вы можете бесплатно читать книгу О чём не пишут в книгах по Delphi - А. Григорьев.
Комментарии