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.


Модуль CMPSCN имеет следующую реализацию:

**

** 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 ;

};