Занятие 3 по курсу МПК — различия между версиями
Admin (обсуждение | вклад) (→Класс Scanner) |
Admin (обсуждение | вклад) (→Обязательные задания) |
||
(не показано 26 промежуточных версий этого же участника) | |||
Строка 5: | Строка 5: | ||
GPLex - программа для автоматической генерации сканеров (лексических анализаторов) | GPLex - программа для автоматической генерации сканеров (лексических анализаторов) | ||
− | Комплект для практического занятия [http://pascalabc.net/downloads/CompilerConstruction/ | + | Комплект для практического занятия [http://pascalabc.net/downloads/CompilerConstruction/GPLexProject.zip скачиваем отсюда]. Состав: |
*LexProjects.sln - файл решения, содержащее проект Lex1.csproj | *LexProjects.sln - файл решения, содержащее проект Lex1.csproj | ||
*Lex1.csproj - файл демонстрационного проекта для GPLex | *Lex1.csproj - файл демонстрационного проекта для GPLex | ||
Строка 11: | Строка 11: | ||
*mymain.cs - основная программа, содержащая создание сканера и сканирование всех лексем в файле | *mymain.cs - основная программа, содержащая создание сканера и сканирование всех лексем в файле | ||
*ShiftReduceParserCode.cs - файл проекта, необходимый для работы лексического анализатора | *ShiftReduceParserCode.cs - файл проекта, необходимый для работы лексического анализатора | ||
− | *SimpleLex.lex - файл, содержащий правила для генерации лексического анализатора | + | *'''SimpleLex.lex''' - файл, содержащий правила для генерации лексического анализатора |
*generateScanner.bat - командный файл. Содержит одну команду: gplex.exe /noparser SimpleLex.lex | *generateScanner.bat - командный файл. Содержит одну команду: gplex.exe /noparser SimpleLex.lex | ||
*SimpleLex.cs - файл проекта, автоматически создаваемый при запуске generateScanner.bat | *SimpleLex.cs - файл проекта, автоматически создаваемый при запуске generateScanner.bat | ||
*a.txt - файл программы, подаваемой на вход сгенерированному лексеру | *a.txt - файл программы, подаваемой на вход сгенерированному лексеру | ||
− | ===Компиляция проекта=== | + | ===Компиляция проекта и выполнение программы=== |
*Выполняем команду gplex.exe /noparser SimpleLex.lex, запустив командный файл generateScanner.bat | *Выполняем команду gplex.exe /noparser SimpleLex.lex, запустив командный файл generateScanner.bat | ||
:При этом генерируется файл SimpleLex.cs, содержащий код лексера. Ключ /noparser означает, что генерируется лексер без парсера. | :При этом генерируется файл SimpleLex.cs, содержащий код лексера. Ключ /noparser означает, что генерируется лексер без парсера. | ||
*Открываем и компилируем .sln | *Открываем и компилируем .sln | ||
+ | *Меняем содержимое a.txt, запускаем проект и анализируем результаты | ||
− | === | + | ===Файл ScannerHelper.cs === |
− | + | Вспомогательный файл для сканера. Содержит глобальные определения. | |
− | + | В данном случае ScannerHelper.cs содержит перечислимый тип Tok, содержащий виды лексем, возвращаемых нашим сканером. | |
− | + | ||
− | + | Лексема EOF должна идти первой и быть связанной с константой 0. | |
− | |||
− | + | ====Группы лексем==== | |
+ | * EOF | ||
+ | * Литеральные константы: ID, INUM, RNUM | ||
+ | * Разделители, знаки операций: COLON, SEMICOLON, ASSIGN | ||
+ | * Ключевые слова: BEGIN, END, CYCLE | ||
+ | <source lang="Csharp"> | ||
+ | namespace ScannerHelper | ||
+ | { | ||
+ | public enum Tok { EOF = 0, ID, INUM, RNUM, COLON, SEMICOLON, ASSIGN, BEGIN, END, CYCLE }; | ||
+ | }</source> | ||
− | + | ===Lex-файл SimpleLang.lex === | |
− | <source lang="csharp">%namespace SimpleScanner | + | <source lang="csharp">%using ScannerHelper; |
+ | %namespace SimpleScanner | ||
Alpha [a-zA-Z_] | Alpha [a-zA-Z_] | ||
Строка 41: | Строка 51: | ||
REALNUM {INTNUM}\.{INTNUM} | REALNUM {INTNUM}\.{INTNUM} | ||
ID {Alpha}{AlphaDigit}* | ID {Alpha}{AlphaDigit}* | ||
+ | |||
+ | // Здесь можно делать описания типов, переменных и методов - они попадают в класс Scanner | ||
+ | %{ | ||
+ | public int LexValueInt; | ||
+ | public double LexValueDouble; | ||
+ | %} | ||
%% | %% | ||
{INTNUM} { | {INTNUM} { | ||
− | + | LexValueInt = int.Parse(yytext); | |
+ | return (int)Tok.INUM; | ||
} | } | ||
{REALNUM} { | {REALNUM} { | ||
− | + | LexValueDouble = double.Parse(yytext); | |
+ | return (int)Tok.RNUM; | ||
} | } | ||
begin { | begin { | ||
− | + | return (int)Tok.BEGIN; | |
} | } | ||
end { | end { | ||
− | + | return (int)Tok.END; | |
+ | } | ||
+ | |||
+ | cycle { | ||
+ | return (int)Tok.CYCLE; | ||
} | } | ||
{ID} { | {ID} { | ||
− | + | return (int)Tok.ID; | |
} | } | ||
":" { | ":" { | ||
− | + | return (int)Tok.COLON; | |
} | } | ||
":=" { | ":=" { | ||
− | + | return (int)Tok.ASSIGN; | |
} | } | ||
";" { | ";" { | ||
− | + | return (int)Tok.SEMICOLON; | |
} | } | ||
%% | %% | ||
− | // Здесь можно делать описания переменных и методов - они попадают в класс Scanner | + | // Здесь можно делать описания переменных и методов - они тоже попадают в класс Scanner |
public int Sum = 0;</source> | public int Sum = 0;</source> | ||
+ | |||
+ | ====Формат .lex-файла==== | ||
+ | |||
+ | Определения | ||
+ | %% | ||
+ | Правила | ||
+ | %% | ||
+ | Пользовательский код | ||
+ | |||
+ | Пользовательский код содержит описания полей и методов, включаемых в генерируемый класс Scanner. | ||
+ | |||
+ | Пользовательский код может находиться в секции определений, для этого его надо заключить в | ||
+ | |||
+ | <source lang="Csharp">%{ | ||
+ | |||
+ | %}</source> | ||
+ | |||
+ | ====Комментарии к SimpleLex.lex==== | ||
+ | Секция описаний содержит строки | ||
+ | |||
+ | <source lang="Csharp">%using ScannerHelper; | ||
+ | %namespace SimpleScanner | ||
+ | </source> | ||
+ | |||
+ | которые после преобразования в SimpleLex.cs перейдут в строки | ||
+ | <source lang="Csharp"> | ||
+ | using ScannerHelper; | ||
+ | |||
+ | namespace SimpleScanner | ||
+ | { | ||
+ | // остальной генерируемый код | ||
+ | } | ||
+ | </source> | ||
+ | Строки | ||
+ | <source lang="Csharp">Alpha [a-zA-Z_] | ||
+ | Digit [0-9] | ||
+ | AlphaDigit {Alpha}|{Digit} | ||
+ | INTNUM {Digit}+ | ||
+ | REALNUM {INTNUM}\.{INTNUM} | ||
+ | ID {Alpha}{AlphaDigit}*</source> | ||
+ | в секции описаний определяют так называемые классы символов, которыми можно пользоваться при определении других классов символов и в секции правил. | ||
+ | |||
+ | В секции правил определяются все распознаваемые лексемы и семантические действия, которые нужно выполнять, когда встретилась та или иная лексема. | ||
+ | |||
+ | В семантических действиях в основном мы возвращаем целое значение для функции | ||
+ | <source lang="Csharp">int yylex() | ||
+ | </source> | ||
+ | и, возможно, выполняем другие действия. В качестве целого значения будем возвращать тип лексемы Tok, приведенный к целому типу. | ||
+ | Дополнительные семантические действия проделаем в следующих случаях: | ||
+ | * в случае лексемы целой константы в LexValueInt будем возвращать целое, соответствующее текущей лексеме, | ||
+ | * в случае лексемы вещественной константы в LexValueDouble будем возвращать вещественное, соответствующее текущей лексеме | ||
+ | |||
+ | Переменная | ||
+ | |||
+ | <source lang="Csharp">yytext</source> | ||
+ | используемая в коде, является стандартным полем класса Scanner и представляет собой текстовое представление текущей лексемы. | ||
==== Регулярные выражения, используемые в секции определений==== | ==== Регулярные выражения, используемые в секции определений==== | ||
Строка 138: | Строка 216: | ||
===Класс Scanner=== | ===Класс Scanner=== | ||
+ | Класс Scanner создается в файле SimpleLex.cs, который генерируется по SimpleLex.lex-файлу | ||
+ | |||
* Основной метод - int yylex() - возвращает код следующей лексемы (токена) | * Основной метод - int yylex() - возвращает код следующей лексемы (токена) | ||
− | * Свойства | + | * Свойства: |
string yytext - текст лексемы | string yytext - текст лексемы | ||
int yyline - номер строки лексемы | int yyline - номер строки лексемы | ||
Строка 150: | Строка 230: | ||
using System.IO; | using System.IO; | ||
using SimpleScanner; | using SimpleScanner; | ||
+ | using ScannerHelper; | ||
namespace Main | namespace Main | ||
Строка 157: | Строка 238: | ||
static void Main(string[] args) | static void Main(string[] args) | ||
{ | { | ||
− | + | // Чтобы вещественные числа распознавались и отображались в формате 3.14 (а не 3,14 как в русской Culture) | |
+ | System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US"); | ||
+ | |||
+ | var fname = @"..\..\a.txt"; | ||
+ | Console.WriteLine(File.ReadAllText(fname)); | ||
+ | |||
+ | Scanner scanner = new Scanner(new FileStream(fname, FileMode.Open)); | ||
int tok = 0; | int tok = 0; | ||
do { | do { | ||
tok = scanner.yylex(); | tok = scanner.yylex(); | ||
− | } while (tok != (int) | + | |
+ | Console.Write((Tok)tok + " "); | ||
+ | |||
+ | if ((Tok)tok == Tok.INUM) | ||
+ | Console.WriteLine(scanner.LexValueInt); | ||
+ | else if ((Tok)tok == Tok.RNUM) | ||
+ | Console.WriteLine(scanner.LexValueDouble); | ||
+ | else if ((Tok)tok == Tok.ID) | ||
+ | Console.WriteLine(scanner.yytext); | ||
+ | else Console.WriteLine(); | ||
+ | |||
+ | } while (tok != (int)Tok.EOF); | ||
Console.ReadKey(); | Console.ReadKey(); | ||
} | } | ||
} | } | ||
− | } | + | }</source> |
− | </source> | + | |
+ | ==== Комментарии к программе ==== | ||
+ | Лексический анализатор (сканер) содержит главный метод - yylex() - который возвращает следующую лексему, точнее, ее тип, приведенный к целому типу. | ||
+ | |||
+ | В основной программе мы в цикле вызываем метод scanner.yylex() пока не будет достигнут конец потока | ||
− | === | + | ===Обязательные задания=== |
#Откомпилировать лексический анализатор и запустить его для файла a.txt | #Откомпилировать лексический анализатор и запустить его для файла a.txt | ||
− | |||
#Подсчитать количество, среднюю, минимальную и максимальную длину всех идентификаторов | #Подсчитать количество, среднюю, минимальную и максимальную длину всех идентификаторов | ||
#Найти сумму всех целых и сумму всех вещественных в файле | #Найти сумму всех целых и сумму всех вещественных в файле | ||
+ | |||
+ | ===Дополнительные задания=== | ||
#Дана программа, в которой встречаются ключевые слова begin end. Определить, правильно ли они расставлены | #Дана программа, в которой встречаются ключевые слова begin end. Определить, правильно ли они расставлены | ||
#По данному тексту слов составить таблицу | #По данному тексту слов составить таблицу | ||
слово список его вхождений в текст в формате (строка,столбец), (строка,столбец) | слово список его вхождений в текст в формате (строка,столбец), (строка,столбец) |
Текущая версия на 13:09, 16 августа 2014
Разработка лексических анализаторов с помощью программы GPLex
GPLex - программа для автоматической генерации сканеров (лексических анализаторов)
Комплект для практического занятия скачиваем отсюда. Состав:
- LexProjects.sln - файл решения, содержащее проект Lex1.csproj
- Lex1.csproj - файл демонстрационного проекта для GPLex
- gplex.exe - исполняемый файл генератора сканеров
- mymain.cs - основная программа, содержащая создание сканера и сканирование всех лексем в файле
- ShiftReduceParserCode.cs - файл проекта, необходимый для работы лексического анализатора
- SimpleLex.lex - файл, содержащий правила для генерации лексического анализатора
- generateScanner.bat - командный файл. Содержит одну команду: gplex.exe /noparser SimpleLex.lex
- SimpleLex.cs - файл проекта, автоматически создаваемый при запуске generateScanner.bat
- a.txt - файл программы, подаваемой на вход сгенерированному лексеру
Компиляция проекта и выполнение программы
- Выполняем команду gplex.exe /noparser SimpleLex.lex, запустив командный файл generateScanner.bat
- При этом генерируется файл SimpleLex.cs, содержащий код лексера. Ключ /noparser означает, что генерируется лексер без парсера.
- Открываем и компилируем .sln
- Меняем содержимое a.txt, запускаем проект и анализируем результаты
Файл ScannerHelper.cs
Вспомогательный файл для сканера. Содержит глобальные определения. В данном случае ScannerHelper.cs содержит перечислимый тип Tok, содержащий виды лексем, возвращаемых нашим сканером.
Лексема EOF должна идти первой и быть связанной с константой 0.
Группы лексем
- EOF
- Литеральные константы: ID, INUM, RNUM
- Разделители, знаки операций: COLON, SEMICOLON, ASSIGN
- Ключевые слова: BEGIN, END, CYCLE
namespace ScannerHelper
{
public enum Tok { EOF = 0, ID, INUM, RNUM, COLON, SEMICOLON, ASSIGN, BEGIN, END, CYCLE };
}
Lex-файл SimpleLang.lex
%using ScannerHelper;
%namespace SimpleScanner
Alpha [a-zA-Z_]
Digit [0-9]
AlphaDigit {Alpha}|{Digit}
INTNUM {Digit}+
REALNUM {INTNUM}\.{INTNUM}
ID {Alpha}{AlphaDigit}*
// Здесь можно делать описания типов, переменных и методов - они попадают в класс Scanner
%{
public int LexValueInt;
public double LexValueDouble;
%}
%%
{INTNUM} {
LexValueInt = int.Parse(yytext);
return (int)Tok.INUM;
}
{REALNUM} {
LexValueDouble = double.Parse(yytext);
return (int)Tok.RNUM;
}
begin {
return (int)Tok.BEGIN;
}
end {
return (int)Tok.END;
}
cycle {
return (int)Tok.CYCLE;
}
{ID} {
return (int)Tok.ID;
}
":" {
return (int)Tok.COLON;
}
":=" {
return (int)Tok.ASSIGN;
}
";" {
return (int)Tok.SEMICOLON;
}
%%
// Здесь можно делать описания переменных и методов - они тоже попадают в класс Scanner
public int Sum = 0;
Формат .lex-файла
Определения %% Правила %% Пользовательский код
Пользовательский код содержит описания полей и методов, включаемых в генерируемый класс Scanner.
Пользовательский код может находиться в секции определений, для этого его надо заключить в
%{
%}
Комментарии к SimpleLex.lex
Секция описаний содержит строки
%using ScannerHelper;
%namespace SimpleScanner
которые после преобразования в SimpleLex.cs перейдут в строки
using ScannerHelper;
namespace SimpleScanner
{
// остальной генерируемый код
}
Строки
Alpha [a-zA-Z_]
Digit [0-9]
AlphaDigit {Alpha}|{Digit}
INTNUM {Digit}+
REALNUM {INTNUM}\.{INTNUM}
ID {Alpha}{AlphaDigit}*
в секции описаний определяют так называемые классы символов, которыми можно пользоваться при определении других классов символов и в секции правил.
В секции правил определяются все распознаваемые лексемы и семантические действия, которые нужно выполнять, когда встретилась та или иная лексема.
В семантических действиях в основном мы возвращаем целое значение для функции
int yylex()
и, возможно, выполняем другие действия. В качестве целого значения будем возвращать тип лексемы Tok, приведенный к целому типу. Дополнительные семантические действия проделаем в следующих случаях:
- в случае лексемы целой константы в LexValueInt будем возвращать целое, соответствующее текущей лексеме,
- в случае лексемы вещественной константы в LexValueDouble будем возвращать вещественное, соответствующее текущей лексеме
Переменная
yytext
используемая в коде, является стандартным полем класса Scanner и представляет собой текстовое представление текущей лексемы.
Регулярные выражения, используемые в секции определений
. | один символ кроме '\n' |
* | ноль или более повторений |
+ | одно или более повторений |
? | ноль или одно повторение |
[] | класс символов, обозначающий любой символ внутри [] |
^ | при использовании внутри [] обозначает отрицание. При использовании вне [] обозначает, что шаблон начинается с начала строки |
\ | начало esc-последовательности (например, \n) |
Внутри [] можно использовать: - для задания диапазона, например [0-9] ^ в начале для задания отрицания, например [^"\n] (не кавычки и не символ перехода на новую строку)
Примеры регулярных выражений
[0-9] | |
[0-9]+ | |
[0-9]*\.[0-9]+ | \. здесь обозначает точку, т.к. просто . имеет другое значение |
[+-]?[0-9]+ | |
#.* | комментарий, начинающийся с #, после которого идет ноль или более символов до конца строки |
\"[^"\n]*["\n] | кавычка, после которой идет любое количество не кавычек и не символов перехода на новую строку, после которых идет кавычка или переход на новую строку - так может задаваться литеральная строка. Здесь вне [] кавычка предваряется \, поскольку символ " имеет самостоятельный смысл; в [] кавычку можно писать без \, поскольку внутри [] кавычка не имеет самостоятельного смысла |
[^ \n\t]+ | любой символ, не являющийся пробелом, переходом на новую строчку, табулостопом, повторяющийся 1 или более раз |
^[ \t]*\n | строка из whitespace - пробелы или знаки табуляции, начинающиеся с начала строки и повторенные ноль или более раз, после которых идет символ перехода на новую строку |
Класс Scanner
Класс Scanner создается в файле SimpleLex.cs, который генерируется по SimpleLex.lex-файлу
- Основной метод - int yylex() - возвращает код следующей лексемы (токена)
- Свойства:
string yytext - текст лексемы int yyline - номер строки лексемы int yycol - номер столбца лексемы int yyleng - длина лексемы
Основная программа
using System;
using System.IO;
using SimpleScanner;
using ScannerHelper;
namespace Main
{
class mymain
{
static void Main(string[] args)
{
// Чтобы вещественные числа распознавались и отображались в формате 3.14 (а не 3,14 как в русской Culture)
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
var fname = @"..\..\a.txt";
Console.WriteLine(File.ReadAllText(fname));
Scanner scanner = new Scanner(new FileStream(fname, FileMode.Open));
int tok = 0;
do {
tok = scanner.yylex();
Console.Write((Tok)tok + " ");
if ((Tok)tok == Tok.INUM)
Console.WriteLine(scanner.LexValueInt);
else if ((Tok)tok == Tok.RNUM)
Console.WriteLine(scanner.LexValueDouble);
else if ((Tok)tok == Tok.ID)
Console.WriteLine(scanner.yytext);
else Console.WriteLine();
} while (tok != (int)Tok.EOF);
Console.ReadKey();
}
}
}
Комментарии к программе
Лексический анализатор (сканер) содержит главный метод - yylex() - который возвращает следующую лексему, точнее, ее тип, приведенный к целому типу.
В основной программе мы в цикле вызываем метод scanner.yylex() пока не будет достигнут конец потока
Обязательные задания
- Откомпилировать лексический анализатор и запустить его для файла a.txt
- Подсчитать количество, среднюю, минимальную и максимальную длину всех идентификаторов
- Найти сумму всех целых и сумму всех вещественных в файле
Дополнительные задания
- Дана программа, в которой встречаются ключевые слова begin end. Определить, правильно ли они расставлены
- По данному тексту слов составить таблицу
слово список его вхождений в текст в формате (строка,столбец), (строка,столбец)