Прежде всего, сама грамматика не сверху вниз или снизу вверх, синтаксический анализатор (хотя есть грамматики, которые могут быть проанализированы одной, но не другой).
С практической точки зрения основное отличие состоит в том, что большинство рукописных синтаксических анализаторов являются нисходящими, тогда как гораздо больший процент генерируемых машиной синтаксических анализаторов являются восходящими (хотя, конечно, обратное, безусловно, возможно).
В синтаксическом анализаторе сверху вниз обычно используется рекурсивный спуск, что обычно означает структуру, подобную этой (используя типичные математические выражения в качестве примера):
expression() { term() [-+] expression }
term() { factor() [*/] term() }
factor() { operand() | '(' expression() ')' }
Восходящий синтаксический анализатор работает в обратном направлении - когда анализатор рекурсивного спуска начинается с полного выражения и разбивает его на более мелкие и мелкие фрагменты, пока не достигнет уровня отдельных токенов, запускается восходящий синтаксический анализатор из отдельных токенов и использует таблицы правил о том, как эти токены вписываются в более высокие и более высокие уровни иерархии выражений, пока не достигнут верхнего уровня (что обозначено как «выражение» выше).
Редактировать: Чтобы уточнить, возможно, имеет смысл добавить действительно тривиальный парсер. В этом случае я просто сделаю старый классический способ преобразования упрощенной версии типичного математического выражения из инфикса в постфикс:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void expression(void);
void show(int ch) {
putchar(ch);
putchar(' ');
}
int token() {
int ch;
while (isspace(ch=getchar()))
;
return ch;
}
void factor() {
int ch = token();
if (ch == '(') {
expression();
ch = token();
if (ch != ')') {
fprintf(stderr, "Syntax error. Expected close paren, found: %c\n", ch);
exit(EXIT_FAILURE);
}
}
else
show(ch);
}
void term() {
int ch;
factor();
ch = token();
if (ch == '*' || ch == '/') {
term();
show(ch);
}
else
ungetc(ch, stdin);
}
void expression() {
int ch;
term();
ch = token();
if (ch == '-' || ch=='+') {
expression();
show(ch);
}
else
ungetc(ch, stdin);
}
int main(int argc, char **argv) {
expression();
return 0;
}
Обратите внимание, что лексизм здесь довольно глупый (он в основном просто принимает один символ в качестве токена) и допустимые выражения весьма ограничены (только + - * /). ОТО, это достаточно хорошо для обработки ввода, как:
1 + 2 * (3 + 4 * (5/6))
из которого он производит то, что я считаю правильным выводом:
1 2 3 4 5 6 / * + * +