приведенный здесь код будет вам понятен. Некоторых комментариев требует только функция
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. Окончательный вариант грамматики выражения со скобками
Здесь опущены определения некоторых вспомогательных символов, которые не изменились.
Мы видим, что грамматика стала "более рекурсивной", т.е. в определении символа
<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;