Задачей лексического анализа является разбить входную последовательность (в моем случае код на языке «Паскаль») на слова и лексемы.
Для начала я создал 5 типизированных листов для хранения данных, а именно: идентификаторов, констант, ключевых слов, разделителей и свертки. Также необходим массив разделителей
и массив ключевых слов. Я ограничился одиннадцатью ключевыми словами, так как статья написана как начальный пример реализации лексического анализа языка «Паскаль» на языке C#.
Итак, массив ключевых слов:
На входе мы имеем путь к .txt-файлу с программой в переменной path. Считываем весь в файл в AllTextProgram и начинаем.
В цикле for берем последовательно каждый символ из AllTextProgram и отправляем его в метод Analysis. Отдельно я рассматриваю лишь два исключения. Первое – это апострофы, которые в моем случае встречаются при использовании ключевого слова «writeln». Если встречаем апостроф, то отходим от правил и идем по переменной AllTextPorgram, пока ни найдем второй апостроф. Второе – это комментарии в программе. Для последних я просто считаю число открывающихся и закрывающихся фигурных скобок и ожидаю пока разница ни будет равна нулю.
Казалось бы программа должна зациклиться, если последующий апостроф не найден или кол-во фигурных скобок не равно 0. Да, это верно, в этих циклах необходимо добавить дополнительные условия выхода из проверки. Эти условия для систематизации я решил сделать на этапе синтаксического анализа.
Переменная type отвечает за номер таблицы, к которому относится лексема.
Идентификаторы – таблица 1
Константы – таблица 2
Ключевые слова – таблица 3
Разделители – таблица 4
Теперь настало время поговорить что же происходит в методе Analysis. Это лучше сделать в виде комментариев к коду. Сразу отмечу, что в переменной temp будем хранить промежуточный результат работы, пока ни встретим разделитель. Встретив разделитель, нам необходимо отправить переменную temp в метод Result, чтобы определить к идентификатору, константе или ключевому слову относится нынешняя лексема.
Заключительным для рассматрения является методе Result, где мы определяем, что же мы нашли. В самом начале проверяем переменную temp на принадлежность к ключевым словам. Проверяем в начале, чтобы не перепутать найденную лексему с идентификатором. Если найденное не является ключевым словом, то через switch проверяем тип таблицы, определенный заранее в методе Analysis. Не зыбываем проверить, чтобы найденный идентификатор/константа/разделитель уже не значился в таблице.
И легкий пример, чтобы показать как это все работает.
На входе программа:
И результат работы:

Для начала я создал 5 типизированных листов для хранения данных, а именно: идентификаторов, констант, ключевых слов, разделителей и свертки. Также необходим массив разделителей
static char[] limiters = {',', '.', '(', ')', '[', ']', ':', ';', '+', '-', '*', '/', '<', '>', '@'};
и массив ключевых слов. Я ограничился одиннадцатью ключевыми словами, так как статья написана как начальный пример реализации лексического анализа языка «Паскаль» на языке C#.
Итак, массив ключевых слов:
static string[] reservedWords = { "program", "var", "real", "integer", "begin", "for", "downto", "do", "begin", "end", "writeln" };
На входе мы имеем путь к .txt-файлу с программой в переменной path. Считываем весь в файл в AllTextProgram и начинаем.
StreamReader sr = new StreamReader(path, Encoding.Default); AllTextProgram = sr.ReadToEnd(); sr.Close();
В цикле for берем последовательно каждый символ из AllTextProgram и отправляем его в метод Analysis. Отдельно я рассматриваю лишь два исключения. Первое – это апострофы, которые в моем случае встречаются при использовании ключевого слова «writeln». Если встречаем апостроф, то отходим от правил и идем по переменной AllTextPorgram, пока ни найдем второй апостроф. Второе – это комментарии в программе. Для последних я просто считаю число открывающихся и закрывающихся фигурных скобок и ожидаю пока разница ни будет равна нулю.
Казалось бы программа должна зациклиться, если последующий апостроф не найден или кол-во фигурных скобок не равно 0. Да, это верно, в этих циклах необходимо добавить дополнительные условия выхода из проверки. Эти условия для систематизации я решил сделать на этапе синтаксического анализа.
for (i = 0; i < AllTextProgram.Length; i++) { char c = AllTextProgram[i]; if (AllTextProgram[i] == '\'') { temp += '\''; i++; while (AllTextProgram[i] != '\'') { temp += AllTextProgram[i]; i++; } temp += '\''; type = 2; Result(temp); temp = null; } if (AllTextProgram[i] == '{') { int chet = 1; while (chet != 0) { i++; if (AllTextProgram[i] == '{') chet++; if (AllTextProgram[i] == '}') chet--; } } Analysis(AllTextProgram[i]); }
Переменная type отвечает за номер таблицы, к которому относится лексема.
Идентификаторы – таблица 1
Константы – таблица 2
Ключевые слова – таблица 3
Разделители – таблица 4
Теперь настало время поговорить что же происходит в методе Analysis. Это лучше сделать в виде комментариев к коду. Сразу отмечу, что в переменной temp будем хранить промежуточный результат работы, пока ни встретим разделитель. Встретив разделитель, нам необходимо отправить переменную temp в метод Result, чтобы определить к идентификатору, константе или ключевому слову относится нынешняя лексема.
static void Analysis(char nextChar) { int acsiiCode = (int)nextChar; // Получаем код символа // Проверяем относится ли код символа к буквам английского алфавита и знаку нижнего подчеркивания if (((acsiiCode >= 65) && (acsiiCode <= 90)) || ((acsiiCode >= 97) && (acsiiCode <= 122)) || (acsiiCode == 95) { if (temp == null) type = 1; temp += nextChar; return; } // Проверяем относится ли код символа к цифрам или к точке if (((acsiiCode >= 48) && (acsiiCode <= 57)) || (acsiiCode == 46)) { // Отдельная проверка, если код символа относиться к точке проверяем, чтобы в переменной temp // было число. Если в temp число, значит теперь // мы считаем его дробным, в противном случае это что-то другое if (acsiiCode == 46) { int out_r; if (!int.TryParse(temp, out out_r)) goto not_the_number; } if (temp == null) type = 2; temp += nextChar; return; } not_the_number: if ((nextChar == ' ' || nextChar == '\n') && temp != null) { Result(temp); temp = null; return; } // И последняя проверка на принадлежность к массиву разделителей. Не забываем о двойном разделителе «:=» и двойных условных разделителях. foreach (char c in limiters) { if (nextChar == c) { if (temp != null) Result(temp); type = 3; if (nextChar == ':' && AllTextProgram[i+1] == '=') { temp = nextChar.ToString() + AllTextProgram[i + 1]; Result(temp); temp = null; return; } if (nextChar == '<' && (AllTextProgram[i + 1] == '>' || AllTextProgram[i + 1] == '=')) { temp = nextChar.ToString() + AllTextProgram[i + 1]; Result(temp); temp = null; return; } if (nextChar == '>' && AllTextProgram[i + 1] == '=') { temp = nextChar.ToString() + AllTextProgram[i + 1]; Result(temp); temp = null; return; } temp = nextChar.ToString(); Result(temp); temp = null; return; } }
Заключительным для рассматрения является методе Result, где мы определяем, что же мы нашли. В самом начале проверяем переменную temp на принадлежность к ключевым словам. Проверяем в начале, чтобы не перепутать найденную лексему с идентификатором. Если найденное не является ключевым словом, то через switch проверяем тип таблицы, определенный заранее в методе Analysis. Не зыбываем проверить, чтобы найденный идентификатор/константа/разделитель уже не значился в таблице.
static void Result(string temp) { for (int j = 0; j < reservedWords.Length; j++) { if (temp == reservedWords[j]) { for (int i = 0; i < tableR.Count; i++) { if (temp == tableR[i]) { LConv.Add("3" + i); return; } } tableR.Add(temp); LConv.Add("3" + (tableR.Count - 1)); return; } } switch (type) { case 1: for (int j = 0; j < tableI.Count; j++) { if (temp == tableI[j]) { LConv.Add("1" + j); return; } } tableI.Add(temp); LConv.Add("1" + (tableI.Count - 1)); break; case 2: for (int j = 0; j < tableC.Count; j++) { if (temp == tableC[j]) { LConv.Add("2" + j); return; } } tableC.Add(temp); LConv.Add("2" + (tableC.Count - 1)); break; case 3: for (int j = 0; j < tableL.Count; j++) { if (temp == tableL[j]) { LConv.Add("4" + j); return; } } tableL.Add(temp); LConv.Add("4" + (tableL.Count - 1)); break; } }
И легкий пример, чтобы показать как это все работает.
На входе программа:
program main; { pro{g}am }
var sum: real;
f, per_1, x_1,i:integer;
begin
{sum:=(-x_1+2.5)*4 - (x_1-6)*((((x_1+2))));}
x_1:=18;
f:= 456;
for i:=10 downto per_1-f+i*(x_1+1) do
begin
per_1 := per_1 + x_1*sum*f;
x_1:=-x_1-1;
f:=(f+1)*(x_1-24700);
sum:=(x_1+2.5)*4 - (x_1-6)*(x_1+2);
end;
writeln('summa = ', sum);
end.И результат работы:

