Программист, использующий Yacc должен написать сам (или создать при
помощи программы типа Lex) внешний лексический анализатор, который
будет читать символы из входного потока (какого - это внутреннее дело
лексического анализатора), обнаруживать терминальные символы (токены)
и передавать их синтаксическому анализатору, построенному Yaccом (возможно
вместе с неким значением) для дальнейшего разбора. Лексический анализатор
должен быть оформлен как функция с именем yylex
,
возвращающая значение типа int
, которое представляет
собой тип (номер) обнаруженного во входном потоке токена. Если есть
желание вернуть еще некое значение (например номер в группе), оно
может быть присвоено глобальной, внешней (по отношению
к yylex
) переменной по имени yylval
.
Лексический анализатор и Yacc должны использовать одинаковые номера
токенов, чтобы понимать друг друга. Эти номера обычно выбираются Yaccом,
но могут выбираться и человеком. В любом случае механизм define
языка Си позволяет лексическому анализатору возвращать эти значения
в символическом виде. Предположем, что токен по имени DIGIT
был определен в секции объявлений спецификации Yaccа, тогда соответствующий
текст лексического анализатора может выглядеть так:
yylex()
{
extern int yylval;
int c;
...
c = getchar();
...
switch (c) {
. . .
case '0':
case '1':
...
case '9':
yylval = c - '0';
return DIGIT;
. . .
}
. . .
Вышеприведенный фрагмент возвращает номер токена DIGIT
и значение,
равное цифре. Если при этом сам текст лексического анализатора был
помещен в секцию программ спецификации Yacca - есть гарантия, что
идентификатор DIGIT
был определен номером токена DIGIT
, причем тем
самым, который ожидает Yacc.
Такой механизм позволяет создавать понятные, легкие в модификации
лексические анализаторы. Единственным ограничением является запрет
на использование в качестве имени токена слов, зарезервированых или часто используемых
в языке Си слов. Например, использование в качестве имен токенов таких
слов как if
или while
, почти
наверняка приведет к возникновению проблем при компиляции лексического
анализатора. Кроме этого, имя error
зарезервировано
для токена, служащего делу обработки ошибок, и не должно использоваться.
Как уже было сказано, номера токенов выбираются либо Yaccом,
либо человеком, но чаще Yaccом, при этом для отдельных символов
(например для (
или ;
) выбирается
номер, равный ASCII коду этого символа. Для других токенов номера выбираются
начиная с 257.
Для того, чтобы присвоить токену (или даже литере) номер вручную, необходимо в секции объявлений после имени токена добавить положительное целое число, которое и станет номером токена или литеры, при этом необходимо позаботиться об уникальности номеров. Если токену не присвоен номер таким образом, Yacc присваивает ему номер по своему выбору.
По традици, концевой маркер должен иметь номер токена, равный, либо меньший нуля, и лексический анализатор должен возвращать ноль или отрицательное число при достижении конца ввода (или файла).
Очень неплохим средством для создания лексических анализаторов является программа Lex. Лексические анализаторы, построенные с ее помощью прекрасно гармонируют с синтаксическими анализаторами, построенными Yaccом. Lex можно легко использовать для построения полного лексического анализатора из файла спецификаций, основанного на системе регулярных выражений (в отличие от системы грамматических правил для Yacca), но, правда, существуют языки (например Фортран) не попадающие ни под какую теоретическю схему, но для них приходится писать лексический анализатор вручную.