Препроцессор для словаря грамматического движка

Оглавление

Введение

Технические детали реализации

Директивы препроцессора

    Фолдинг

    Создание переменной и присваивание ей значения

    Удаление переменной

    Включение файла в процесс трансляции

    Альтернатива (оператор условного выбора)

    Остановка трансляции

    Остановка трансляции с выдачей сообщения об ошибке

    Печать сообщения без остановки трансляции

    Арифметические, логические и строковые выражения

    Встроенные функции

    Приоритеты

    Группировка операций

    Ограничители выражения

    Слияние лексем

    Последовательности

Отступления от стандарта и ограничения

Тестовая программа (пример использования)

Введение в препроцессинг

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

Синтаксис препроцессорных директив практически полностью соответствует средствам языка C/C++, с минимальными расширениями (директивы #begin...#end и значения формальных аргументов макросов по умолчанию) .

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

Технические детали реализации препроцессора

Классы, реализующие препроцессор, находятся в каталоге \Lem\Io\Iridium. Весь интерфейс реализован в классе Macro_Parser (см. заголовочный файл macro_parser.h и модуль ir_macro_parser.cpp). Транслятор открывает поток чтения (создает объект класса Macro_Parser) и читает лексемы (объекты класса BethToken) одну за другой (методами ::read, ::read_it). Можно также применять итераторы для перебора загруженных лексем (методы Macro_Parser::begin и ::end соответственно для начала и после-конца). Возвращаемые токены уже обработаны препроцессором. Фактически, вся препроцессорная обработка (включая догрузку файлов директивами #include) производится сразу после открытия потока чтения. Токены запоминаются в памяти в простом списке (поле Macro_Parser::list), а потом только возвращаются по требованию методами ::readXX.

Для хранения символов используются wchar_t - то есть символы хранятся в UNICODE. Благодаря этому использование национальных символов в объявлениях макросов ничем не ограничивается, можно давать макросам русские и китайские "имена", смешивая их по необходимости.

Класс препроцессора использует класс парсера-лексера Base_Parser<...> (см. parsers.h). Лексер Base_Parser, а точнее его специализации - лексеры для ASCII и UNICODE-символов могут использоваться в других программах, так как они включены в библиотеку LEM, и не привязаны непосредственно к обработке файлов в системе Solarix. Учебный пример использования парсеров можно видеть в программе LEM\Demo\Misc\Hello\3\Hello.cpp.

Директивы препроцессора

С точки зрения программной реализации, препроцессор - это автономная часть транслятора словаря, совершенно "не знающая" что-либо о смысле обрабатываемого ею программного текста.

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

Препроцессор обрабатывает следующие команды. 

Фолдинг

Пара директив #region и #endregion позволяет выделить в исходном тексте диапазон строк. Многие текстовые редакторы, предназначенные для редактирования текстов программ, умеют распознавать C# синтаксис и, таким образом, сворачивать куски текста. Программисту это даёт удобный способ структурировать исходный текст.

Весь текст после #region и #endregion до конца строки игнорируется.

Создание переменной и присваивание ей значения

#define имя_перем [выражение]

Простой формат без указания инициализирующего значения позволяет создать именованный флаг-переключатель, при этом подразумевается, что само его существование (или несуществование) несет определенную смысловую нагрузку. Переменная, определенная посредством директивы #define в одном файле, доступна и в остальных файлах, обрабатываемых в данном сеансе трансляции. Одно из возможных использований переменных - введение синонимов для стандартных ключевых слов языка ПРИИСК. К примеру, если вместо стандартного ключевого слова if желательно использовать русское если (хотя такие замены в программировании считаются плохим тоном), то можно где-то в первом транслируемом файле записать:

#define if  “если

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

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

                    #define азбука "alphabet {"
                   
азбука

приведет к остановке трансляции с выдачей диагностики о том, что лексема азбука не найдена. В действительности, Транслятор вместо лексемы азбука получит строку alphabet{, которая не является лексемой, поскольку содержит символ-разделитель - пробел.

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

Более сложный синтаксис - для использования макроса как процедуры, когда используются формальные аргументы макроса, заменяемые при подстановке:

#define r2( x, y, z ) x*x+y*y+z*z

Здесь r2 - имя функции, x, y, z - формальные аргументы.

Допустимо определение тела макропроцедуры в нескольких строках с помощью пары операторных скобок #begin и #end, как показывает следующий пример:

                    #define print_f( x, y, z )
                    #begin
                    float r2 = x*x+y*y+z*z;
                    printf( “r2=%rf”, r2 );
                    #end

Это более удобно, чем использование слэша согласно спецификации препроцессора языка C:

                    #define print_f( x, y, z ) \
                               float r2 = x*x+y*y+z*z; \
                               print_f( “r2=%rf”, r2 );

Впрочем, допускается и использование слэша.

Удобство использования макропроцедур расширяется возможностью влияния на ход подстановки тела макроса в текст программы с помощью операторов #if#else#endif.

                    #define pow_n( x, n )
                   
#begin
                    #if n==2
                   
x*x
                   
#else
                   
fpow(x,n)
                   
#end

Условный оператор #if в ходе считывания тела макропроцедуры будет просто запомнен, без анализа условия. При подстановке, когда формальный аргумент будет заменен конкретным значением, будет выполнено вычисление логического условия, и выполнена подстановка в текст программы одной из веток оператора #if. Важно отметить, что для приведенного примера строка:

pow_n( 123, 2 )

приведет к подстановке 123*123,а строка

pow_n( 123, n1 )

даст fpow(123,n1), даже если значение переменной n1 равно 2, так как препроцессор ничего не знает о значениях переменных, а анализирует фактически только их имена.

Формальным аргументам при описании макроса можно присваивать значения по умолчанию:

                    #define pow_n( x, n=2 ) ...

Синтаксис и семантика при этом соответствуют C++, то есть если число фактических аргументов меньше числа объявленных для макроса, то отсутствующие значения берутся из значений по умолчанию.

В отличие от макросов C/C++, аргументы макроса Iridium могут принимать значения не только единственной лексемы, но и списка. Например:

pow_n( 0 1 2, 3 )

В данном случае переменная x получит значение из трех лексем0 1 2. Чтобы передать в макропеременной список лексем с запятой в качестве разделителя, необходимо заключать этот список в круглые скобочки:

pow_n( (0,1,2), 3 )

 

Удаление переменной

#undef имя_перем

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

 

Включение файла в процесс трансляции

#include имя_файла

#include строк_выраж

Результат такой команды будет выглядеть так, как будто вместо строки с командой #include находится полное содержимое файла, имя которого задано. Необходимо заметить, что имя файла необходимо заключать в двойные апострофы:

#include filename.ext

Имя файла может быть задано как строковое выражение (о правилах составления таких выражений см. далее):

#include “filename” + “.ext

 

Альтернатива (оператор условного выбора)

Вариант 1:

                        #if

лог_выраж
                       
then-ветка
                       
#elifлог_выраж2
                       
else2-ветка
                       
#else
                       
else-ветка
                        
#endif

Вариант 2:

                        #if лог_выраж
                       
then-ветка
                       
#endif

Здесь под словами then-ветка и else-ветка подразумевается произвольный текст, состоящий из команд языка ПРИИСК и, возможно, директив препроцессора.

Итак, эффект от выполнения директивы такой. Вычисляется выражение, стоящие после оператора #if. Если его результат соответствует логическому значению истина, то будет произведена трансляция then-ветки, а else-ветка, если она присутствует, будет пропущена.

Таким образом, фрагмент:

                    #if 1
                    A
                   
#else
                   
B
                   
#endif

равнозначен тексту:

                    A

    Фрагмент:

                    #if 0
                    A
                   
#else
                   
B
                   
#endif

равнозначен тексту:

                    B

Операторы #if...#elif#else ...#then могут быть вложенными без каких-либо ограничений.

Рассмотрим, как оператор альтернативы используется для подавления многократного включения файлов оператором #include.

Допустим, что мы определили два алфавита, латиницу и кириллицу, причем определения классов (class буква) и координат (enum гласность и enum размер) решили сделать едиными для обоих алфавитов. Имеет смысл разнести оба алфавита в разные файлы, с тем чтобы можно было использовать их автономно. Поэтому определение класса и координат заносим в файл, к примеру, [abc.inc]:

                    #if !defined(ABC_INC)
 
                   #define ABC_INC
                    // Файл должен быть включен только один раз.
                    #define ABC "alphabet"
                    ABC
                    {
                     enum гласность { гласная согласная безгласная полугласная }
                     enum размер { большая малая }
                     class буква
                     {
                      attributes
{ гласность  }
                      dimentions {размер}
                     } // end of class
                   }
                   #endif

    Два файла с алфавитами могут иметь примерно такой вид:

    Файл с кириллицей:

                    #include "abc.inc"
                    alphabet
                    {
                     :
                     :
                    
Ю: буква
                     {
                        гласность:гласная
                        размер { Ю ю }
                     }
                     :
                     :
                   
}

И файл с латиницей:

#include "abc.inc"
alphabet
{
:
:
Z: буква
{
гласность:согласная
размер { Z z }
}
:
:
}

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

#include "rus_abc.inc"
#include
"lat_abc.inc"

    Теперь в основном текстовом файле определения Словаря достаточно записать:

#include "alphabet.inc"

и будут загружены определения обоих алфавитов.

Обратим внимание, что файл abc.inc будет включен в процесс трансляции дважды: один раз при чтении файла rus_abc.inc, и второй раз при чтении lat_abc.inc. Естественно, повторное определение классов и координат приведет к остановке трансляции с соответствующей диагностикой. Чтобы избежать этого, в файле abc.inc используется оператор #if...#endif. При первом чтении этого файла переменная ABC_INC еще не создана, поэтому строка:

#if !defined( ABC_INC)

позволит прочитать весь текст до оператора #endif. Вторая строка:

#define ABC_INC

создаст переменную ABC_INC. Поэтому во время второго чтения файла abc.inc выражение !defined(ABC_INC) имеет значение ЛОЖЬ, и произойдет попытка трансляции else-ветки, которой нет, и в результате содержимое файла будет вообще пропущено. Все последующие трансляции файла abc.inc будут иметь тот же результат, поскольку, будучи единожды определена, переменная ABC_INC существует весь сеанс трансляции. Только используя директиву #undefABC_INC можно заставить транслировать файл abc.inc повторно, если конечно на это есть причины.

Остановка трансляции

#stop

    Происходит принудительная остановка трансляции, файл двоичного образа Словаря не сохраняется. Все происходит так, будто Транслятор встретил ошибку в строке, где размещена директива #stop.

Остановка трансляции с выдачей сообщения об ошибке

#error выражение_для_печати

На терминале печатается сообщение о месте появления директивы #error, далее текст, заданный выражением, и после этого трансляция останавливается без сохранения бинарного образа Словаря. Эта директива полезна для контроля правильности задания требуемых макросов. Например, если макропеременная mvar должна быть обязательно определена, то фрагмент:

#if !defined(mvar)
#error
mvar must be defined!
#endif

проконтролирует это условие.

Печать сообщения без остановки трансляции

#print выражение_для_печати

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

Пример использования:

#print Контрольная точка трансляции

Арифметические, логические и строковые выражения

Арифметические и логические

Операндами служат числа в целочисленном формате в десятеричной системе счисления.

 

Унарные операции.

унарный плюс: +операнд

унарный минус: -операнд

инкремент: операнд++

декремент: операнд--

Бинарные арифметические операции.

сложение: операнд +операнд

вычитание: операнд-операнд

умножение: операнд*операнд

деление: операнд/операнд

остаток от деления: операнд%операнд

Бинарные логические операции.

равно: операнд==операнд

не равно: операнд!=операнд

больше: операнд>операнд

не меньше: операнд>=операнд

меньше: операнд<операнд

не больше: операнд<=операнд

    Результатом этих операций всегда будет целое число, 1 в случае выполнения условия, и 0 в случае его невыполнения.

Логические

Операндами служат целые числа, причем значение 1означает ИСТИНУ, а 0 - ЛОЖЬ.

Унарные

Отрицание: !X

Бинарные

Логическое И: X && Y или X & Y

Логическое ИЛИ: X || Y или X | Y

Кроме того, можно использовать операторы сравнения ==,!= и т.д., поскольку они работают с целыми числами, а логические значения представляются как целые числа.

Строковые.

Бинарные операции.

Конкатенация: X+"abc"

Равенство: X==Y

Неравенство: X!=Y

Лексически больше: X>Y

Лексически не меньше: X>=Y

Лексически меньше: X<Y

Лексически не больше: X<=Y

Результат операций сравнения для строковых операндов является логическим значением, 1 в случае выполнения условия, 0 - в противоположном случае.

Встроенные функции

defined(имя_перем) - результат равен 1, если переменная с указанным именем существует, в противном случае возвращает 0. Имя переменной задается как лексема, а не как строковое выражение, поэтому запись типа:

defined("var"+"_name")

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

Приоритеты

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

[!]

[!=][==] [>] [<] [<=] [>=]

[&][&&]

[|][||]

Группировка операций

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

(2+3)*4 

Ограничители выражения

Естественным ограничителем выражения является особый символ "конец строки", который представлен двумя кодами 13 и 10. Этот символ автоматически вставляется текстовыми редакторами в файл.

Слияние лексем

Операция слияния двух (или нескольких лексем в одну) нередко используется внутри определения макроса. Общий формат:

a##b

в результате Транслятор увидит одну лексему ab. Если a или b являются формальными именами аргументов макроса, то при слиянии будет использоваться значения этих аргументов. Например, фрагмент

                   #define to_hex(a) 0x##a

                   to_hex(1234)

будет увиден Транслятором как

0x1234

Последовательности

Макрооператор

#seq

помещает при каждом употреблении в итоговый текст очередное целое число, начиная с 0.

Отступления от стандарта и ограничения

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

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

Во-вторых, не работает концепция поиска включаемых файлов в заданном списке каталогов (для компиляторов C/C++ обычно есть опции /I, через которую указываются каталоги, где искать файлы для #include).

В-третьих, не реализованы специальные макросы __FILE__, __LINE__ и другие.

Дополнительные материалы

Компилятор словаря

Внутренний язык описания словаря

  © Elijah Koziev 2010
прикладные проекты на основе грамматического словаря API грамматической машины компоненты для доступа к грамматическому словарю условия получения SDK токенизатор и сегментатор морфологический анализ и синтез лемматизатор база N-грамм синтаксический анализатор словоформы морфология и синтаксис русского языка падеж число род совершенный и несовершенный вид экспорт в SQL формат экспорт в XML формат скрипт SQL словаря структура SQL словаря структура XML словаря компоненты для доступа к грамматическому словарю ORM Persistent Dictionary Library лемматизация стемминг примеры использования грамматического словаря склонение существительных в русском языке склонение русских прилагательных спряжение глаголов в русском языке поиск текста с учетом морфологии OCR подсистема расширенные регулярные выражения генератор текста генератор случайного текста и имитатор рандомизатор синонимизатор перефразировщик Статистика буквенных паттернов

Грамматический словарь русского языка



Грамматический словарь
склонение и спряжение глаголов, существительных, прилагательных

В состав входит русский и английский словарь.

платформа:  Windows 2000 ... Windows 7
требования: 512 Mb свободной памяти, 300 Мб на диске
размер:         34 Мб

  скачать грамматический словарь купить грамматический словарь SDK грамматического словаря
грамматический словарь русского языка



SDK Грамматического словаря



SDK Грамматического Словаря
склонение и спряжение глаголов, существительных, прилагательных

В состав входит русский и английский словарь.

платформа:  Windows 2000 ... Windows 7
размер:         13 Мб

SQL словарь (демо):
sqlite mysql oracle firebird mssql

скачать демо-версию SDK купить SDK API грамматического словаря



Поисковая система



Integra
настольная и сетевая поисковая система 

платформа:  Windows XP ... Windows 7
требования: 512 Mb свободной памяти
размер:         21 Мб

Дополнительные компоненты:
MySQL поисковый сервер 13.5 Мб
Integra.Premium MySQL 3.9 Мб

скачать поисковую систему SDK поисковой системыописание поисковой системы



SDK Поисковой системы



SDK Поискового движка
API для настольной и сетевой поисковая система 

платформа:  Windows XP ... Windows 7
размер:         17 Мб

Дополнительные компоненты:

MySQL поисковый сервер 13.5 Мб
Integra.Premium MySQL 3.9 Мб

скачать SDK SDK поисковой системы



Экранный переводчик



Translator
экранный переводчик

платформа:  Windows XP ... Windows 7
требования: 256 Mb свободной памяти
размер:         4.4 Мб

Дополнительные компоненты:
расширенный англо-русский словарь 6.4 Мб


скачать экранный переводчикописание экранного переводчика



изменено 29-Jul-12