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

Материал из Вики ИТ мехмата ЮФУ
Перейти к: навигация, поиск
(Модуль синтаксического анализатора (парсера))
(Дополнительные задания (5 баллов))
 
(не показаны 52 промежуточные версии этого же участника)
Строка 1: Строка 1:
 
[[Страница_курса_%22Методы_построения_компиляторов%22| К основной странице курса]]
 
[[Страница_курса_%22Методы_построения_компиляторов%22| К основной странице курса]]
 +
__NOTOC__
 +
=== Комментарии к синтаксическому анализатору===
 +
Мы пишем синтаксический анализатор '''методом рекурсивного спуска'''. Для этого грамматика должна быть ''праворекурсивной''.
  
==== Модуль синтаксического анализатора (парсера) ====
+
Грамматика нашего языка имеет вид:
 +
  progr -> block
 +
  stlist -> statement ; stlist | statement
 +
  statement -> assign | block | cycl
 +
  assign -> ID := expr
 +
  expr -> ID | INUM
 +
  block -> BEGIN stlist END
 +
  cycl -> CYCLE expr st 
 +
 
 +
Здесь правило
 +
  stlist -> statement ; stlist | statement
 +
праворекурсивно. Следует обратить внимание, что точка с запятой разделяет операторы, а не завершает их.
 +
 
 +
Примеры правильных программ в данной грамматике:
 +
 
 +
<source lang="Delphi">begin
 +
  a := 2
 +
end</source>
 +
 
 +
 
 +
 
 +
<source lang="Pascal">begin
 +
  a := 2;
 +
  cycle a
 +
  begin
 +
    b := a;
 +
    c := 234
 +
  end
 +
end</source>
 +
 
 +
 
 +
<source lang="Pascal">
 +
begin
 +
  b := 2
 +
end</source>
 +
 
 +
 
 +
Примеры неправильных программ в данной грамматике:
 +
<pre>
 +
a := 2;
 +
</pre>
 +
 
 +
<pre>
 +
begin
 +
  b := 2
 +
end.
 +
</pre>
 +
 
 +
При написании синтаксического анализатора методом рекурсивного спуска  следует для каждого нетерминала реализовать процедуру, которая будет распознавать этот нетерминал. При реализации необходимо придерживаться следующей стратегии: перед вызовом каждой процедуры для нетерминала считается, что первая лексема уже считана с помощью NextLexem. Кроме того, каждая процедура после распознавания своей конструкции считывает следующую лексему с помощью NextLexem, подготавливая вызов следующей процедуры.
 +
 
 +
Ошибки, распознаваемые синтаксическим анализатором, называются '''синтаксическими''' и выводятся вызовом процедуры syntaxerror.
 +
 
 +
Отметим также, что приведенный парсер (синтаксический анализатор) не выполняет никаких семантических действий - он только распознает, принадлежит ли программа языку, порождаемому данной грамматикой.
 +
 
 +
=== Модуль синтаксического анализатора (парсера) ===
 
<source lang="Delphi">
 
<source lang="Delphi">
// Распознавание программы на простом языке. Распознаватель грамматики (парсер)
+
unit SimpleLangParser;
{ Грамматика:
+
  progr -> stlist
 
  stlist -> st ; stlist | st
 
  st -> assign | comp | cycl
 
  assign -> id signa expr
 
  signa -> := | += | -= | *=
 
  expr -> id | num
 
  comp -> begin stlist end
 
  cycl -> cycle expr st 
 
}
 
unit SimpleLangParser1;
 
 
 
 
interface
 
interface
 
+
 +
procedure Progr;
 
procedure Expr;
 
procedure Expr;
 
procedure Assign;
 
procedure Assign;
procedure StList;
+
procedure StatementList;
procedure St;  
+
procedure Statement;  
procedure Comp;
+
procedure Block;
 
procedure Cycle;
 
procedure Cycle;
procedure Progr;
+
procedure syntaxerror(message: string := '');
procedure error(message: string := '');
+
 
 
 
implementation
 
implementation
 
+
 
uses  
 
uses  
   System.Collections.Generic,
+
   SimpleLangLexer;
  System.IO,
+
  SimpleLangLexer1;
 
 
 
 
procedure Progr;
 
procedure Progr;
 
begin
 
begin
   StList
+
   Block
 
end;
 
end;
 
+
procedure StList;
+
procedure Statement;
begin
 
  St;
 
  while Lex=lexSemicolon do
 
  begin
 
    NextLexem;
 
    St;
 
  end
 
end;
 
 
 
procedure St;
 
 
begin
 
begin
   case Lex of
+
   case LexKind of
lexBegin: Comp;  
+
Tok.&Begin: Block;  
lexCycle: Cycle;
+
Tok.Cycle: Cycle;
lexId: Assign;
+
Tok.Id: Assign;
else error('Ожидался оператор');
+
else syntaxerror('Ожидался оператор');
 
   end;
 
   end;
 
end;
 
end;
 
+
procedure Comp;
+
procedure Block;
 
begin
 
begin
   NextLexem;
+
   NextLexem; // Пропуск begin
   StList;
+
   StatementList;
   if Lex=lexEnd then
+
   if LexKind=Tok.&End then
 
     NextLexem
 
     NextLexem
   else error('Ожидалось end');     
+
   else syntaxerror('Ожидалось end');     
 
end;
 
end;
 
+
 
procedure Cycle;
 
procedure Cycle;
 
begin
 
begin
   NextLexem;
+
   NextLexem; // Пропуск cycle
 
   Expr;
 
   Expr;
   St;
+
   Statement;
 
end;
 
end;
 
+
 
procedure Assign;
 
procedure Assign;
 
begin
 
begin
   NextLexem;
+
   NextLexem; // Пропуск id
   if Lex in [lexAssign,lexAssignPlus,lexAssignMinus,lexAssignMult] then
+
   if LexKind = Tok.ASSIGN then
 
     NextLexem
 
     NextLexem
   else error('Ожидалось :=');   
+
   else syntaxerror('Ожидалось :=');   
 
   Expr;   
 
   Expr;   
 
end;
 
end;
 
+
procedure Expr;
+
procedure StatementList;
 +
begin
 +
  Statement;
 +
  while LexKind=Tok.SEMICOLON do
 +
  begin
 +
    NextLexem;
 +
    Statement;
 +
  end
 +
end;
 +
 +
procedure Expr; // Выражение - это id или num
 
begin
 
begin
   if lex in [lexId,lexNum] then
+
   if LexKind in [Tok.ID,Tok.INUM] then
 
     NextLexem  
 
     NextLexem  
   else error('Ожидалось выражение');   
+
   else syntaxerror('Ожидалось выражение');   
 
end;
 
end;
 
+
procedure error(message: string);
+
procedure syntaxerror(message: string);
 
begin
 
begin
 
   var ss := System.IO.File.ReadLines(fname).Skip(LexRow-1).First(); // Строка row файла
 
   var ss := System.IO.File.ReadLines(fname).Skip(LexRow-1).First(); // Строка row файла
Строка 103: Строка 147:
 
end;
 
end;
 
   
 
   
end.
+
end.</source>
</source>
 
 
 
Основная программа:
 
<source lang="Delphi">uses SimpleLangLexer1,SimpleLangParser1;
 
  
 +
====Основная программа====
 +
<source lang="Delphi">uses SimpleLangLexer,SimpleLangParser;
 +
 
begin  
 
begin  
 
   Init('progr.txt');
 
   Init('progr.txt');
 
   Progr;
 
   Progr;
   if Lex=lexEot then
+
   if LexKind=Tok.EOF then
 
     writeln('программа распознана правильно ')
 
     writeln('программа распознана правильно ')
   else error('ожидался конец файла');  
+
   else syntaxerror('ожидался конец файла');  
 
end.</source>
 
end.</source>
  
'''Задания'''
+
====Основные задания (7 баллов)====
# Добавить цикл for
+
# Добавить оператор WHILE expr DO statement(в лексический анализатор - добавлять новые ключевые слова) '''(3 балла)'''
# Добавить простые логические выражения вида x<0 (в лексический анализатор придется добавлять знаки отношений, а в expr - ветку id_or_num relop id_or_num)
+
# Добавить оператор FOR ID := expr TO expr DO statement(в лексический анализатор - добавлять новые ключевые слова) '''(4 балла)'''
# Добавить грамматику выражений
+
 
 +
====Дополнительные задания (7 баллов)====
 +
# Добавить оператор IF expr THEN stat ELSE stat (предусмотреть полную и неполную форму оператора) (3 балла)
 +
# Добавить грамматику выражений (4 балла)
 
  E ::= T A
 
  E ::= T A
 
  A ::= ε | + T A | - T A
 
  A ::= ε | + T A | - T A
Строка 127: Строка 173:
 
  M ::= id | num | (E)
 
  M ::= id | num | (E)
 
Грамматика составлена так, что по ней можно составить ручной анализатор методом рекурсивного спуска.
 
Грамматика составлена так, что по ней можно составить ручной анализатор методом рекурсивного спуска.
4. Создать модуль, содержащий все узлы программы: ExprNode, AssignNode, CompNode и пр.
 
5. Добавить семантические действия, строящие дерево программы с корнем root: ProgrNode
 

Текущая версия на 14:32, 22 октября 2015

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

Комментарии к синтаксическому анализатору

Мы пишем синтаксический анализатор методом рекурсивного спуска. Для этого грамматика должна быть праворекурсивной.

Грамматика нашего языка имеет вид:

 progr -> block 
 stlist -> statement ; stlist | statement
 statement -> assign | block | cycl
 assign -> ID := expr
 expr -> ID | INUM
 block -> BEGIN stlist END
 cycl -> CYCLE expr st   

Здесь правило

 stlist -> statement ; stlist | statement

праворекурсивно. Следует обратить внимание, что точка с запятой разделяет операторы, а не завершает их.

Примеры правильных программ в данной грамматике:

begin
  a := 2
end


begin
  a := 2;
  cycle a
  begin
    b := a;
    c := 234
  end
end


begin
  b := 2
end


Примеры неправильных программ в данной грамматике:

a := 2;
begin
  b := 2
end.

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

Ошибки, распознаваемые синтаксическим анализатором, называются синтаксическими и выводятся вызовом процедуры syntaxerror.

Отметим также, что приведенный парсер (синтаксический анализатор) не выполняет никаких семантических действий - он только распознает, принадлежит ли программа языку, порождаемому данной грамматикой.

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

unit SimpleLangParser;
 
interface
 
procedure Progr;
procedure Expr;
procedure Assign;
procedure StatementList;
procedure Statement; 
procedure Block;
procedure Cycle;
procedure syntaxerror(message: string := '');
 
implementation
 
uses 
  SimpleLangLexer;
 
procedure Progr;
begin
  Block
end;
 
procedure Statement;
begin
  case LexKind of
Tok.&Begin: Block; 
Tok.Cycle: Cycle;
Tok.Id: Assign;
else syntaxerror('Ожидался оператор');
  end;
end;
 
procedure Block;
begin
  NextLexem; // Пропуск begin
  StatementList;
  if LexKind=Tok.&End then
    NextLexem
  else syntaxerror('Ожидалось end');    
end;
 
procedure Cycle;
begin
  NextLexem; // Пропуск cycle
  Expr;
  Statement;
end;
 
procedure Assign;
begin
  NextLexem; // Пропуск id
  if LexKind = Tok.ASSIGN then
    NextLexem
  else syntaxerror('Ожидалось :=');  
  Expr;  
end;
 
procedure StatementList;
begin
  Statement;
  while LexKind=Tok.SEMICOLON do
  begin
    NextLexem;
    Statement;
  end
end;
 
procedure Expr; // Выражение - это id или num
begin
  if LexKind in [Tok.ID,Tok.INUM] then
    NextLexem 
  else syntaxerror('Ожидалось выражение');  
end;
 
procedure syntaxerror(message: string);
begin
  var ss := System.IO.File.ReadLines(fname).Skip(LexRow-1).First(); // Строка row файла
  writeln('Синтаксическая ошибка в строке ',LexRow,':');
  writeln(ss);
  writeln('^':LexCol-1);
  if message<>'' then 
    writeln(message);
  Done;  
  halt;
end;
 
end.

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

uses SimpleLangLexer,SimpleLangParser;
 
begin 
  Init('progr.txt');
  Progr;
  if LexKind=Tok.EOF then
    writeln('программа распознана правильно ')
  else syntaxerror('ожидался конец файла'); 
end.

Основные задания (7 баллов)

  1. Добавить оператор WHILE expr DO statement(в лексический анализатор - добавлять новые ключевые слова) (3 балла)
  2. Добавить оператор FOR ID := expr TO expr DO statement(в лексический анализатор - добавлять новые ключевые слова) (4 балла)

Дополнительные задания (7 баллов)

  1. Добавить оператор IF expr THEN stat ELSE stat (предусмотреть полную и неполную форму оператора) (3 балла)
  2. Добавить грамматику выражений (4 балла)
E ::= T A
A ::= ε | + T A | - T A
T ::= M B
B ::= ε | * M B | / M B
M ::= id | num | (E)

Грамматика составлена так, что по ней можно составить ручной анализатор методом рекурсивного спуска.