1.11.6. СКАНЕР Результатом работы сканера является поток лексем, каждая из которых представлена двумя символами. Первый символ указывает на класс лексемы. В дальнейшем мы будем описывать синтаксис объектных выражений с помощью расширенной БНФ, нетерминалы которой записаны в виде переменных Рефала Плюс. При этом мы предполагаем, что значения нетерминалов должны соответствовать их типам. Таким образом, лексемы, поступающие из сканера имеют следующий синтаксис:
$ e.Tokens = { e.Token }.
$ e.Token =
$ Key s.Key | Name s.Name | Value s.Value |
$ Char s.Char.
$ s.Key = s.Word.
$ s.Name = s.Word.
$ s.Value = s.Int.
Лексема вида Key s.Key изображает ключевое слово, где s.Key - символ-слово, печатное изображение которого соответствует ключевому слову. Лексема вида Name s.Name изображает имя переменной, где s.Name - символ-слово, печатное изображение которого соответствует идентификатору - имени переменной. Лексема вида Value s.Value изображает числовую константу, где s.Value - соответствующий символ-число. Лексема вида Char s.Char изображает неопознанную литеру s.Char. Когда исходная программа прочитана до конца, сканер выдает в конце лексему Key Eof.**
** File: CMPSCN.RF
**
$use STDIO;
$use CLASS;
$use CONVERT;
$use BOX;
$func Scan-Token
s.Chl e.Line = s.TokenKey s.TokenInfo (e.Line1);
$func Scan-Id-Rest
(e.Id-Chars) e.Chars = s.TokenKey s.Word (e.Rest);
$func Scan-Int-Rest
(e.Int-Chars) e.Chars = s.TokenKey s.Int (e.Rest);
$func? Blank? s.Char = ;
$func? One-Char-Token? s.Char = ;
$func? Compound-Token? s.Char e.Line = s.Word e.Rest;
$func? KeyWord? s.Word = ;
** Ящики для хранения канала, из которого читаются лексемы,
** и остатка очередной строки.
$box Scan-Chl Scan-Line;
Init-Scanner s.Chl = ** Инициализация сканера.
<Store &Scan-Chl s.Chl>, ** Прячем канал в ящик.
<Store &Scan-Line >; ** Текущая строка - пустая.
Term-Scanner = ** Завершение работы.
<Store &Scan-Chl >, ** Забываем канал
<Store &Scan-Line >; ** и текущую строку.
Read-Token = ** Чтение лексемы.
<? &Scan-Chl> : s.Chl,
<? &Scan-Line> :: e.Line,
<Scan-Token s.Chl e.Line>
:: s.TokenKey s.TokenInfo (e.Line),
<Store &Scan-Line e.Line>,
= s.TokenKey s.TokenInfo;
Scan-Token s.Chl e.Line =
e.Line :
{
= ** Остаток строки -
{ ** пустой. Читаем
<Read-Line! s.Chl> :: e.Line ** следующую.
= <Scan-Token s.Chl e.Line>;
= Key Eof (); ** Конец файла.
};
s.Char e.Rest = ** Изучаем очередную
{ ** литеру.
<Blank? s.Char>
= <Scan-Token s.Chl e.Rest>;
<Letter? s.Char>
= <Scan-Id-Rest (s.Char) e.Rest>;
<Digit? s.Char>
= <Scan-Int-Rest (s.Char) e.Rest>;
<One-Char-Token? s.Char>
= Key <To-Word s.Char> (e.Rest);
<Compound-Token? s.Char e.Rest> :: s.Word e.Rest
= Key s.Word (e.Rest);
= Char s.Char (e.Rest); ** Неопознанная литера.
};
};
** Отщепляем остаток идентификатора.
Scan-Id-Rest (e.Id-Chars) e.Rest =
{
e.Rest : s.Char e.Rest1,
\{<Letter? s.Char>; <Digit? s.Char>;}
= <Scan-Id-Rest (e.Id-Chars s.Char) e.Rest1>;
= <To-Word <To-Upper e.Id-Chars>> : s.Word,
{<KeyWord? s.Word> = Key; = Name;} :: s.TokenKey,
= s.TokenKey s.Word (e.Rest);
};
** Отщепляем остаток целого числа.
Scan-Int-Rest (e.Int-Chars) e.Rest =
{
e.Rest : s.Char e.Rest1, <Digit? s.Char>
= <Scan-Int-Rest (e.Int-Chars s.Char) e.Rest1>;
= Value <To-Int e.Int-Chars> (e.Rest);
};
Blank? s.Char = ** "Межевая" литера?
' \n\t' : e s.Char e;
One-Char-Token? s.Char = ** Однолитерная лексема?
';()+-*/' : e s.Char e;
Compound-Token? ** Пытаемся отщепить
\{ ** многолитерную лексему.
':=' e.Rest = ":=" e.Rest;
'<=' e.Rest = "<=" e.Rest;
'<>' e.Rest = "<>" e.Rest;
'<' e.Rest = "<" e.Rest;
'>=' e.Rest = ">=" e.Rest;
'>' e.Rest = ">" e.Rest;
'=' e.Rest = "=" e.Rest;
};
KeyWord? ** Идентификатор - ключевое слово?
\{
DO ; ELSE ; IF ; READ ; THEN ; WHILE ; WRITE ;
};