МІНІСТЕРСТВО ОСВІТИ І НАУКИ, МОЛДІ ТА СПОРТУ УКРАЇНИ
Національний університет "Львівська політехніка"
Кафедра СКС
КУРСОВИЙ ПРОЕКТ
з курсу “Системне програмування”
на тему: " Розробка та реалізація компонент системного програмного забезпечення ".
Зміст
Вступ____________________________________________________________2
Завдання ________________________________________________________5
Принцип роботи компілятора_____________________________________6
Опис мови_______________________________________________________10
Синтаксичні діаграми____________________________________________ 11
Блок – схеми _____________________________________________________15
Текст програми__________________________________________________17
Інтерфейс________________________________________________________36
Тестова програма________________________________________________37
Висновок________________________________________________________38
Література_______________________________________________________39
1. Вступ.
Компiлятор – це програма, яка перетворює програму, написану на мовi високого рiвня, у відповідні машинні коди. Складання компілятора ділять на фази, на яких перетворюють одне зображення програми в інше. Взаємодію процесів можна представити наступним чином:
Вхідна ОП Об’єктний
Програма код
ЛА СА ГПК ОК ГК
КТ
Фази лексичного (ЛА) та синтаксичного (СА) аналізів розкладають початкову програму на частини. Генерація проміжного коду (ГПК),оптимізація (ОК) та генерація коду (ГК) машини синтезують об’єктну програму (в нашому випадку це буде текст Сі програми). Керування таблицею (КТ) та обробка помилок (ОП) використовуються на всіх фазах компіляції.
Компілятор повинен виконати наступні кроки:
1.Розпiзнати визначенi рядки та окремі символи як базовi елементи мови.
2.Розпiзнати визначенi комбiнацiї елементiв як синтаксичнi одиницi i iнтерпретувати їх значення.
3.Виділити пам’ять для змiнних даної програми.
4.Згенерувати вiдповiднi об’єктнi коди.
Компіляція може проводитись в декілька переглядів вихідного файла.
На першому перегляді відбувається лексичний аналіз. Компілятор виділяє окремі слова і символи, пропускаючи роздільники і коментарі, створює таблицю.
Другий перегляд відповідає синтаксичній фазі. Переглядається таблиця, виділяються ідентифікатори і заносяться в таблицю разом з їх типами, генерується об’єктна програма. Перший і другий перегляд можна об’єднати в один.
Починаючи з третього перегляду, відбуваються фази оптимізації, генерації асемблерного коду та інші, які ми розглядати не будемо.
1.1. Лексичний аналiз.
Граматичний подiл вихiдної програми на вiдповiднi синтаксичнi класи називається лексичним аналiзом. Програма проглядає вихiдний текст i дiлить його на окремi частини (лексеми). В цьому випадку використовуються звичайнi засоби обробки рядків. Вихiдна програма проглядається послiдовно. Базовi елементи, або лексичнi одиницi, роздiляються пробiлами, знаками операцiй i спецiальними символами, i таким чином видiляються iдентифiкатори, лiтерали i термiнальнi символи (операцiї, ключові слова).
Наступнi фази компiлятора використовують властивостi базових елементiв i через це повиннi мати доступ до iнформацiї про них. Цим фазам компiлятора передається або вся iнформацiя про кожен елемент, або, що бiльш типово, сам вихiдний рядок перетворюється в рядок «стандартних символiв». Стандартнi символи мають фiксовану довжину i складаються iз вказiвника синтаксичного типу i показника елемента таблицi, який вiдповiдає базовому елементу.
Оскiльки всi стандартнi символи мають фiксовану довжину, звертання до них робить наступнi фази компiлятора бiльш простими. Так само спрощується перевірка належностi елемента вiдповiдному синтаксичному класу, оскiльки це може бути зроблено простим спiвставленням синтаксичного типу i вказiвника.
При лексичному аналiзі виявляються i вiдзначаються лексичнi помилки (наприклад, недопустимi символи i неправильнi iдентифiкатори). Лексична фаза вiдкидає також i коментарi, оскiльки вони не мають нiякого впливу на виконання програми.
Лексичний аналізатор(сканер) не обов’язково обробляє всю програму до
початку всіх інших фаз. Частіше це підпрограма, що викликається в процесі синтаксичного аналізу для отримання чергової лексеми.
1.2. Синтаксичний аналiз.
Пiд час синтаксичного аналізу речення вихідної мови розпізнаються як мовні конструкції, що описуються граматикою мови. Цей процес можна розглядати як побудову дерева граматичного розбору. Методи граматичного розбору можна поділити на два класи: висхідні і низхідні – відповідно до порядку побудови дерева граматичного розбору. Низхідні починають з правила граматики, що визначає кінцеву мету аналізу з кореня дерева, і намагаються так його нарощувати, щоб наступні вузли відповідали синтаксису. Висхідні методи починають з кінцевих вузлів дерева і намагаються об’єднати їх побудовою вузлів все більш і більш високого рівня доти, поки не буде досягнуто корінь дерева.
В цьому проекті використано один із низхідних методів – метод рекурсивного спуску. Програма синтаксичного аналізу складається з окремих підпрограм, що описують кожне правило граматики. Кожна така підпрограма намагається починаючи з текучої лексеми розпізнати у вхідному потоці певну мовну структуру. Вона може викликати інші подібні підпрограми, або і саму себе. Знайшовши відповідну структурну одиницю, вона закінчує роботу, повертаючи керування підпрограмі, що викликала її. На цьому етапі визначаються синтаксичнi помилки .
Також під час синтаксичного аналізу необхідно скласти таблицю ідентифікаторів – це таблиця змінних і констант та інформація, яка необхідна для того, щоб при генерації асемблерного коду відвести місце в пам’яті під них. При необхідності складаються і деякі інші допоміжні таблиці, як, наприклад, в даному проекті таблиця міток.
1.3. Утворення об’єктного коду .
Після того синтаксична аналіз перевірив вхідний файл на наявність синтаксичних помилок і закінчився успішно , компілятор, для кожної конструкції, генерує об’єктний код (в нашому випадку – текст Сі програми). Це може здійснюватись під час синтаксичного аналізу : розпізнавши конструкцію мови, синтаксичний аналізатор ініціює генерацію відповідної частини об’єктного коду. Саме таким чином відбувається генерація коду в нашому проекті.
2. Завдання
Розробити компілятор з вхідної мови програмування короткий опис якої подано нижче (у відповідності з заданим варіантом) з виводом необхідної проміжної інформації на кожному кроці. Розробити інтерфейс користувача (інтегроване середовище програмування вхідною мовою).
Правила запису конструкцій на вхідній мові
Опис конструкції
Запис на вхідній мові
Блок
Begin - end
Оператор вводу
read
Оператор виводу
write
Умовний оператор
if then goto
Оператор циклу
for-, to-, do-
Функції
Однорівневі
Операції
+ , - , * , / , %
Логічні операції
! , & , | , ^
Операції порівняння
==,!=,>,<,<=,>=
Ідентифікатори
Великі букви, до 2 символів
Коментар
// . . . //
Оператор присвоєння
>>
Типи даних
int, boolean, doubl, string
* - передбачити використання масивів
3. Принцип роботи компілятора
Створений транслятор представлений об’єктом класу CСursCompiler. Нижче буде приведено опис усіх функцій і даних даного класу.
В нашому проекті лексичний аналізатор, синтаксичний аналізатор і генерація об’єктного коду відбувається за один перегляд вхідної програми. При зверненні до лексичного аналізатора відбувається сканування вхідної програми і повертається лексема чи ідентифікатор. Для відображення усіх типів лексем було створено перелічувальний тип даних lex_type, вміст якого представлений наступними таблицями.
Лексеми-роздільники
&
lexAnd
!
lexNE
|
lexOr
^
lexPow
%
lexMod
(
lexLftRndBr
)
lexghRndBr
[
lexLftSqBr
]
lexRghSqBr
;
lexSemicolon
,
lexComma
:
lexColon
+
lexPlus
–
lexMinus
lexStar
lexSlash
\n
lexSkip
‘ ‘
lexSkip
Лексеми-слова
begin
kwBeg
end
kwEnd
read
kwRead
write
kwWrite
goto
kwGoto
for
kwFor
to
kwTo
do
kwDo
if
kwIf
then
kwThen
procedure
kwProcedure
ret
kwRet
int
kwInt
doubl
kwDouble
boolean
kwBool
string
kwString
>>
lexAssign
==
lexEQ
!=
lexNE
>
lexGT
>=
lexGE
<
lexLT
<=
lexLE
рядкова константа
lexString
ціле число
lexInt
число з плаваючою комою
lexFloat
коментар
lexComment
true
kwTrue
false
kwFalse
3.1. Лексичний аналіз.
Лексичний аналіз виконує функція NextLex(). При кожному її виклику вона продовжує читання вхідного тексту програми і повертає тип чергової лексеми. Розрізняються 2 види лексем: лексеми-роздільники і лексеми-слова, представлені вище у таблицях. Якщо символ на якому зупинився курсор є пробіл, табуляція, перехід на новий рядок чи кінець рядка, то ці символи пропускаються і читається наступна лексема. Власне читання наступного символу з вхідного тексту виконує функція NextSym(), яка викликається функцією NextLex(). Якщо прочитана лексема співпадає з одною з таблиці лексем (функція find_idkw()), повертається код цієї лексеми. Інакше припускається, що це число або ідентифікатор. Якщо це число, то повертається код числа (функція number()). Якщо це і не число, то це ідентифікатор. Якщо лексема не упізнана, повертається код lexError. Значення текучої лексеми зберігається в глобальній змінній lex_val, а тип лексеми – в змінній lex. Лексичний аналізаторв своїй роботі використовує наступні функції:
BOOL isLetter(char); //визначає чи є символ буквою
BOOL isDigit(char); //визначає чи є символ цифрою
BOOL isSkip(char); //визначає чм є символ пробілом
void id_etc(void); //визначає приналежність лексеми до ідентифікатора чи ключового слова
void find_idkw(void); //визначає приналежність лексеми до ключового слова
void divcom(void); //визначає чи це знак ділення, чи це коментар
void number(void); //визначає чи це ціле число, чи ні
void fltnumber(void); //обробка числа з плаваючою комою
void string_const(void); //обробка строкових констант
void NextSym(void); //читає наступний символ
Ці функції викликаються функцією NextSym() для визначення типу текучої лексеми.
3.2. Синтаксичний аналіз
В ході синтаксичного аналізу виконується перевірка семантики мови і здійснюється заповнення таблиць змінних. Для представлення таблиць змінних було створено клас CListClass, який представляє собою однозв’язний список з даних типу list_item. Даний тип представляє собою структуру з наступних полів:
struct list_item {
CString name; // ім’я ідентифікатора
lex_type l_type; // тип ідентифікатора
lex_type param_type[9]; // масив типів параметрів функції
BYTE param_num; // кількість параметрів функції
list_item* next; // вказівник на наступний елемент таблиці
};
Клас CListClass містить наступні функції:
BOOL chek_type(CString name, lex_type l_type); //перевіряє чи змінна з //іменем name має тип l_type
list_item *find_id(CString name); //повертає вказівник на запис, що //описує змінну з іменем name
void remove_items(); // знищує всі записи в таблиці
void add_item(list_item* new_item); //додає змінну в кінець списку
void remove_local_items(int count); //знищує з кінця списку count змінних
Клас CCursCompiler містить три об’єкти класу CListClass:
CListClass GlobalVar; //таблиця змінних
CListClass GlobalFunc; //таблиця функцій
CListClass GlobalArray; //таблиця масивів
В даному проекті в ході синтаксичного аналізу виконується заповнення згаданих вище таблиць і виконується власне генерація коду. Синтаксичний аналіз виконується методом рекурсивного спуску функцією SynaAnalyse(), яка викликає наступні функції (в ході роботи всі ці функції виконують генерацію коду):
void RetToC(); //перевірка оператора ret
void ArrayToC(CString id_name);// перевірка звернення до елементу масиву
void FunctionCall(CString fun_name); // перевірка правильності виклику функції
void ScanToC(); //перевірка оператора read
void PrintToC(); //перевірка оператора write
void FunctionToC(); //перевірка правильності оголошення функції
void AssignToC(); //перевірка присвоєння
void BlockToC(); //перевірка блоку
void IfToC(); //перевірка оператора if-then-goto
int BoolExpr(); //перевірка умовного виразу
int MathExpr(); //перевірка математичного виразу
void ForToC(); //перевірка оператора for
void GotoToC(); //перевірка оператора goto
void Declaration(BOOL isGlobalDeclar); //перевірка оголошення змінних
Кожна з цих функцій викликає функцію NextLex() для отримання наступної лексеми. В випадку знаходження помилки, генерується виключення, якому в якості параметра передається текст повідомлення про помилку. Обробка виключення виконується в функції SynaAnalyse().
4.Опис мови
Програма може починатись описом глобальних змінних та функцій. Ідентифікатори - великі букви, довжина – до 2 символів, перший символ буква. Тіло програми обмежується словами Begin - end. Оператори не чутливі до регістру.
Зарезервовані слова: procedure, ret, read, write, if, then, goto , for-, to-, do-, int, doubl, boolean, string, true, false.
Операції: +, –, *, /, =, >, >=, <, <=, !=,&,|,^,%.
Роздільник операндів: ; .
Будь-який блок програми може містити оголошення локальних змінних. Ці змінні будуть діяти до кінця даного блоку, включаючи всі вкладені блоки, відміняючи змінні з таким же іменем, оголошені в “вищих” блоках.
5.Cинтаксичні діаграми
Рядок – ASCIIZ-рядок в лапках (“ ”).
Змінна – ідентифікатор, описаний в розділі опису змінних.
6.Блок-схеми
Порядок роботи компілятора
Блок-схема розгляду синтаксичним
аналізатором блоку програми
7 Текст програми
class CCursCompiler
{
public:
void GotoToC();
void DelFileSymbol();
void SetText(CString);
CString GetProgram();
void ForExpr();
CStdioFile OutputFile;
int SynaAnalize(CString);
int sym_count;
void NextLex();
void print_lex(CStdioFile&);
CCursCompiler(CString);
virtual ~CCursCompiler();
char cs;
void NextSym(void); //читає наступний символ
int column; //текуча позиція
void Tab(int n);
BOOL isEndOfText();
protected:
void RetToC();
void ArrayToC(CString id_name);
void FunctionCall(CString fun_name);
void ScanToC();
CListClass GlobalVar;
CListClass GlobalFunc;
CListClass GlobalArray;
void PrintToC();
void FunctionToC();
void AssignToC();
void BlockToC();
// void BlockToC(CListClass *LocalVar);
void IfToC();
int BoolExpr();
int MathExpr();
void ForToC();
void Declaration(BOOL isGlobalDeclar);
void WriteToC();
void WriteToC(CString mes);
CString Prg_Text; //текст програми
int line; //текучий рядок
BOOL isLetter(char); //визначає чи є символ буквою
BOOL isDigit(char); //визначає чи є символ цифрою
BOOL isSkip(char); //визначає чм є символ пробілом
BOOL isIgnore(int); //визнчає чи є символ ігноруємим
void find_idkw(void);
void id_etc(void);
void divcom(void);
void number(void);
void fltnumber(void);
void string_const(void);
};
struct list_item {
CString name;
lex_type l_type;
lex_type param_type[9];
BYTE param_num;
list_item* next;
};
class CListClass
{
public:
BOOL chek_type(CString name, lex_type l_type);
list_item *find_id(CString name);
void remove_items();
void add_item(list_item* new_item);
void remove_local_items(int count);
CListClass();
virtual ~CListClass();
protected:
list_item* root;
list_item* last;
};
Реалізація функцій класу CCursCompiler
// CursCompiler.cpp: implementation of the CCursCompiler class.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "Compiler.h"
#include "CursCompiler.h"
#include "share_data.h"
#include "scaner_data.h"
#include "ListClass.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
#define Error(a) throw a
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CCursCompiler::CCursCompiler(CString Doc)
{
line = 0;
column = 0;
sym_count = 0;
Prg_Text = Doc; //збереження текстy програми
}
CCursCompiler::~CCursCompiler()
{
GlobalVar.remove_items();
GlobalFunc.remove_items();
GlobalArray.remove_items();
}
//------------------------------------------------------------------------------
// Функції, що використовуються при визначенні кдаса лексем
//------------------------------------------------------------------------------
//визначає приналежність символа до класу букв
BOOL CCursCompiler::isLetter(char ch)
{
if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))
return true;
else
return false;
}
//визначає приналежність символа до класу десяткових цифр
BOOL CCursCompiler::isDigit(char ch)
{
if ((ch >= '0' && ch <= '9'))
return true;
else
return false;
}
void copy(char * kio,char *lop);
//визначає приналежність символа до класу пробілів
BOOL CCursCompiler::isSkip(char ch)
{
if(ch == ' ' || ch == '\t' || ch == '\n' || ch == '\f')
return true;
else
return false;
}
// Визначає приналежність символу до класу ігноруємих
BOOL CCursCompiler::isIgnore(int ch)
{
if(ch >0 && ch < ' ' && ch != '\t' && ch != '\n' && ch != '\f')
return true;
else
return false;
}
//Читає наступний символ з вхідного потоку
void CCursCompiler::NextSym(void)
{
CString s;
int i;
i = Prg_Text.GetLength();
if(sym_count == Prg_Text.GetLength()) {cur_sym = EOF;sym_count = -1;return;}
cur_sym = Prg_Text.GetAt(sym_count);
if(cur_sym == '\n') {++line; column = 0; ++sym_count;}
else {++column; ++sym_count;}
}
char * lop = " ";
// Визначає приналежність до ключового слова
int cmp(char * kio,char * lop)
{
int i;
for(i=0;i<strlen(kio);i++)
if(kio[i]!=lop[i])return-1;
return 1;
}
void CCursCompiler::find_idkw(void)
{
char * yy;
for(int i = 0; i < rw_size; ++i)
if(!strcmp(rwtab[i].wrd, lex_val))
{
lex = rwtab[i].lc;
return;
}
//copy(lop,lex_val);
yy = strupr(lex_val);
if(strlen(lex_val)>2)Error("Ідентифікатор неправильний");
if(!cmp(yy,lop))Error("Ідентифікатор неправильний");
lex = lexId;
}
//Визначає приналежність до ідентифікатора чи ключового слова
void CCursCompiler::id_etc(void)
{
char * kio;
int i;
while(isLetter(cur_sym) || isDigit(cur_sym) || cur_sym=='_') {
lex_val[++i_lv] = cur_sym;
NextSym();
}
lex_val[++i_lv] = '\0';
//lop = lex_val;
copy(lop,lex_val);
kio = strlwr(lex_val);
for(i = 0;i<strlen(kio);i++)lex_val[i]=kio[i];
find_idkw();
}
// Знак ділення чи коментар ?
void CCursCompiler::divcom(void)
{
if(cur_sym != '/') {lex = lexSlash; lex_val[0] = '/'; lex_val[1] = '\0';return;}
NextSym();
if(cur_sym == EOF) {
lex = lexError; lex_val[++i_lv]='\0'; return;
}
while(cur_sym != '/') {
lex_val[++i_lv] = cur_sym;
if(cur_sym == EOF) {lex = lexError; lex_val[++i_lv]='\0'; return;}
NextSym();
}
NextSym();
if(cur_sym != '/') {lex_val[++i_lv] = cur_sym;}
lex_val[++i_lv] = '\0';
lex = lexComment;
NextSym();
}
// Ціле чи дійсне число
void CCursCompiler::number(void)
{
do
{
lex_val[++i_lv] = cur_sym;
NextSym();
} while (isDigit(cur_sym));
if(cur_sym == '.') {fltnumber(); return;}
if(isLetter(cur_sym)) {lex=lexError; lex_val[++i_lv]='\0'; return;}
lex = lexInt; lex_val[++i_lv] = '\0';
}
// Число з плаваючою комою
void CCursCompiler::fltnumber(void) {
lex_val[++i_lv]=cur_sym;
NextSym();
while (isDigit(cur_sym))
{
lex_val[++i_lv]=cur_sym;
NextSym();
}
if(isLetter(cur_sym)) {lex=lexError; lex_val[++i_lv]='\0';return;}
lex=lexFloat; lex_val[++i_lv]='\0'; return;
}
// Рядок символів
void CCursCompiler::string_const(void)
{
if(cur_sym == '\"') { // пустиая строка или апостроф в строке
NextSym();
if(cur_sym == '\"' && cur_sym != EOF) { // дубль для апострофа
lex_val[++i_lv]=cur_sym; // фиксируем его в строке как один
NextSym(); }
lex = lexString; lex_val[++i_lv] = '\0'; return; // законченная строка
}
while (cur_sym != '\"' )
{
lex_val[++i_lv]=cur_sym;
NextSym();
}
lex = lexString; lex_val[++i_lv] = '\0'; NextSym(); return; // законченная строка
}
//Функція, що формує наступну лексему
//Викликається з синтаксичного аналізатора
void CCursCompiler::NextLex()
{
do {
i_lv = -1;
lex_val[0] = '\0';
if(cur_sym == EOF) {lex = lexEof;break;}
else if(isSkip(cur_sym)) {NextSym(); lex = lexSkip;}
else if(isLetter(cur_sym) || cur_sym == '_')
{
lex_val[++i_lv]=cur_sym; NextSym(); id_etc();
}
else if(isDigit(cur_sym)) {number();}
else if(isIgnore(cur_sym)) {NextSym(); lex = lexIgnore;}
else if(cur_sym == '/') {NextSym(); divcom();}
else if(cur_sym == '\"') {NextSym(); string_const();}
else if(cur_sym == ';') {NextSym(); lex = lexSemicolon; lex_val[0] = ';'; lex_val[1] = '\0';}
else if(cur_sym == ',') {NextSym(); lex = lexComma; lex_val[0] = ','; lex_val[1] = '\0';}
else if(cur_sym == ':')
{
NextSym();
if(cur_sym == '=') {NextSym(); lex = lexAssign; lex_val[0] = ':'; lex_val[1] = '='; lex_val[2] = '\0';}
else {lex = lexColon; lex_val[0] = ':'; lex_val[1] = '\0';}
}
else if(cur_sym == '(') {NextSym(); lex = lexLftRndBr; lex_val[0] = '('; lex_val[1] = '\0';}
else if(cur_sym == ')') {NextSym(); lex = lexRghRndBr; lex_val[0] = ')'; lex_val[1] = '\0';}
else if(cur_sym == '[') {NextSym(); lex = lexLftSqBr; lex_val[0] = '['; lex_val[1] = '\0';}
else if(cur_sym == ']') {NextSym(); lex = lexRghSqBr; lex_val[0] = ']'; lex_val[1] = '\0';}
else if(cur_sym == '*') {NextSym(); lex = lexStar; lex_val[0] = '*'; lex_val[1] = '\0';}
else if(cur_sym == '+') {NextSym(); lex = lexPlus; lex_val[0] = '+'; lex_val[1] = '\0';}
else if(cur_sym == '&') {NextSym(); lex = lexAnd; lex_val[0] = '&';lex_val[1] = '\0';}
else if(cur_sym == '|') {NextSym(); lex = lexOr; lex_val[0] = '|';lex_val[1] = '\0';}
else if(cur_sym == '!') {NextSym(); lex = lexNot; lex_val[0] = '!';lex_val[1] = '\0';}
else if(cur_sym == '%') {NextSym(); lex = lexMod; lex_val[0] = '%';lex_val[1] = '\0';}
else if(cur_sym == '^') {NextSym(); lex = lexPow; lex_val[0] = '^';lex_val[1] = '\0';}
else if(cur_sym == '-') {NextSym(); lex = lexMinus; lex_val[0] = '-'; lex_val[1] = '\0';}
else if(cur_sym == '=') {NextSym(); lex = lexEQ; lex_val[0] = '='; lex_val[1] = '\0';}
else if(cur_sym == '>')
{
NextSym();
lex = lexGT; lex_val[0] = '>';
if(cur_sym == '=') {NextSym(); lex = lexGE; lex_val[0] = '>'; lex_val[1] = '='; lex_val[2] = '\0';}
if(cur_sym == '>') {NextSym(); lex = lexAssign; lex_val[0] = '>'; lex_val[1] = '>'; lex_val[2] = '\0';}
}
else if(cur_sym == '<')
{
NextSym();
if(cur_sym == '=') {NextSym(); lex = lexLE; lex_val[0] = '<'; lex_val[1] = '='; lex_val[2] = '\0';}
lex = lexLT; lex_val[0] = '<';
}
else {lex = lexError; NextSym();}
}
while (lex == lexComment || lex == lexSkip || lex == lexIgnore);
}