Создание лексического анализатора простого языка программирования — различия между версиями

Материал из Вики ИТ мехмата ЮФУ
Перейти к: навигация, поиск
(Модуль лексического анализатора)
(Дополнительные задания (10 баллов - каждое по 2 балла))
 
(не показаны 53 промежуточные версии этого же участника)
Строка 1: Строка 1:
 
[[Страница_курса_%22Методы_построения_компиляторов%22| К основной странице курса]]
 
[[Страница_курса_%22Методы_построения_компиляторов%22| К основной странице курса]]
  
==== Предварительные замечания ====
+
=== Предварительные замечания ===
===== Лексемы языка: =====
+
==== Лексемы языка: ====
   ;  :=  +=  -=  *=  id  num  begin  end  cycle
+
   ;  :=  id  num  begin  end  cycle
  
 
Здесь  
 
Здесь  
Строка 10: Строка 10:
 
   begin  end  cycle - ключевые слова
 
   begin  end  cycle - ключевые слова
  
===== Глобальные переменные, необходимые для работы лексического анализатора: =====
+
==== Глобальные переменные, необходимые для работы лексического анализатора: ====
 
   fname: string;          // Имя файла программы
 
   fname: string;          // Имя файла программы
 
   LexRow,LexCol: integer;  // Строка-столбец начала лексемы. Конец лексемы = LexCol+Length(LexText)
 
   LexRow,LexCol: integer;  // Строка-столбец начала лексемы. Конец лексемы = LexCol+Length(LexText)
   Lex: TLex;               // Тип лексемы   
+
   LexKind: Tok;           // Тип лексемы   
 
   LexText: string;        // Текст лексемы
 
   LexText: string;        // Текст лексемы
 
   LexValue: integer;      // Целое значение, связанное с лексемой lexNum
 
   LexValue: integer;      // Целое значение, связанное с лексемой lexNum
  
Тип TLex является перечислимым и содержит все возможные лексемы нашего языка, а также специальную лексему lexEot конца текста:
+
Тип Tok является перечислимым и содержит все возможные лексемы нашего языка, а также специальную лексему EOFF конца текста:
   TLex = (lexId,lexNum,lexSemicolon,lexAssign,lexAssignPlus,lexAssignMinus,lexAssignMult,lexBegin,lexEnd,lexCycle,lexEot);
+
   Tok = (EOF, ID, INUM, COLON, SEMICOLON, ASSIGN, &BEGIN, &END, CYCLE);
 +
 
 +
==== Вспомогательные глобальные переменные, описанные в секции реализации модуля: ====
 +
  ch: Char;        // Текущий символ
 +
  f: text;          // Текущий файл
 +
  row,col: integer; // Текущие строка и столбец в файле
 +
  KeywordsMap := new Dictionary<string,TLex>; // Словарь, сопоставляющий ключевым словам константы типа TLex.
 +
                                              // Инициализируется процедурой InitKeywords
 +
 
 +
==== Процедура NextCh ====
 +
Помимо считывания следующего символа ch, процедура NextCh поддерживает в актуальном состоянии текущие строку-столбец (row,col). При достижении конца файла в переменную ch возвращается специальный символ #0
  
 +
==== Процедура InitKeywords ====
 +
Процедура InitKeywords инициализирует словарь ключевых слов KeywordsMap. Этот словарь ставит в соответствие строке ключевого слова константу типа TLex:
 +
KeywordsMap['begin'] := lexBegin;
  
==== Модуль лексического анализатора ====
+
=== Модуль лексического анализатора ===
  
 
<source lang="Delphi">
 
<source lang="Delphi">
Строка 28: Строка 41:
 
   Лексемы:
 
   Лексемы:
 
   ;  :=  +=  -=  *=  id  num  begin  end  cycle
 
   ;  :=  +=  -=  *=  id  num  begin  end  cycle
 
+
 
   Ключевые слова:
 
   Ключевые слова:
 
   begin  end  cycle
 
   begin  end  cycle
 
}
 
}
unit SimpleLangLexer1;
+
unit SimpleLangLexer;
 
+
 
interface
 
interface
 
+
 
// TLex - перечислимый тип - все лексемы грамматики
 
// TLex - перечислимый тип - все лексемы грамматики
 
// lexEot - конец текста программы
 
// lexEot - конец текста программы
 
type  
 
type  
   TLex = (lexId,lexNum,lexSemicolon,lexAssign,lexAssignPlus,lexAssignMinus,lexAssignMult,lexBegin,lexEnd,lexCycle,lexEot);
+
   Tok = (EOF, ID, INUM, COLON, SEMICOLON, ASSIGN, &BEGIN, &END, CYCLE);
 
+
 
var  
 
var  
 
   fname: string;          // Имя файла программы
 
   fname: string;          // Имя файла программы
 
   LexRow,LexCol: integer;  // Строка-столбец начала лексемы. Конец лексемы = LexCol+Length(LexText)
 
   LexRow,LexCol: integer;  // Строка-столбец начала лексемы. Конец лексемы = LexCol+Length(LexText)
   Lex: TLex;               // Тип лексемы   
+
   LexKind: Tok;           // Тип лексемы   
 
   LexText: string;        // Текст лексемы
 
   LexText: string;        // Текст лексемы
 
   LexValue: integer;      // Целое значение, связанное с лексемой lexNum
 
   LexValue: integer;      // Целое значение, связанное с лексемой lexNum
 
+
procedure NextCh;
 
procedure PassSpaces;
 
 
procedure NextLexem;
 
procedure NextLexem;
 
procedure Init(fn: string);
 
procedure Init(fn: string);
 
procedure Done;
 
procedure Done;
procedure lexerror(message: string := '');
+
function TokToString(t: Tok): string;
 
+
 
implementation
 
implementation
 
+
uses System.Collections.Generic;
 
 
 
 
var  
 
var  
 
   ch: Char;        // Текущий символ
 
   ch: Char;        // Текущий символ
 
   f: text;          // Текущий файл
 
   f: text;          // Текущий файл
 
   row,col: integer; // Текущие строка и столбец в файле
 
   row,col: integer; // Текущие строка и столбец в файле
   KeywordsMap := new Dictionary<string,TLex>; // Словарь, сопоставляющий ключевым словам константы типа TLex. Инициализируется процедурой InitKeywords
+
   KeywordsMap := new Dictionary<string,Tok>; // Словарь, сопоставляющий ключевым словам константы типа TLex. Инициализируется процедурой InitKeywords
 +
  
procedure lexerror(message: string);
+
procedure LexError(message: string); // ошибка лексического анализатора
 
begin
 
begin
 
   var ss := System.IO.File.ReadLines(fname).Skip(row-1).First(); // Строка row файла
 
   var ss := System.IO.File.ReadLines(fname).Skip(row-1).First(); // Строка row файла
Строка 73: Строка 83:
 
   if message<>'' then  
 
   if message<>'' then  
 
     writeln(message);
 
     writeln(message);
   Done;
+
   Done;
 
   halt;
 
   halt;
 
end;
 
end;
 
+
 
procedure NextCh;
 
procedure NextCh;
 
begin
 
begin
 
   // В LexText накапливается предыдущий символ и считывается следующий символ
 
   // В LexText накапливается предыдущий символ и считывается следующий символ
 
   LexText += ch;
 
   LexText += ch;
   if not eof(f) then
+
   if not f.Eof then
 
   begin
 
   begin
 
     read(f,ch);
 
     read(f,ch);
Строка 92: Строка 102:
 
     end;
 
     end;
 
   end
 
   end
   else ch := #0;
+
   else  
 +
  begin
 +
    ch := #0; // если достигнут конец файла, то возвращается #0
 +
    Done;
 +
  end;
 
end;  
 
end;  
 
+
 
procedure PassSpaces;
 
procedure PassSpaces;
 
begin
 
begin
Строка 100: Строка 114:
 
     NextCh;
 
     NextCh;
 
end;
 
end;
 
+
 
procedure NextLexem;
 
procedure NextLexem;
 
begin
 
begin
 
   PassSpaces;
 
   PassSpaces;
 +
  // R К этому моменту первый символ лексемы считан в ch
 
   LexText := '';
 
   LexText := '';
 
   LexRow := Row;
 
   LexRow := Row;
Строка 112: Строка 127:
 
   ';': begin
 
   ';': begin
 
         NextCh;
 
         NextCh;
         Lex := lexSemicolon;
+
         LexKind := Tok.SEMICOLON;
 
       end;   
 
       end;   
 
   ':': begin
 
   ':': begin
Строка 119: Строка 134:
 
           lexerror('= ожидалось');
 
           lexerror('= ожидалось');
 
         NextCh;
 
         NextCh;
         Lex := lexAssign;
+
         LexKind := Tok.ASSIGN;
      end;
 
  '+': begin
 
        NextCh;
 
        if ch<>'=' then
 
          lexerror('= ожидалось');
 
        NextCh;
 
        Lex := lexAssignPlus;
 
      end;
 
  '-': begin
 
        NextCh;
 
        if ch<>'=' then
 
          lexerror('= ожидалось');
 
        NextCh;
 
        Lex := lexAssignMinus;
 
      end;
 
  '*': begin
 
        NextCh;
 
        if ch<>'=' then
 
          lexerror('= ожидалось');
 
        NextCh;
 
        Lex := lexAssignMult;
 
 
       end;
 
       end;
 
   'a'..'z': begin
 
   'a'..'z': begin
Строка 146: Строка 140:
 
           NextCh;
 
           NextCh;
 
         if KeywordsMap.ContainsKey(LexText) then
 
         if KeywordsMap.ContainsKey(LexText) then
           Lex := KeywordsMap[LexText]
+
           LexKind := KeywordsMap[LexText]
         else Lex := lexId;
+
         else LexKind := Tok.ID;
 
       end;
 
       end;
 
   '0'..'9': begin
 
   '0'..'9': begin
         while ch in ['0'..'9'] do
+
         while char.IsDigit(ch) do
 
           NextCh;
 
           NextCh;
 
         LexValue := integer.Parse(LexText);
 
         LexValue := integer.Parse(LexText);
         Lex := lexNum;
+
         LexKind := Tok.INUM;
 
       end;
 
       end;
     #0: Lex := lexEot;   
+
     #0: LexKind := Tok.EOF;   
 
     else lexerror('Неверный символ '+ch);
 
     else lexerror('Неверный символ '+ch);
 
   end;   
 
   end;   
 
end;
 
end;
 
+
 
procedure InitKeywords;
 
procedure InitKeywords;
 
begin
 
begin
   KeywordsMap['begin'] := lexBegin;
+
   KeywordsMap['begin'] := Tok.&BEGIN;
   KeywordsMap['end'] := lexEnd;
+
   KeywordsMap['end'] := Tok.&END;
   KeywordsMap['cycle'] := lexCycle;
+
   KeywordsMap['cycle'] := Tok.CYCLE;
 
end;
 
end;
 
+
 
procedure Init(fn: string);
 
procedure Init(fn: string);
 
begin
 
begin
 
   InitKeywords;
 
   InitKeywords;
 
   fname := fn;
 
   fname := fn;
   assign(f,fname);
+
   AssignFile(f,fname);
 
   reset(f);
 
   reset(f);
 
   row := 1; col := 1;
 
   row := 1; col := 1;
   NextCh;
+
   NextCh;   // Считать первый символ в ch
   NextLexem;
+
   NextLexem; // Считать первую лексему, заполнив LexText, LexKind и, возможно, LexValue
 
end;
 
end;
 
+
 
procedure Done;
 
procedure Done;
 
begin
 
begin
 
   close(f);
 
   close(f);
 +
end;
 +
 +
function TokToString(t: Tok): string;
 +
begin
 +
  Result := t.ToString;
 +
  case t of
 +
Tok.ID:  Result += ' ' + LexText;
 +
Tok.INUM: Result += ' ' + LexValue;
 +
  end;
 
end;
 
end;
 
   
 
   
end.
+
end.</source>
 +
 
 +
=== Основная программа ===
 +
<source lang="Pascal">
 +
uses SimpleLangLexer;
 +
 
 +
begin
 +
  Init('a.txt');
 +
  repeat
 +
    writeln(TokToString(LexKind));
 +
    NextLexem;
 +
  until LexKind = Tok.EOF;
 +
end.</source>
 +
 
 +
=== Основное задание ===
 +
Разобраться в программе. Откомпилировать программу (0 баллов)
  
</source>
+
=== Дополнительные задания (10 баллов - каждое по 2 балла)===
 +
# Добавить распознавание лексем  , : + - * / div mod and or not
 +
# Добавить распознавание лексем += -= *= /=. Тщательно продумать, как совместить распознавание лексем с одинаковым префиксом: например, + и +=
 +
# Добавить распознавание лексем > < >= <= = <>
 +
# Добавить пропуск комментариев // - до конца строки
 +
# Добавить пропуск комментариев { комментарий до закрывающей фигурной скобки }. Обратить внимание, что незакрытый до конца файла комментарий - это синтаксическая ошибка

Текущая версия на 12:33, 22 декабря 2016

К основной странице курса

Предварительные замечания

Лексемы языка:

 ;  :=  id  num  begin  end  cycle

Здесь

 id - идентификатор
 num - целое без знака
 begin  end  cycle - ключевые слова

Глобальные переменные, необходимые для работы лексического анализатора:

 fname: string;           // Имя файла программы
 LexRow,LexCol: integer;  // Строка-столбец начала лексемы. Конец лексемы = LexCol+Length(LexText)
 LexKind: Tok;           // Тип лексемы   
 LexText: string;         // Текст лексемы
 LexValue: integer;       // Целое значение, связанное с лексемой lexNum

Тип Tok является перечислимым и содержит все возможные лексемы нашего языка, а также специальную лексему EOFF конца текста:

 Tok = (EOF, ID, INUM, COLON, SEMICOLON, ASSIGN, &BEGIN, &END, CYCLE);

Вспомогательные глобальные переменные, описанные в секции реализации модуля:

 ch: Char;         // Текущий символ
 f: text;          // Текущий файл
 row,col: integer; // Текущие строка и столбец в файле
 KeywordsMap := new Dictionary<string,TLex>; // Словарь, сопоставляющий ключевым словам константы типа TLex. 
                                             // Инициализируется процедурой InitKeywords

Процедура NextCh

Помимо считывания следующего символа ch, процедура NextCh поддерживает в актуальном состоянии текущие строку-столбец (row,col). При достижении конца файла в переменную ch возвращается специальный символ #0

Процедура InitKeywords

Процедура InitKeywords инициализирует словарь ключевых слов KeywordsMap. Этот словарь ставит в соответствие строке ключевого слова константу типа TLex:

KeywordsMap['begin'] := lexBegin;

Модуль лексического анализатора

// Распознавание программы на простом языке. Распознаватель лексем (лексер)
{ 
  Лексемы:
  ;  :=  +=  -=  *=  id  num  begin  end  cycle
 
  Ключевые слова:
  begin  end  cycle
}
unit SimpleLangLexer;
 
interface
 
// TLex - перечислимый тип - все лексемы грамматики
// lexEot - конец текста программы
type 
  Tok = (EOF, ID, INUM, COLON, SEMICOLON, ASSIGN, &BEGIN, &END, CYCLE);
 
var 
  fname: string;           // Имя файла программы
  LexRow,LexCol: integer;  // Строка-столбец начала лексемы. Конец лексемы = LexCol+Length(LexText)
  LexKind: Tok;            // Тип лексемы   
  LexText: string;         // Текст лексемы
  LexValue: integer;       // Целое значение, связанное с лексемой lexNum
 
procedure NextLexem;
procedure Init(fn: string);
procedure Done;
function TokToString(t: Tok): string;
 
implementation
 
var 
  ch: Char;         // Текущий символ
  f: text;          // Текущий файл
  row,col: integer; // Текущие строка и столбец в файле
  KeywordsMap := new Dictionary<string,Tok>; // Словарь, сопоставляющий ключевым словам константы типа TLex. Инициализируется процедурой InitKeywords
 

procedure LexError(message: string); // ошибка лексического анализатора
begin
  var ss := System.IO.File.ReadLines(fname).Skip(row-1).First(); // Строка row файла
  writeln('Лексическая ошибка в строке ',row,':');
  writeln(ss);
  writeln('^':col-1);
  if message<>'' then 
    writeln(message);
  Done;
  halt;
end;
 
procedure NextCh;
begin
  // В LexText накапливается предыдущий символ и считывается следующий символ
  LexText += ch;
  if not f.Eof then
  begin
    read(f,ch);
    if ch<>#10 then
      col += 1
    else
    begin
      row += 1;
      col := 1;
    end;
  end
  else 
  begin
    ch := #0; // если достигнут конец файла, то возвращается #0
    Done;
  end;
end; 
 
procedure PassSpaces;
begin
  while char.IsWhiteSpace(ch) do
    NextCh;
end;
 
procedure NextLexem;
begin
  PassSpaces;
  // R К этому моменту первый символ лексемы считан в ch
  LexText := '';
  LexRow := Row;
  LexCol := Col;
// Тип лексемы определяется по ее первому символу
// Для каждой лексемы строится синтаксическая диаграмма
  case ch of
  ';': begin
         NextCh;
         LexKind := Tok.SEMICOLON;
       end;  
  ':': begin
         NextCh;
         if ch<>'=' then 
           lexerror('= ожидалось');
         NextCh;
         LexKind := Tok.ASSIGN;
       end;
  'a'..'z': begin
         while ch in ['a'..'z','0'..'9'] do
           NextCh;
         if KeywordsMap.ContainsKey(LexText) then
           LexKind := KeywordsMap[LexText]
         else LexKind := Tok.ID;
       end;
  '0'..'9': begin
         while char.IsDigit(ch) do
           NextCh;
         LexValue := integer.Parse(LexText);
         LexKind := Tok.INUM;
       end;
    #0: LexKind := Tok.EOF;   
    else lexerror('Неверный символ '+ch);
  end;  
end;
 
procedure InitKeywords;
begin
  KeywordsMap['begin'] := Tok.&BEGIN;
  KeywordsMap['end'] := Tok.&END;
  KeywordsMap['cycle'] := Tok.CYCLE;
end;
 
procedure Init(fn: string);
begin
  InitKeywords;
  fname := fn;
  AssignFile(f,fname);
  reset(f);
  row := 1; col := 1;
  NextCh;    // Считать первый символ в ch
  NextLexem; // Считать первую лексему, заполнив LexText, LexKind и, возможно, LexValue
end;
 
procedure Done;
begin
  close(f);
end;

function TokToString(t: Tok): string;
begin
  Result := t.ToString;
  case t of
Tok.ID:   Result += ' ' + LexText;
Tok.INUM: Result += ' ' + LexValue; 
  end;
end;
 
end.

Основная программа

uses SimpleLangLexer;

begin
  Init('a.txt');
  repeat
    writeln(TokToString(LexKind));
    NextLexem;
  until LexKind = Tok.EOF;
end.

Основное задание

Разобраться в программе. Откомпилировать программу (0 баллов)

Дополнительные задания (10 баллов - каждое по 2 балла)

  1. Добавить распознавание лексем , : + - * / div mod and or not
  2. Добавить распознавание лексем += -= *= /=. Тщательно продумать, как совместить распознавание лексем с одинаковым префиксом: например, + и +=
  3. Добавить распознавание лексем > < >= <= = <>
  4. Добавить пропуск комментариев // - до конца строки
  5. Добавить пропуск комментариев { комментарий до закрывающей фигурной скобки }. Обратить внимание, что незакрытый до конца файла комментарий - это синтаксическая ошибка