Приклад перевірки контексних умов
Розглянемо приклад перевірки контекстних умов при обробці оператора опису у мові типу С.
Розглянемо граматику з такими правилами:
<оператор_опису>::=<тип><список>;
<список>::=<ідентифікатор>{ , <ідентифікатор>}
Нагадаємо, що у прикладі програмування лексичного аналізатора, розглянутого раніше, формувався потік лексем-дескрипторів, які описувалась парою
syn, lex[lexsize],
де символьна змінна syn призначалась для позначення синтаксичної категорії лексеми, у символьний масив-рядок lex[lexsize] записувалась конкретна лексема (ідентифікатор, число, службове слово, розділювач). Так, для ідентифікаторів було вибрано syn=’i’, а для односимвольних розділювачів
, ;
syn=’,’ і syn=’;’ відповідно. Припустимо, що для службових слів int і float було вибрано syn=’1’ і syn=’2’ відповідно.
Тоді наведені правила граматики можна переписати у вигляді
O → ( 1 | 2 ) i { , i }
Для проведення синтаксичного контролю ланцюжків, що виводяться з нетермінального символа О, можна запропонувати таку процедуру:
void Op_opysy ( )
{ switch (syn)
{ case ‘1’:
case ‘2’: scan ( ) ;
if (syn != ‘i’) { f=0; puts(“Nema identyfikatora u spysku”); }
else { scan( ); while (syn==’,’)
{ scan( ); if (syn != ‘i’) { f=0; puts(“Nema identyfikatora pislja comu”); }
else scan( ) ; }
if (syn == ‘;’) scan( ); else { f=0; puts(“Nema ; pislja spysku”); }
break;
default: f=0; puts(“Nema typu”);
}
}
Для проведення семантичного контролю потрібно викликати певні семантичні підпрограми. Семантична підпрограма для обробки оператора опису повинна забезпечити виконання семантичної угоди про єдиність опису ідентифікаторів. Для цього використовується таблиця ідентифікаторів, у якій кожному ідентифікатору повинен відповідати лише один запис. Такий запис додається у таблицю, коли даний ідентифікатор виявлено вперше. Це повинна зробити семантична підпрограма, що викликається під час обробки оператора опису. Вона повинна спочатку здійснити пошук кожного розпізнаного ідентифікатора у таблиці символів (ідентифікаторів). Якщо такий ідентифікатор відсутній, потрібно внести новий запис у таблицю. Якщо такий ідентифікатор знайдено, це свідчить про те, що він був описаний раніше, і робиться спроба повторного опису ідентифікатора. У цьому випадку потрібно сформувати повідомлення про семантичну помилку. Крім того, при обробці оператора опису фіксується інформація про тип змінної; дана інформація заноситься у полі значення для відповідного ідентифікатора.
Таблицю символів будемо розглядати як масив структур. У кожному записі таблиці передбачимо поле для самого ідентифікатора (ключ запису), а також інформацію про його тип. Нехай структура одного запису описується у такий спосіб:
typedef struct
{ char name[lexsize];
char type; /* 1 – int, 2 – float, 0 – unknown */
int ozn ; /* 1 – було присвоєно значення, 0 – не було*/
} el_tabl;
Тоді таблицю ідентифікаторів можна описати так:
el_tabl tabl_ident[tablsize];
Розмістимо ці описи серед описів глобальних змінних, щоб до таблиці символів легко було звертатись з різних підпрограм. Також серед глобальних змінних передбачимо змінну цілого типу k_el − для обліку кількості занесених у таблицю записів (спочатку k_el=0) і змінну символьного type_ident − для запам'ятовування типу, що вказаний на початку оператора опису.
# include <stdio.h>
# include <string.h>
# define lexsize 30
# define tablsize 100
…
char lit, syn, lex[lexsize] type_ident;
int cl, i , f , k_el=0;
typedef struct
{ char name[lexsize];
char type; /* 1 – int, 2 – float, 0 – unknown */
int ozn ; /* 1 – було присвоєно значення, 0 – не було*/
} el_tabl;
el_tabl tabl_ident[tablsize];
…
int poshuk(void);
void zapys(void);
void scan(void);
…
void Op_opysy ( )
{ switch (syn)
{ case ‘1’:
case ‘2’: type_ident=syn; scan ( ) ;
if (syn != ‘i’) { f=0; puts(“Nema identyfikatora u spysku”); }
else { if (poshuk()=-1) zapys( );
else ( printf (“Povtornyy opys %s \n”,lex);
scan( ); while (syn==’,’)
{ scan( ); if (syn != ‘i’) { f=0; puts(“Nema identyfikatora pislja comu”); }
else {if (poshuk()=-1) zapys( );
else ( printf (“Povtornyy opys %s \n”,lex);
scan( ) ; }
}
if (syn == ‘;’) scan( ); else { f=0; puts(“Nema ; pislja spysku”); }
break;
default: f=0; puts(“Nema typu”);
}
}
У мові типу Паскаль тип змінної у операторі опису вказано після списку змінних, тому семантична програма для обробки оператора опису дещо складніша. Можна, наприклад, під час формування нового запису у таблиці ідентифікаторів вказати : type_ident=0 (тип невідомий), а наприкінці обробки чергового оператора опису у всіх елементах таблиці відповідне поле з таким типом замінити. Для послідовних невпорядковиших таблиць можна вести облік кількості ідентифікаторів, описаних у одному операторі опису, і наприкінці його обробки змінити відповідне поле у певній кількості останніх записів. Можна також створити список всіх ідентифікаторів, які описані у даному операторі. У всіх випадках потрібно ретельно продумати спосіб організації таблиці символів і зміст полів у її записах.