Реализация компилятора. Организация лексического блока

Страницы работы

Содержание работы

Реализация компилятора

Организация лексического блока

Назначение лексического блока

Как мы уже ранее говорили, основным назначением ЛБ является разбиение цепочки литер, представляющей программу на исходном языке, в цепочку символов языка, опознание этих цепочек, начальное заполнение таблиц, замена символов кодами. При этом, если в программе были комментарии, то ЛБ их должен удалить.

Т.о. ЛБ представляет собой программу, в которой:

1)  открывается на чтение файл с текстом программы на исходном языке программирования;

2)  открывается на запись файл, в который будем заносить версию программы на исходном языке программирования в виде кодов символов;

3)  читаем построчно файл с исходным текстом программы;

4)  обрабатываем строку:

·  удаляем комментарии;

·  выделяем очередную цепочку литер до ближайшего разделителя, распознаем ее и записываем в файл в виде кода;

·  все это продолжаем, пока не дойдем до конца считанной строки;

5)  пункты 3,4 повторяем до тех пор, пока не дойдем до конца файла.

Реализация лексического блока

Прежде чем рассматривать вопросы программной реализации ЛБ необходимо задать коды символов (лексем). Всего может быть 4 группы символов:

1)  служебные слова;

2)  разделители;

3)  идентификаторы;

4)  константы.

 Коды всех этих символов лучше всего задать символическими константами и потом использовать их в программе. Это можно сделать, например,  т.о.:

Для служебных слов:

#define            TTL        1

#define            END       2

#define            READ    3

#define            WRITE   4

#define            PBASE   5

#define            Int          6       //имя INT  C++Builder уже использует

#define            REAL     7

#define            POW      8

#define            ABS       9

#define            DIV      10

#define            OR       11

#define            AND     12

#define            NOT     13

или через перечисляемый тип:

enum Term {

  TTL=1,END,READ,WRITE,PBASE,Int, REAL,POW,ABS,DIV,OR,AND,NOT

};

Для разделителей:

#define            PLUS             16

#define            MINUS           17

#define            UMINUS        18

#define            MUL               19

#define            EQ                   20

#define            LT                   21

#define            GT                   22

#define            LE                    23

#define            GE                   24

#define            NE                   25

#define            LB                    26

#define            RB                   27

#define            COMMA        28

#define            SCOL               29

или:

enum Delim {

  PLUS=16,MINUS,UMINUS,MUL,EQ,LT,GT,LE,GE,NE,LB,RB,COMMA,SCOL

};

Для идентификаторов:

#define            BEGTBL        31

#define            ENDTBL        59

Для констант:

#define            BEGLIT          61

#define            ENDLIT         99

Разбиение цепочки входных литер на цепочки символов и их опознание может быть выполнено фрагментами программ вида:

Головная функция для ЛБ

. . .

out=fopen(lname, "w");

Form3->Memo1->Lines->Clear();

Memo2->Lines->Clear();

Memo2->Visible = false;

lerr=0;

for (int i = 0; i < Memo1->Lines->Count; i++){

Lex(Memo1->Lines->Strings[i].c_str(), i+1);

if (lerr) Memo2->Visible =  true;

fclose(out);

Form3->ShowModal();

     . . .

Разбиение:

  .   .   .

void Lex(char *s, int ns){

  char ls[80], str[160], *pstr, *ps, errstr[80];

  ps=s;

  *str='\0'; pstr=str;

  while(*s!='\0'){

        s=NextLex(s,ls);  lcod=LexRecogn(ls);

        if (lcod>0) { sprintf(pstr,"%02d ",lcod); pstr += 3; }

/*можно: pos= sprintf(pstr,"%02d ",lcod); pstr += pos;

Где локальная переменная : int pos;*/

        else { lerr++; sprintf(errstr,"Неопознанная лексема %s в строке %d", ls, ns);

        Memo2->Lines->Add(errstr);

     }

   }

   sprintf(pstr,"00 %s",ps); //Чтобы вывести после кодов исходный текст

   Form3->Memo1->Lines->Add(str);

   fprintf(out,"%s\n",str);

}

.   .   .

Здесь предполагается, что имелись глобальные описания и функции:

FILE *out;                     //выходной файл с кодами лексем

int  lcod;                         //содержит код лексемы ( норма >0)

int lerr=0;                      //количество ошибок лексического блока

int LexRecogn(char *);  //функция, распознающая лексему и возвращающая ее код >0

Form1->Memo2 – для вывода сообщений об ошибках, если они будут.

Опознание:

LexRecogn(char *s){

    int cod;      

    if((cod=TermRecogn (s)) = = 0)

        if((cod=IdRecogn (s)) = = 0)

            if((cod=LitRecogn (s)) = = 0)

                if((cod=DlmRecogn (s)) = = 0)

                     return(0);

                return(cod);

}

Прежде чем рассматривать вопросы распознавания символов, рассмотрим понятие конечного автомата.

Конечные автоматы

На практике вопросы организации лексического блока очень тесно связаны с понятием конечного автомата. Конечный автомат - это устройство для распознавания строк языка, имеющее конечное множество состояний. По мере считывания литер из входной строки, автомат меняет свое состояние в соответствии с заданным множеством переходов. Конечное состояние автомата после считывания последней литеры строки определяет результат распознавания.

Таким образом, на вход конечного автомата подается цепочка литер из алфавита нашего языка, а автомат либо принимает (распознает), либо отвергает эту цепочку. Грамматика, реализуемая конечным автоматом, должна быть линейной. Сам конечный автомат задается пятеркой множеств:

M=(K, T, d, S, F)

где  K - конечное множество состояний;

T - конечный входной алфавит;

d- множество переходов;

S - начальное состояние (S Î K);

F - множество конечных, допускающих состояний (F Ì K).

Рассмотрим пример реализации конечного автомата для распознавания идентификатора.

Грамматика для этого случая имеет вид:

G=(N, T, P, S)                    N={S, Z}                 T={l,  d}

P={ S àl | lZ

       Z à l | lZ |d | dZ

     }

Описание автомата имеет вид:

M=(K, T, d, S,F)       K={S, Z }     T={+, -, d}

l

d

\0

S

Z

0

Z

Z

Z

1

         d  =

Здесь d означает понятие "цифра", а l – "буква".

Ниже представлен пример программной реализации этого автомата в предположении, что распознаваемая цепочка символов представлена отдельной строкой. В дальнейшем также будем предполагать, что на вход конечного автомата подается строка литер, полученная выделением из входной строки всех литер до первого символа-разделителя (см. лабораторную работу 2).

IsId (char *s){

if (isalpha(*s) { //перешли в состояние Z после появления 1-ой буквы

    s++;

    while (isdigit(*s) || isalpha(*s)) s++;

    /* проверка на допускающее состояние */

    if (*s== '\0') return(1); else return 0;

}else return(0);

}

или по-другому (хотя это и не совсем по таблице переходов) :

IsId (char *s){

if (!isalpha(*s++) return 0;

//перешли в состояние Z после появления 1-ой буквы

while (isdigit(*s) || isalpha(*s)) s++;

 /* проверка на допускающее состояние */

if (*s== '\0') return(1); else return 0;

}

Рассмотрим пример реализации конечного автомата для распознавания целого числа.

Грамматика для этого случая имеет вид:

G=(N, T, P, S)                    N={S, U}                 T={ d}

P={ S àU

       U à d | dU

     }

Описание автомата имеет вид:

M=(K, T, d, S,F)       K={S, U }     T={ d}

D

\0

S

U

0

U

U

1

         d  =

Пример программной реализации этого автомата.

IsNumb (char *s){

if (isdigit(*s) { //перешли в состояние U после появления 1-ой цифры

    s++;

    while (isdigit(*s)) s++;

    /* проверка на допускающее состояние */

Похожие материалы

Информация о работе