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

Материал из Вики ИТ мехмата ЮФУ
Перейти к: навигация, поиск
(Модуль синтаксического анализатора (парсера))
(Дополнительные задания (5 баллов))
 
(не показано 49 промежуточных версий этого же участника)
Строка 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)
 
Грамматика составлена так, что по ней можно составить ручной анализатор методом рекурсивного спуска.
 
Грамматика составлена так, что по ней можно составить ручной анализатор методом рекурсивного спуска.
 
==== Модуль узлов синтаксического дерева ====
 
<source lang="Delphi">unit ProgramTree;
 
 
uses
 
  System.Collections.Generic,
 
  SimpleLangLexer;
 
 
 
type
 
  Node = class // базовый класс для всех узлов   
 
  end;
 
 
 
  ExprNode = class(Node) // базовый класс для всех выражений
 
  end;
 
 
  IdNode = class(ExprNode)
 
    name: string;
 
    constructor (name: string);
 
    begin
 
      Self.name := name;
 
    end;
 
  end;
 
 
  NumNode = class(ExprNode)
 
    num: integer; 
 
    constructor (num: integer);
 
    begin
 
      Self.num := num;
 
    end;
 
  end;
 
 
 
  StatementNode = class(Node) // базовый класс для всех операторов
 
  end; 
 
 
 
  AssignOpType = lexAssign..lexAssignMult;
 
 
 
  AssignNode = class(StatementNode)
 
    id: IdNode;
 
    expr: ExprNode;
 
    assop: AssignOpType;
 
    constructor (id: IdNode; expr: ExprNode; assop: AssignOpType);
 
    begin
 
      Self.id := id;
 
      Self.expr := expr;
 
      Self.assop := assop;
 
    end;
 
  end;
 
 
 
  CycleNode = class(StatementNode)
 
    ex: ExprNode;
 
    st: StatementNode;
 
    constructor (ex: ExprNode; st: StatementNode);
 
    begin
 
      Self.ex := ex;
 
      Self.st := st;
 
    end;
 
  end;
 
 
  StatementList = class
 
    stlist: List<StatementNode>;
 
    constructor ();
 
    begin
 
      stlist := new List<StatementNode>();
 
    end;
 
    constructor (st: StatementNode);
 
    begin
 
      stlist := new List<StatementNode>();
 
      stlist.Add(st);
 
    end;
 
  end;
 
 
 
  BlockNode = class(StatementNode)
 
    sl: StatementList;
 
    constructor (sl: StatementList);
 
    begin
 
      Self.sl := sl;
 
    end;
 
  end;
 
 
 
  ProgrNode = class(Node)
 
    body: BlockNode;
 
    constructor (body: BlockNode);
 
    begin
 
      Self.body := body;
 
    end;
 
  end;
 
 
 
var root: ProgrNode;
 
 
 
end.</source>
 
 
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)

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