Предположим, что необходимо провести анализ выражения, записанного в строке, и построить дерево разбора. В выражении допускаются бинарные операции +, *, числа и скобки.
Такие выражения описываются следующей грамматикой:
expr0 : expr1 { '+' expr1 } ;
expr1 : expr2 { '*' expr2 } ;
expr2 : '(' expr0 ')' | NUM ;
Здесь символы :, |, ;, {, } — специальные символы, используемые для записи грамматики.
Определим константы, перечисляющие возможные виды лексических единиц (лексем):
enum
{
OP_EOF, // конец строки
OP_NUM, // число
OP_PLUS, // знак '+'
OP_STAR, // знак '*'
OP_LBR, // знак '('
OP_RBR // знак ')'
};
Определим тип узла дерева, который будет использоваться как для передачи информации от лексического анализатора к синтаксическому, так и для построения дерева.
typedef struct Tree
{
int opcode; // код лексемы (см. выше)
struct Tree *left; // левое поддерево
struct Tree *right; // правое поддерево
int num; // значение числа
} Tree;
Для хранения текущего состояния анализатора будем использовать глобальные переменные.
static const unsigned char *parse_str; // анализируемая строка static int parse_pos; // текущая позиция в строке static Tree *parse_token; // текущая лексема
Работа лексического анализатора заключается в создании узла типа Tree для лексемы в текущей позиции текста и продвижении текущей позиции вперед по строке.
Фрагмент лексического анализатора приведен ниже:
Tree *
get_token()
{
if (!parse_str[parse_pos]) {
// достигли конца строки
return tree_create_token(OP_EOF);
}
// проверим, что в текущей позиции находится знак операции
int opcode = OP_EOF;
switch (parse_str[parse_pos]) {
case '+':
opcode = OP_PLUS;
break;
case '(':
opcode = OP_LBR;
break;
// прочие символы операций...
}
if (opcode) {
parse_pos++;
return tree_create_token(opcode);
}
if (isdigit(parse_str[parse_pos])) {
// создание лексемы для числа
}
}
Работа синтаксического анализатора заключается в инициализации переменных состояния и запуска рекурсивной функции разбора. После завершения разбора нужно проверить, что вся входная строка прочитана.
Tree *
parse_expr(const unsigned char *str)
{
parse_str = str; // задать строку для разбора
parse_pos = 0; // установить позицию в строке
parse_token = get_token(); // считать первую лексему
Tree *t = parse_expr0(); // разобрать строку
if (parse_token->opcode != OP_EOF) { // проверить, что дошли до конца
tree_free(t);
t = 0;
}
tree_free(parse_token);
return t;
}
Функция, разбирающая выражения на слагаемые, может выглядеть примерно так:
Tree *
parse_expr0(void)
{
Tree *t1 = parse_expr1();
if (!t1) return 0;
while (parse_token->opcode == OP_PLUS) {
Tree *t2 = parse_token;
parse_token = get_token();
Tree *t3 = parse_expr1();
if (!t3) {
tree_free(t1);
tree_free(t2);
return 0;
}
t2->left = t1;
t2->right = t3;
t1 = t2;
}
return t1;
}
Функция для анализа одного множителя (то есть либо числа, либо выражения в скобках) может выглядеть следующим образом:
Tree *
parse_expr2(void)
{
if (parse_token->opcode == OP_NUM) {
Tree *t = parse_token;
parse_token = get_token();
return t;
} else if (parse_token->opcode == OP_LBR) {
tree_free(parse_token);
parse_token = get_token();
Tree *t = parse_expr0();
if (t == NULL) {
return NULL;
}
if (parse_token->opcode != OP_RBR) {
tree_free(t);
return NULL;
}
parse_token = get_token();
return t;
} else {
return NULL;
}
}