Онлайн переводчик http://translate.meta.ua
поменять
По-русски

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

С другой стороны, в некоторых языках лексемы могут содержать незначащие символы (например, символ пробела в Фортране). В Си разделительное значение символов-разделителей может блокироваться ("\" в конце строки внутри "...").

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

Для осуществления двух дальнейших фаз анализа лексический анализатор выдает информацию двух типов: для синтаксического анализатора, работающего вслед за лексическим, существенна информация о последовательности классов лексем, ограничителей и ключевых слов, а для контекстного анализатора, работающего вслед за синтаксическим, существенна информация о конкретных значениях отдельных лексем (идентификаторов, чисел и т.д.).

Таким образом, общая схема работы лексического анализатора такова. Сначала выделяется отдельная лексема (при этом, возможно, используются символы- разделители). Ключевые слова распознаются явным выделением непосредственно из текста, либо сначала выделяется идентификатор, а затем делается проверка на принадлежность его множеству ключевых слов. Если выделенная лексема является ограничителем, то этот ограничитель (точнее, некоторый его признак) выдается как результат лексического анализа.

Если выделенная лексема является ключевым словом, то выдается признак соответствующего ключевого слова. Если выделенная лексема является идентификатором - выдается признак идентификатора, а сам идентификатор сохраняется отдельно. Наконец, если выделенная лексема принадлежит какому-либо из других классов лексем (например, лексема представляет собой число, строку и т.д.

), то выдается признак соответствующего класса, а значение лексемы сохраняется отдельно. Лексический анализатор может быть как самостоятельной фазой трансляции, так и подпрограммой, работающей по принципу "дай лексему". В первом случае (рис. 3.1, а) выходом анализатора является файл лексем, во втором - (рис. 3.1., б) лексема выдается при каждом обращении к анализатору (при этом, как правило, признак класса лексемы возвращается как результат функции "лексический анализатор", а значение лексемы передается через глобальную переменную).

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

Рис. 3.1. Работа лексического анализатора задается некоторым конечным автоматом. Однако, непосредственное описание конечного автомата неудобно с практической точки зрения. Поэтому для задания лексического анализатора, как правило, используется либо регулярное выражение, либо праволинейная грамматика. Все три формализма (конечных автоматов, регулярных выражений и праволинейных грамматик) имеют одинаковую выразительную мощность.

В частности, по регулярному выражению или праволинейной грамматике можно сконструировать конечный автомат, распознающий тот же язык.

Регулярные множества и выражения

Введем понятие регулярного множества, играющего важную роль в теории формальных языков.

Регулярное множество в алфавите T определяется рекурсивно следующим образом:

1. (пустое множество) - регулярное множество в алфавите T;

2. {e} - регулярное множество в алфавите T (e - пустая цепочка);

3. {a} - регулярное множество в алфавите T для каждого ;

4. если P и Q - регулярные множества в алфавите T, то регулярными являются и множества

1.

2.

3.

5. ничто другое не является регулярным множеством в алфавите T.

Итак, множество в алфавите T регулярно тогда и только тогда, когда оно либо , либо {e}, либо {a} для некоторого , либо его можно получить из этих множеств применением конечного числа операций объединения, конкатенации и итерации.

Приведенное выше определение регулярного множества позволяет ввести следующую удобную форму его записи, называемую регулярным выражением.

Регулярное выражение в алфавите T и обозначаемое им регулярное множество в алфавите T определяются рекурсивно следующим образом:

1. регулярное выражение, обозначающее регулярное множество ;

2. {e} - регулярное выражение, обозначающее регулярное множество {e};

3. {a} - регулярное выражение, обозначающее регулярное множество {a};

4. если p и q - регулярные выражения, обозначающие регулярные множества P и Q соответственно, то

1. (p|q) - регулярное выражение, обозначающее регулярное множество ,

2. (pq) - регулярное выражение, обозначающее регулярное множество PQ,

3. (p*) - регулярное выражение, обозначающее регулярное множество P*;

5. ничто другое не является регулярным выражением в алфавите T.

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

Кроме того, мы будем пользоваться записью p для обозначения pp*. Таким образом, запись (a|((ba)(a*))) эквивалентна a|ba .

Также, мы будем использовать запись L(r) для регулярного множества, обозначаемого регулярным выражением r.

Пример 3.1. Несколько примеров регулярных выражений и обозначаемых ими регулярных множеств:

1. a(e|a)|b - обозначает множество {a; b; aa};

2. a(a|b)* - обозначает множество всевозможных цепочек, состоящих из a и b, начинающихся с a;

3. (a|b)*(a|b)(a|b)* - обозначает множество всех непустых цепочек, состоящих из a и b, то есть множество {a, b} ;

4. ((0|1)(0|1)(0|1))* - обозначает множество всех цепочек, состоящих из нулей и единиц, длины которых делятся на 3.

Ясно, что для каждого регулярного множества можно найти регулярное выражение, обозначающее это множество, и наоборот. Более того, для каждого регулярного множества существует бесконечно много обозначающих его регулярных выражений.

Будем говорить, что регулярные выражения равны или эквивалентны (=), если они обозначают одно и то же регулярное множество.

Существуют алгебраические законы, позволяющие осуществлять эквивалентное преобразование регулярных выражений.

Лемма. Пусть p,

По-украински

Основне завдання лексичного аналізу - розбити вхідний текст, що складається з послідовності поодиноких символів, на послідовність слів, або лексем, тобто виділити ці слова з безперервної послідовності символів. Усі символи вхідної послідовності з цієї точки зору розділяються на символи, що належать яким-небудь лексемам, і символи, що розділяють лексеми (роздільники). В деяких випадках між лексемами може і не бути роздільників.

З іншого боку, в деяких мовах лексеми можуть містити незначущі символи (наприклад, символ пропуску у Фортрані). У Сі розділове значення символів-роздільників може блокуватися ("

Зазвичай усі лексеми діляться на класи. Прикладами таких класів є числа (цілі, вісімкові, шістнадцяткові, дійсні і так далі), ідентифікатори, рядки. Окремо виділяються ключові слова і символи пунктуації (іноді їх називають символи-обмежувачі). Як правило, ключові слова - ця деяка кінцева підмножина ідентифікаторів. У деяких мовах (наприклад, ПЛ/1) сенс лексеми може залежати від її контексту і неможливо провести лексичний аналіз у відриві від синтаксичного.

Для здійснення двох подальших фаз аналізу лексичний аналізатор видає інформацію двох типів : для синтаксичного аналізатора, працюючого услід за лексичним, істотна інформація про послідовність класів лексем, обмежувачів і ключових слів, а для контекстного аналізатора, працюючого услід за синтаксичним, істотна інформація про конкретні значення окремих лексем (ідентифікаторів, чисел і так далі).

Таким чином, загальна схема роботи лексичного аналізатора така. Спочатку виділяється окрема лексема (при цьому, можливо, використовуються символи- роздільники). Ключові слова розпізнаються явним виділенням безпосередньо з тексту, або спочатку виділяється ідентифікатор, а потім робиться перевірка на приналежність його безлічі ключових слів.

Якщо виділена лексема є обмежувачем, то цей обмежувач (точніше, деяка його ознака) видається як результат лексичного аналізу. Якщо виділена лексема є ключовим словом, то видається ознака відповідного ключового слова. Якщо виділена лексема є ідентифікатором - видається ознака ідентифікатора, а сам ідентифікатор зберігається окремо. Нарешті, якщо виділена лексема належить якому-небудь з інших класів лексем (наприклад, лексема є числом, рядком і так далі

)то видається ознака відповідного класу, а значення лексеми зберігається окремо.

Лексичний аналізатор може бути як самостійною фазою трансляції, так і підпрограмою, працюючою за принципом "дай лексему". У першому випадку (мал. 3.1, а) виходом аналізатора є файл лексем, в другому - (мал. 3.1., б) лексема видається при кожному зверненні до аналізатора (при цьому, як правило, ознака класу лексеми повертається як результат функції "лексичний аналізатор", а значення лексеми передається через глобальну змінну).

З точки зору обробки значень лексем, аналізатор може або просто видавати значення кожної лексеми, при цьому побудова таблиць об'єктів (ідентифікаторів, рядків, чисел і так далі) переноситься на пізніші фази, або він може самостійно будувати таблиці об'єктів. В цьому випадку в якості значення лексеми видається покажчик на вхід у відповідну таблицю.

Мал. 3.1.

Робота лексичного аналізатора задається деяким кінцевим автоматом. Проте, безпосередній опис кінцевого автомата незручний з практичної точки зору. Тому для завдання лексичного аналізатора, як правило, використовується або регулярне вираження, або праволінійна граматика. Усі три формалізму (кінцевих автоматів, регулярних виразів і праволінійних граматик) мають однакову виразну потужність.

Зокрема, за регулярним виразом або праволінійною граматикою можна сконструювати кінцевий автомат, що розпізнає ту ж мову.

Регулярні множини і вирази

Введемо поняття регулярної великої кількості, що відіграє важливу роль в теорії формальних мов.

Регулярна множина в алфавіті T визначається рекурсивно таким чином:

1. (порожня множина) - регулярна множина в алфавіті T;

2. {e} - регулярна множина в алфавіті T (e - порожній ланцюжок);

3. {a} - регулярна множина в алфавіті T для кожного ;

4. якщо P і Q - регулярні множини в алфавіті T, то регулярними є і множини

1.

2.

3.

5. ніщо інше не є регулярною множиною в алфавіті T.

Отже, множина в алфавіті T регулярно тоді і тільки тоді, коли воно або, або {e}, або {a} для деякого, або його можна отримати з цих множин застосуванням кінцевого числа операцій об'єднання, конкатенації і ітерації.

Приведене вище визначення регулярної великої кількості дозволяє ввести наступну зручну форму його запису, що називається регулярним вираженням.

Регулярне вираження в алфавіті T і регулярна множина, що означає ним, в алфавіті T визначаються рекурсивно таким чином:

1. регулярне вираження, що означає регулярну множину ;

2. {e} - регулярне вираження, що означає регулярну множину {e};

3. {a} - регулярне вираження, що означає регулярну множину {a};

4. якщо p і q - регулярні вирази, що означають регулярну безліч P і Q відповідно, то

1. (p|q) - регулярне вираження, що означає регулярну множину

2. (pq) - регулярне вираження, що означає регулярну безліч PQ

3. (p*) - регулярне вираження, що означає регулярну безліч P*;

5. ніщо інше не є регулярним вираженням в алфавіті T.

Ми опускатимемо зайві дужки в регулярних виразах, домовившись про те, що операція ітерації має найвищий пріоритет, потім йде операції конкатенації, нарешті, операція об'єднання має найменший пріоритет.

Крім того, ми користуватимемося записом p для позначення pp*. Таким чином, запис (a|((ba)(a*))) еквівалентний a|ba .

Також, ми використовуватимемо запис L(r) для регулярної множини, що означає регулярним вираженням r.

Приклад 3.1. Декілька прикладів регулярних виразів і регулярних множин, що означають ними :

1. a(e|a)|b - означає множина {a; b; aa};

2. a(a|b)* - означає безліч всіляких ланцюжків, що складаються з a і b, що починаються з a;

3. (a|b)*(a|b)(a|b)* - означає безліч усіх непорожніх ланцюжків, що складаються з a і b, тобто множина {a, b} ;

4. ((0|1)(0|1)(0|1))* - означає безліч усіх ланцюжків, що складаються з нулів і одиниць, довжини яких діляться на 3.

Ясно, що для кожної регулярної множини можна знайти регулярне вираження, цю множину, що означає, і навпаки. Більше того, для кожної регулярної множини існує нескінченно багато регулярних виразів, що означають його.

Говоритимемо, що регулярні вирази рівні або еквівалентні (=), якщо вони означають одну і ту ж регулярну множину.