ЛАБОРАТОРНАЯ РАБОТА № 2. СИНТАКСИЧЕСКИЙ АНАЛИЗ МЕТОДОМ РЕКУРСИВНОГО СПУСКА



Цель лабораторной работы.

 

 Изучение методов синтаксического анализа, получение навыков в  программировании алгоритмов синтаксического разбора.

 

Теоретическое введение

 

Постановка задачи синтаксического анализа. Организация данных. Общая схема алгоритмов детерминированного разбора

 

Синтаксический анализ (разбор) - это процесс, в котором исследуется цепочка лексем и устанавливается, удовлетворяет ли она условиям, сформулированным в определении синтаксиса языка /1/. Выходом синтаксического анализатора является синтаксическое дерево разбора, которое представляет синтаксическую структуру исходной программы.

Пример.

Зададим грамматику G(V,T,P,S) следующим набором правил (полагая, что терминальные символы в правых частях правил представлены соответствующими кодами лексем):

1) <S> ::= <E>

2) <E> ::= <T> + <E>

3) <E> ::= <T>

4) <T> ::= <F> * <T>

5) <T> ::= <F>

6) <F> ::= i

В результате синтаксического анализа цепочки i*i+i может быть построено синтаксическое дерево разбора (рис. 1) .

 

                               <S>

                                 │

               ┌─────<E>─────┐

               │           │                 

           ┌──<T>────┐ +     <E>

           │ │ │       │

          <F>* <T>    <T>

           │    │        │

           i    <F>    <F>

                       │        │

                       i           i

 

Рис.2.1. Синтаксическое дерево разбора

 

Метод рекурсивного спуска

 

Пример: пусть дана грамматика G =({a,b,c, ^}, {S,A,B}, P, S), где

P: S   AB^

A   a | cA

    B   bA

и надо определить, принадлежит ли цепочка caba языку L(G).

Построим вывод этой цепочки:

S AB^ cAB^  caB^  cabA^ caba^

Следовательно, цепочка принадлежит языку L(G).

Последовательность применений правил вывода эквивалентна построению дерева разбора методом "сверху вниз":

 

 

 

 

Метод рекурсивного спуска (РС-метод) реализует этот способ практически "в лоб": для каждого нетерминала грамматики создается своя процедура, носящая его имя; ее задача - начиная с указанного места исходной цепочки найти подцепочку, которая выводится из этого нетерминала. Если такую подцепочку считать не удается, то процедура завершает свою работу вызовом процедуры обработки ошибки, которая выдает сообщение о том, что цепочка не принадлежит языку, и останавливает разбор. Если подцепочку удалось найти, то работа процедуры считается нормально завершенной и осуществляется возврат в точку вызова. Тело каждой такой процедуры пишется непосредственно по правилам вывода соответствующего нетерминала: для правой части каждого правила осуществляется поиск подцепочки, выводимой из этой правой части. При этом терминалы распознаются самой процедурой, а нетерминалы соответствуют вызовам процедур, носящих их имена.

 

Пример: совокупность процедур рекурсивного спуска для грамматики

 G = ({a,b,c,^}, {S,A,B}, P, S), где

P: S  AB^

    A a | cA

    B bA

будет такой:

#include <stdio.h>

int c;

FILE *fp;/*указатель на файл, в котором находится анализируемая цепочка */

void A();

void B();

void ERROR(); /* функция обработки ошибок */

void S() {A(); B();

    if (c != '^') ERROR();

    }

void A() {if (c=='a') c = fgetc(fp);

    else if (c == 'c') {c = fgetc(fp); A();}

        else ERROR();

    }

void B() {if (c == 'b') {c = fgetc(fp); A();}

     else ERROR();

    }

void ERROR() {printf("ERROR !!!\n"); exit(1);}

main() {fp = fopen("data","r");

   c = fgetc(fp);

   S();

   printf("SUCCESS !!!\n"); exit(0);

  }

 


Дата добавления: 2019-02-12; просмотров: 763; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!