3.1.1 Программа синтаксического разбора
Вот грамматика языка, допускаемого калькулятором:
program: END // END – это конец ввода expr_list END
expr_list: expression PRINT // PRINT – это или '\n' или ';' expression PRINT expr_list
expression: expression + term expression – term term
term: term / primary term * primary primary
primary: NUMBER // число с плавающей точкой в С++ NAME // имя С++ за исключением '_' NAME = expression – primary ( expression )
Другими словами, программа есть последовательность строк. Каждая строка состоит из одного или более выражений, разделенных запятой. Основными элементами выражения являются числа, имена и операции *, /, +, – (унарный и бинарный) и =. Имена не обязательно должны описываться до использования.
Используемый метод обычно называется рекурсивным спуском это популярный и простой нисходящий метод. В таком языке, как С++, в котором вызовы функций относительно дешевы, этот метод к тому же и эффективен. Для каждого правила вывода грамматики имеется функция, вызывающая другие функции. Терминальные символы (например, END, NUMBER, + и -) распознаются лексическим анализатором get_token(), а нетерминальные символы распознаются функциями синтаксического анализа expr(), term() и prim(). Как только оба операнда (под)выражения известны, оно вычисляется; в настоящем компиляторе в этой точке производится генерация кода.
Программа разбора для получения ввода использует функцию get_token(). Значение последнего вызова get_token() находится в переменной curr_tok; curr_tok имеет одно из значений перечисления token_value:
enum token_value (* NAME NUMBER END PLUS='+' MINUS='-' MUL='*' DIV='/' PRINT=';' ASSIGN='=' LP='(' RP=')' *); token_value curr_tok;
В каждой функции разбора предполагается, что было обращение к get_token(), и в curr_tok находится очередной символ, подлежащий анализу. Это позволяет программе разбора заглядывать на один лексический символ (лексему) вперед и заставляет функцию разбора всегда читать на одну лексему больше, чем используется правилом, для обработки которого она была вызвана. Каждая функция разбора вычисляет «свое» выражение и возвращает значение. Функция expr() обрабатывает сложение и вычитание; она состоит из простого цикла, который ищет термы для сложения или вычитания:
double expr() // складывает и вычитает (* double left = term();
for(;;) // ``навсегда`` switch(curr_tok) (* case PLUS: get_token(); // ест '+' left += term();
break; case MINUS: get_token(); // ест '-' left -= term(); break; default: return left; *) *)
Фактически сама функция делает не очень много. В манере, достаточно типичной для функций более высокого уровня в больших программах, она вызывает для выполнения работы другие функции. Заметьте, что выражение 2-3+4 вычисляется как (2-3)+ 4, как указано грамматикой.