Чтение онлайн

ЖАНРЫ

О чём не пишут в книгах по Delphi

Григорьев Антон Борисович

Шрифт:

function IsOperator1(Ch: Char): Boolean;

begin

 Result := Ch in ['+', '-'];

end;

// Проверка символа на соответствие <Operator2>

function IsOperator2(Ch: Char): Boolean;

begin

 Result := Ch in ['*', '/'];

end;

// Выделение подстроки, соответствующей <Term>,

// и ее вычисление

function Term(const S: string; var P: Integer): Extended;

var

 OpSymb: Char;

begin

 Result := Number(S,P);

 while (P <= Length(S)) and IsOperator2(S[P]) do

 begin

OpSymb := S[P];

Inc(P);

case OpSymb of

'*': Result := Result * Number(S, P);

'/': Result := Result / Number(S, P);

 end;

end;

//
Проверка строки на соответствие <Expr>

// и вычисление выражения

function Expr(const S: string): Extended;

var

 P: Integer;

 OpSymb: Char;

begin

 P := 1;

 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;

 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), которые не являются ни знаками, ни допустимыми бинарными операциями.

Поделиться с друзьями: