Со временем языки эволюционируют, обогащаясь новыми конструкциями, и выполняют новые задачи. Добавление конструкций в язык окажется более простой задачей, если существующая реализация языка основана на его грамматическом описании.
1.2 Анализ предметной области
Входящая последовательность - исходный код программы (рис. 1). В коде присутствуют массивы String Char, Int, Float, а так же указатели и ссылки “*”. Программа, работающая по исходному коду, в качестве результата выводит сначала размер массива после выводит массив рандомных чисел от 0 до 10 (рис. 2). В ходе лексического анализа должны быть выделены лексемы каждого класса, в том числе ключевых слов. Также в ходе анализа должны быть обнаружены ошибки в написании лексем, если таковые имеются. Синтаксический анализ на основе грамматики проверит правильность написания с точки зрения синтаксиса.
#include <iostream>
#include <string>
#include <ctime>
using namespace std;
void opF(int* ar, int* gogo) {
*gogo += *ar;
}
int main()
{
srand(time(NULL));
int m = 0;
int arr[5] = { 3,4,2 };
string mas ="awd";
for (int i = 0; i <2; i++)
{
opF(& arr[i], &m);
}
float *m1 = new float[m];
for (int y = 0; y < m; y++) {
m1[y] = rand() % 10;
cout <<"_"<<m1[y];
}
}
2. Конструирование модели лексического анализа
2.1 Лексический анализ
Лексический анализ -- процесс аналитического разбора входной последовательности символов на распознанные группы -- лексемы, с целью получения на выходе идентифицированных последовательностей. Цель лексического анализа обычно состоит в том, чтобы подготовить входную последовательность для другой программы, например, для синтаксического анализатора, и избавить его от определения лексических подробностей.
Каждый класс лексем описывается правилом, называемым шаблоном. Шаблон соответствует каждой строке в наборе-классе лексем. Лексема же представляет собой последовательность символов исходной программы, которая соответствует шаблону.
2.2 Построение конечного автомата
Граф конечного автомата представлен в п. 2.3, рис. 1
Конечный автомат (КА) демонстрирует работу лексического анализатора по считыванию входной последовательности символов. Считывание начинается в состоянии S0 и продолжается по соответствующему пути в зависимости от считанного символа. Входящим символом автомата считается последний считанный символ. При считывании определённого символа (первой буквы ключевого слова, цифры или прочего) автомат из состояния S0 переходит в соответствующее состояние.
Конечный автомат состоит из пяти компонентов следующего вида:
А = (X, S, F, S0, д), где:
- X - конечное непустое множество входных символов;
- S - конечное непустое множество состояний;
- F - множество выделенных заключительных состояний;
- S0 - начальное состояние;
- д - функция перехода конечного автомата (X*S > S).
В результате выполнения курсовой работы был построен следующий конечный автомат, граф которого представлен на рис. П2.1:
X: ( 0..9, a..z, A..Z,
операторы - {+, -, *, /, %, =, !, ++,--,+=,-=,*=, << , >>},
сравнения - {>, <,<=,>=,==,!=,||,&&},
пунктуации - {;, (, ), [, ], {, }, “.”, “,”, “})
F: {S1, S2, S301, S305, S100, S101}
S: (S0, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, S13, S14, S15, S16, S17, S18, S19, S20, S21, S22, S23, S24, S25, S26, S27, S28, S29, S30, S31, S32, S33, S34, S35, S36, S37, S38, S39, S40, S41, S42, S43, S47, S48, S49, S50, S60, S61, S62, S63, S64, S65, S66, S67, S68, S69, S70, S71, S100, S101, S200, S300, S301, S302, S305, S400, S401)
д: функция переходов конечного автомата представлена в таблице (табл. 1):
Таблица 1. Функция д
|
(S2, a..z, A..Z) >S2 |
(S3..S99, a..z, A..Z )>S200 |
|||
|
(S3..S99,пунктуация )>S0 |
(S3..S99,операторы )>S300 |
|||
|
(S, c )> S3 |
(S3, o)> S34 |
(S20, a)> S21 |
(S41, a)> S42 |
|
|
(S0, b)> S6 |
(S3, i)> S43 |
(S21, t)> S1 |
(S42, i)> S43 |
|
|
(S0, e)> S11 |
(S3, a)> S69 |
(S22, t)> S23 |
(S43, n)> S1 |
|
|
(S0, i)> S16 |
(S4, a)> S5 |
(S22, w)> S64 |
(S47, e)> S48 |
|
|
(S0, f)> S18 |
(S5, r)> S1 |
(S22, y)> S36 |
(S47, a)> S49 |
|
|
(S0, s)> S22 |
(S6, o)> S7 |
(S23, r)> S24 |
(S48, w)> S1 |
|
|
(S0, v)> S31 |
(S7, o)> S7 |
(S24, i)> S25 |
(S49, m)> S50 |
|
|
(S0, m)> S41 |
(S7, l)> S1 |
(S25, n)> S26 |
(S49, m)> S50 |
|
|
(S0, d)> S56 |
(S6, r)> S8 |
(S26, g)> S1 |
(S50, e)> S51 |
|
|
(S0, r)> S61 |
(S8, e)> S9 |
(S27, l)> S28 |
(S51, s)> S52 |
|
|
(S0, n)> 47 |
(S9, a)> S10 |
(S28, u)> S29 |
(S52, p)> S53 |
|
|
(S0, a..z, A..Z)>S2 |
(S10, k)> S1 |
(S29, d)> S30 |
(S53, a)> S54 |
|
|
(S0,0..9)> S100 |
(S11, n)> S12 |
(S29, d)> S1 |
(S54, c)> S55 |
|
|
(S0, “(“ )> S400 |
(S12, d)> S13 |
(S30, e)> S1 |
(S55, e)> S1 |
|
|
(S0, “)” )> S400 |
(S13, l)> S1 |
(S31, o)> S32 |
(S56, o)> S57 |
|
|
(S0, { )> S400 |
(S11, l)> S14 |
(S32, i)> S33 |
(S57, u)> S58 |
|
|
(S0, })> S400 |
(S14, s)> S15 |
(S33, d)> S1 |
(S58, b)> S59 |
|
|
(S0, [ )> S400 |
(S15, e)> S1 |
(S34, u)> S35 |
(S59, l)> S60 |
|
|
(S0, ])> S400 |
(S16, n)> S17 |
(S35, t)> S1 |
(S60, e)> S1 |
|
|
(S0, ;)> S400 |
(S17, t)> S1 |
(S36, s)> S37 |
(S61, a)> S62 |
|
|
(S0,”:”)>S400 |
(S17, c)> S27 |
(S37, t)> S38 |
(S62, n)> S63 |
|
|
(S0,”,”)> S400 |
(S18, l)> S19 |
(S38, e)> S39 |
(S63, d)> S1 |
|
|
(S0, “.”)> S400 |
(S18, o)> S40 |
(S39, m)> S1 |
(S64, i)> S65 |
|
|
(S3,v)> S4 |
(S19, o)> S20 |
(S40, r)> S1 |
(S65, t)> S66 |
|
|
(S300, *=)>S301 |
(S0, !)> S300 |
(S0, <)> S300 |
(S66, c)> S67 |
|
|
(S300, >>)> S301 |
(S0, &)> S300 |
(S0, +) > S300 |
||
|
(S300, <<)> S301 |
(S0, >)> S300 |
(S0, -)> S300 |
||
|
(S300, ++)> S301 |
(S0, 0..9)> S100 |
(S0, *)> S300 |
||
|
(S300, --)> S301 |
(S100, 0..9)> S100 |
(S0, =)> S300 |
||
|
(S300, +=)> S301 |
(S100, “.”)> S101 |
(S0, /)> S300 |
||
|
(S300, -=)> S301 |
(S101, 0..9)> S101 |
(S1, /)> S300 |
2.3 Построение регулярной грамматики по конечному автомату
Грамматика - это способ описания, задания языка. Это система правил, описывающая множество последовательностей символов некоего алфавита. Регулярная грамматика (РГ) эквивалентна конечным автоматам, поэтому по конечному автомату можно получить регулярную грамматику. Алгоритм преобразования:
По конечному автомату A = (X, S, F, S0, д) можно построить регулярную грамматику G = < N, T, P, S >, где:
T = X - конечное непустое множество терминальных символов.
N = S - конечное непустое множество нетерминальных символов
S - стартовый символ грамматики;
P - конечное множество правил грамматики - продукции.
Множество правил подстановки Р строится таким образом: каждой команде автомата (Si, a) > Sj ставится в соответствие правило подстановки Si > а Sj, если Sj ? S, либо Si > а , если Sj ? F, где F- заключительное состояние.
Построение регулярной грамматики по описанному конечному автомату:
По приведённому выше конечному автомату была сформирована следующая регулярная грамматика:
G = (N, T, S, P)
T: ( 0..9, a..z, A..Z, +, -, *, /, %, =, !, ++,--,+=,-=,*=, << , >>,>, <,<=,>=,==,!=,||,&&,;, (, ), [, ], {, }, “.”, “,”, “)
N:
S0 - начало считывания лексемы
S3…S99 - посимвольное считывание ключевого слова
S400, S401 - пунктуация
|
S3…S45 - cin S3…S35 - cout S3…S5 - char S3…S68 - case S6…S10 - break S6…S8 - bool S11…S15 - else S11…S13 - endl S18…S21 - float S18…S40 - for S56…S60 -double |
S16…S31 - include S16…S17 - int S47…S48 - new S47…S55 - namespace S22…S39 - system S22…S26 - string S22…S67 - switch S31…S33 -void S41…S43 -main S61…S63 - rand |
S1 -проверка ключевых слов на правильность написания, добавление их в таблицу
S200 -проверка написания идентификатора и добавление его в таблицу
S300 - начало распознавания лексемы оператора
S300 - начало распознавания лексемы оператора “+,-,=,*,&,/,<,>,!”, добавление лексемы в таблицу
S301 - распознавания лексемы оператора, добавление лексемы в таблицу
“++,--,+=,-=,*=, <=,>=,==,!=,||,&&”
S302 - ошибка в написании оператора
S100 - распознавание лексемы целочисленной константы, запись её в таблицу. Ошибки в написании лексемы
S101 - распознавание лексемы константы с плавающей запятой, запись её в таблицу. Ошибки в написании лексемы
S:S0
P: Множество продукций представлено в таблице (табл. 2).
Таблица 2. Множество продукций
|
S0> c |
S300> += |
S13> l S1 |
S36> s |
|
|
S0> b |
S300> -= |
S11> l |
S37> t |
|
|
S0> e |
S0>! S300 |
S14> s |
S38> e |
|
|
S0> i |
S0> & S300 |
S15> e S1 |
S39> m |
|
|
S0> f |
S0> > S300 |
S16> n |
S40> r S1 |
|
|
S0> s |
S0> 0..9 |
S17> t S1 |
S41> a |
|
|
S0> v |
S100> 0..9 |
S17> c |
S42> i |
|
|
S0> m |
S100>. S101 |
S18> l |
S43> n S1 |
|
|
S0> d |
S101> 0..9 S101 |
S18> o |
S47> e |
|
|
S0> r |
S0> + S301 |
S19> o |
S47> a |
|
|
S0> n |
S0> - S301 |
S20> a |
S48> w S1 |
|
|
S0>(a..z, A..Z) |
S0> * S300 |
S21> t S1 |
S49> m |
|
|
S0> (0..9) |
S0> = S300 |
S22> t |
S50> e |
|
|
S0>( S400 |
S0> /S300 |
S22> w |
S51> s |
|
|
S0>) S400 |
S3> o |
S22> y |
S52> p |
|
|
S0>{ S400 |
S3> v |
S23> r |
S53> a |
|
|
S0>} S400 |
S3> i |
S24> i |
S54> c |
|
|
S0>[ S400 |
S3> a |
S25> n |
S55> e S1 |
|
|
S0>] S400 |
S4> a |
S26> g S1 |
S56> o |
|
|
S0>; S400 |
S5>r S1 |
S27> l |
S57> u |
|
|
S0>: S400 |
S6> o |
S28> u |
S58> b |
|
|
S0>, S400 |
S7> o |
S29> d |
S59> l |
|
|
S0>. S400 |
S7> l S1 |
S29> d S1 |
S60> e S1 |
|
|
S0> < S300 |
S7> r |
S30> у S1 |
S61> a |
|
|
S300>*= |
S8> e |
S31> o |
S62> n |
|
|
S300> -= |
S9> a |
S32> i |
S63> d S1 |
|
|
S300> += |
S10> k S1 |
S33> d S1 |
S64> i |
|
|
S300> ++ S301 |
S11> n S12 |
S34> u S35 |
S65> t |
|
|
S300> -- S301 |
S12> d S13 |
S35> е S1 |
S66> c |
|
|
S0> > S300 |
S0> & S300 |
S0> * S300 |
S0> ! S300 |
Таблица 3. Совмещенная таблица продукций и переходов
|
КА |
РГ |
КА |
РГ |
|
|
(S0, c )> S3 |
S0> c S3 |
(S13, l)> S1 |
S13> l S1 |
|
|
(S0, b)> S6 |
S0> b S6 |
(S11, l)> S14 |
S11> l S14 |
|
|
(S0, e)> S11 |
S0> e S11 |
(S14, s)> S15 |
S14> s S15 |
|
|
(S0, i)> S16 |
S0> i S16 |
(S15, e)> S1 |
S15> e S1 |
|
|
(S0, f)> S18 |
S0> f S18 |
(S16, n)> S17 |
S16> n S17 |
|
|
(S0, s)> S22 |
S0> s S22 |
(S17, t)> S1 |
S17> t S1 |
|
|
(S0, v)> S31 |
S0> v S31 |
(S17, c)> S27 |
S17> c S27 |
|
|
(S0, m)> S41 |
S0> m S41 |
(S18, l)> S19 |
S18> l S19 |
|
|
(S0, d)> S56 |
S0> d S56 |
(S18, o)> S40 |
S18> o S40 |
|
|
(S0, r)> S61 |
S0> r S61 |
(S19, o)> S20 |
S19> o S20 |
|
|
(S0, n)> S47 |
S0> n S47 |
(S20, a)> S21 |
S20> a S21 |
|
|
(S0,a..z,A..Z)>S2 |
S0>(a..z,A..ZS2 |
(S21, t)> S1 |
S21> t S1 |
|
|
(S0,0..9)> S100 |
S0> (0..9) S100 |
(S22, t)> S23 |
S22> t S23 |
|
|
(S0, “(“ )> S400 |
S0>( |
(S22, w)> S64 |
S22> w S64 |
|
|
(S0, “)” )> S400 |
S0>) |
(S22, y)> S36 |
S22> y S36 |
|
|
(S0, { )> S400 |
S0>{ |
(S23, r)> S24 |
S23> r S24 |
|
|
(S0, })> S400 |
S0>} |
(S24, i)> S25 |
S24> i S25 |
|
|
(S0, [ )> S400 |
S0>[ |
(S25, n)> S26 |
S25> n S26 |
|
|
(S0, ])> S400 |
S0>] |
(S26, g)> S1 |
S26> g S1 |
|
|
(S0, ;)> S400 |
S0>; |
(S27, l)> S28 |
S27> l S28 |
|
|
(S0,”:”)>S400 |
S0>: |
(S28, u)> S29 |
S28> u S29 |
|
|
(S0,”,”)> S400 |
S0>, |
(S29, d)> S30 |
S29> d S30 |
|
|
(S0, “.”)> S400 |
S0>. |
(S29, d)> S1 |
S29> d S1 |
|
|
(S0, <)> S300 |
S0> < S300 |
(S30, e)> S1 |
S30> у S1 |
|
|
(S300, *=)> S301 |
S300>*= |
(S31, o)> S32 |
S31> o S32 |
|
|
(S300, -=)> S301 |
S300> -= |
(S32, i)> S33 |
S32> i S33 |
|
|
(S300, +=)> S301 |
S300> += |
(S33, d)> S1 |
S33> d S1 |
|
|
(S300, ++)> S301 |
S300> ++ |
(S34, u)> S35 |
S34> г S35 |
|
|
(S300, --)> S301 |
S300> -- |
(S35, t)> S1 |
S35> е S1 |
|
|
(S300, +=)> S301 |
S300> += |
(S36, s)> S37 |
S36> s S37 |
|
|
(S300, -=)> S301 |
S300> -= |
(S37, t)> S38 |
S37> t S38 |
|
|
(S0, !)> S300 |
S0>! S300 |
(S38, e)> S39 |
S38> e S39 |
|
|
(S0, &)> S300 |
S0> & S300 |
(S39, m)> S1 |
S39> m S1 |
|
|
(S0, >)> S300 |
S0> > S300 |
(S40, r)> S1 |
S40> r S1 |
|
|
(S0, 0..9)> S100 |
S0> 0..9 |
(S41, a)> S42 |
S41> a S42 |
|
|
(S100, 0..9)> S100 |
S100> 0..9 |
(S42, i)> S43 |
S42> i S43 |
|
|
(S100, “.”)> S101 |
S100>. S101 |
(S43, n)> S1 |
S43> n S1 |
|
|
(S101,0..9)> S101 |
S101> 0..9 S101 |
(S47, e)> S48 |
S47> e S48 |
|
|
(S0, +) > S300 |
S0> + S300 |
(S47, a)> S49 |
S47> a S49 |
|
|
(S0, -)> S300 |
S0> - S300 |
(S48, w)> S1 |
S48> w S1 |
|
|
(S0, *)> S300 |
S0> * S300 |
(S49, m)> S50 |
S49> m S50 |
|
|
(S0, =)> S300 |
S0> = S300 |
(S50, e)> S51 |
S50> e S51 |
|
|
(S0, /)> S300 |
S0> /S300 |
(S51, s)> S52 |
S51> s S52 |
|
|
(S0, > )> S300 |
S0> > S300 |
(S0, ! )> S300 |
S0> ! S300 |
|
|
(S0, & )> S300 |
S0> & S300 |
(S0, < )> S300 |
S0> < S300 |
|
|
(S3, o)> S34 |
S3> o S34 |
(S52, p)> S53 |
S52> p S53 |
|
|
(S3,v)> S4 |
S3> v S4 |
(S53, a)> S54 |
S53> a S54 |
|
|
(S3, i)> S43 |
S3> i S43 |
(S54, c)> S55 |
S54> c S55 |
|
|
(S3, a)> S69 |
S3> a S69 |
(S55, e)> S1 |
S55> e S1 |
|
|
(S4, a)> S5 |
S4> a S5 |
(S56, o)> S57 |
S56> o S57 |
|
|
(S5, r)> S1 |
S5>r S1 |
(S57, u)> S58 |
S57> u S58 |
|
|
(S6, o)> S7 |
S6> o S7 |
(S58, b)> S59 |
S58> b S59 |
|
|
(S7, o)> S7 |
S7> o S7 |
(S59, l)> S60 |
S59> l S60 |
|
|
(S7, l)> S1 |
S7> l S1 |
(S60, e)> S1 |
S60> e S1 |
|
|
(S6, r)> S8 |
S7> r S8 |
(S61, a)> S62 |
S61> a S62 |
|
|
(S8, e)> S9 |
S8> e S9 |
(S62, n)> S63 |
S62> n S63 |
|
|
(S9, a)> S10 |
S9> a S10 |
(S63, d)> S1 |
S63> d S1 |
|
|
(S10, k)> S1 |
S10> k S1 |
(S64, i)> S65 |
S64> i S65 |
|
|
(S11, n)> S12 |
S11> n S12 |
(S65, t)> S66 |
S65> t S66 |
|
|
(S12, d)> S13 |
S12> d S13 |
(S66, c)> S67 |
S66> c S67 |
3. Построение лексического анализатора
3.1 Определение лексического анализатора
Лексический анализатор -- это часть компилятора, которая читает исходную программу и выделяет в ее тексте лексемы входного языка. На вход лексического анализатора поступает текст исходной программы, а выходная информация передается для дальнейшей обработки компилятором на этапе синтаксического анализа и разбора.
С теоретической точки зрения лексический анализатор не является обязательной частью компилятора, так как все его функции могут выполняться на этапе синтаксического разбора. Однако лексический анализ включают в состав практически всех компиляторов по следующим причинам: лексема грамматика логический синтаксический
* применение лексического анализатора упрощает работу с текстом исходной программы на этапе синтаксического разбора и сокращает объем обрабатываемой информации;
* для выделения и разбора лексем применяется простая и эффективная техника анализа, в то время как на этапе синтаксического анализа используются достаточно сложные алгоритмы разбора;
* при конструкции компилятора, когда лексический анализ реализован отдельно от синтаксического, для перехода от одной версии языка программирования к другой достаточно перестроить только лексический анализатор.
Поскольку лексический анализатор является частью компилятора, который считывает исходный код, он может так же выполнять некоторые второстепенные задачи. В частности, это удаление из кода исходной программы комментариев, лишних пробелов, символов табуляции и пустых строк. Еще одна задача состоит в согласовании сообщений об ошибках компиляции и текста исходной программы. Например, лексический анализатор может подсчитывать количество считанных строк и указать строку, вызвавшую ошибку.
Таким образом, можно выделить следующие основные функции лексического анализатора:
• исключение из текста исходной программы комментариев;
• исключение из текста исходной программы незначащих пробелов, символов-табуляций и перевода строки;
• выделение лексем следующих типов: идентификаторов, строковых, символьных и числовых, констант, ключевых (служебных) слов входного языка, знаков операций и разделителей;
• выявление ошибок в написании лексем, сообщение об ошибках (с указанием позиции и типа ошибки).
3.2 Моделирование работы лексического анализатора
На вход, в текстовую область, подаётся входная последовательность - обрабатываемый код. Анализатор должен удалить из кода все комментарии - как однострочные, так и многострочные. Также анализатор должен исключить из текста программы незначащие пробелы, символы-табуляции и пустые строки (рис. 1). Параллельно с этим производится выделение лексем с последующим распределением по таблицам соответствующих классов.