суббота, 30 октября 2010 г.

Обновление примера грамматики с boost::spirit

Обновил пример для грамматики boost::spirit. Порядок построения узлов для бинарных операторов с лево-правой ассоциативностью (т.е. для всех бинарных операторов данной грамматики) был неверен, т.е. в цепочках типа
1 * 2 * 3 / 4
сначала выполнялась операция деления для операндов 3 и 4, затем операция умножения для операнда 2 и результата предыдущей операции и т.д. (на самом деле немного не так, поскольку в исходной версии была предпринята неуклюжая попытка построения правильного дерева, которая, к сожалению, работала только для простых случаев типа 5 * 6 / 7). На данный момент синтаксические деревья для любых сколь угодно сложных выражений парсятся корректно (по крайней мере в тех тестах, которые я написал и добавил в новую версию программы).
Кстати, пока занимался отладкой примера, обнаружил, что python может неверно вычислять арифметические выражения:
$ python
Python 2.6.4 (r264:75706, Jun  4 2010, 18:20:31) 
[GCC 4.4.4 20100503 (Red Hat 4.4.4-2)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 8 * 4 / ( 2 - 7 ) * 6 / 3
-14
>>>
Или я чего-то не понимаю в питоне, или это какой-то баг. В любом случае непонятно, как можно доверять языку, который неправильно вычисляет арифметические выражения. Может им баг засабмиттить?

Вот ссылка на новую версию программы.

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

Updateзафайлил баг в питон, но мне мягко объяснили в чем моя ошибка. Вот дословно комментарий от Mark Dickinson:

It's not a bug:  you're seeing Python's rules for integer division, which do indeed differ from those of (some) other languages when either the divisor or the dividend is negative.  For integers x and y, in Python 2.x, x / y gives the floor of the exact quotient.

>>> -32 / 5
-7
>>> 32 / -5
-7

In Python 3.x, the '/' operator does 'true division', giving you (a floating-point approximation to) the true result:

>>> -32 / 5
-6.4
>>> 32 / -5
-6.4

You can also get this behaviour in Python 2.x if you start your script with 'from __future__ import division'.

See the documentation at:

http://docs.python.org/reference/expressions.html#binary-arithmetic-operations

for more.

В общем, оператор '/' имеет какую-то странную семантику в питоне 2.x

пятница, 20 августа 2010 г.

Как записать аудио CD из файлов flac и mp3

Итак, допустим у нас имеется директория с файлами flac или mp3. Для начала нужно создать файлы cdr. В случае с flac:
for i in *.flac ; do flac123 -w "${i/\.flac/.cdr}" "$i" ; done
В случае с mp3 почти то же самое:
for i in *.mp3 ; do mpg123 -s "$i" > "${i/\.mp3/.cdr}" ; done
Затем вставляем болванку CD в привод и вводим в терминале
cdrecord -v speed=8 -swab -pad -audio *.cdr
Если нужно, чтобы треки играли без перерыва, то эта команда изменится на
cdrecord -v speed=8 -swab -pad -audio -dao *.cdr
Важно заметить, что названия треков должны быть отсортированы. Если исходные flac или mp3 файлы были пронумерованы (как это обычно бывает), то треки на CD будут записаны в правильном порядке, если же нет, то нужно вместо *.cdr в последних двух командах подставить список cdr файлов в нужной последовательности.

вторник, 17 августа 2010 г.

Приемы визуализации состояния кода под управлением Subversion

Здесь я не буду рассматривать стандартные приемы визуализации типа svn log, svn log -v и т.п. Вместо этого я хотел бы показать несколько скриптов-однострочников, которые можно запустить из командной строки оболочки и которые эффективно отражают состояние рабочей версии кода, управляемого системой контроля версий. Поскольку я чаще всего работаю с Subversion, я выбрал именно эту систему контроля версий, хотя это вовсе не обязательное условие и в принципе эти команды при необходимости могут быть адаптированы под любую другую систему, например CVS. В качестве примера рабочей версии кода я взял свой проект sudokurapid. Итак, переходим к приемам.
  1. Самая простая задача. Найти все исходники (например, .cc и .h файлы) в рабочей директории и вывести статистику по количеству строк, слов и символов. В данном случае присутствие какой-либо системы контроля версий вообще не требуется.
    $ find . -name '*.cc' -o -name '*.h' | xargs wc | sort -n
       33    60   651 ./sudokuRapidCommon.h
       62   146  1201 ./sudokuRapidQt/main.cc
       66   129  1277 ./sudokuRapidQt/sudokuForm.h
       66   156  1456 ./sudokuRapidQt/sudokuScene.h
      117   272  2684 ./sudokuRapidQt/sudokuCell.h
      167   373  3654 ./sudokuRapid.h
      171   497  4244 ./sudokuRapidConsole/main.cc
      175   603  4738 ./sudokuRapidQt/sudokuScene.cc
      191   499  4377 ./sudokuRapidQt/sudokuForm.cc
      248   781  6609 ./sudokuRapidQt/sudokuCell.cc
      481  1702 14315 ./sudokuRapid.cc
     1777  5218 45206 итого 
  2. Вывести список всех ревизий, выделив разными цветами имена отдельных пользователей (в данном проекте только один пользователь lyokha, но допустим, что имеется еще один пользователь someone)
    $ svn log | grep '^r[0-9]' | hl -46 lyokha -47 someone
    r16 | lyokha | 2010-08-17 11:40:36 +0400 (Втр, 17 Авг 2010) | 2 lines
    r15 | lyokha | 2009-03-23 16:22:27 +0300 (Пнд, 23 Мар 2009) | 2 lines
    r14 | lyokha | 2009-03-23 15:16:04 +0300 (Пнд, 23 Мар 2009) | 11 lines
    r13 | lyokha | 2009-02-19 22:00:51 +0300 (Чтв, 19 Фев 2009) | 2 lines
    r12 | lyokha | 2009-02-16 14:39:36 +0300 (Пнд, 16 Фев 2009) | 2 lines
    r11 | lyokha | 2009-02-16 14:23:15 +0300 (Пнд, 16 Фев 2009) | 5 lines
    r10 | lyokha | 2009-02-14 17:28:27 +0300 (Сбт, 14 Фев 2009) | 2 lines
    r9 | lyokha | 2009-02-14 16:04:59 +0300 (Сбт, 14 Фев 2009) | 3 lines
    r8 | lyokha | 2009-02-12 19:45:31 +0300 (Чтв, 12 Фев 2009) | 18 lines
    r7 | lyokha | 2009-02-11 23:27:04 +0300 (Срд, 11 Фев 2009) | 2 lines
    r6 | lyokha | 2009-02-11 22:08:13 +0300 (Срд, 11 Фев 2009) | 13 lines
    r5 | lyokha | 2009-02-07 14:39:56 +0300 (Сбт, 07 Фев 2009) | 2 lines
    r4 | lyokha | 2009-02-06 14:06:17 +0300 (Птн, 06 Фев 2009) | 3 lines
    r3 | lyokha | 2009-02-06 11:31:39 +0300 (Птн, 06 Фев 2009) | 2 lines
    r2 | lyokha | 2009-02-06 11:27:49 +0300 (Птн, 06 Фев 2009) | 2 lines
    r1 | lyokha | 2009-02-05 16:24:32 +0300 (Чтв, 05 Фев 2009) | 2 lines

    В данном случае я использовал утилиту hl, которая позволяет подсвечивать разными цветами (46 и 47 - это номера цветов для 256-цветного терминала) шаблоны (lyokha и someone) прямо в терминале. Если бы в списке был пользователь someone, его имя было бы подсвечено другим цветом. 
  3. Вывести статистику работы для определенного пользователя (lyokha) в псевдографическом виде. Здесь я использую замечательную утилиту diffstat:
    $ for i in `svn log | grep lyokha | awk '{print $1}' | sed 's/r//'`
    > do svn diff -c$i
    > done | diffstat -m -f 4 -w 80
     ChangeLog                       |   62    62     0     0 ++++
     Makefile.am                     |   41    36     0     5 ++!
     README                          |   37    37     0     0 ++
     configure.ac                    |   77    72     0     5 +++++
     sudokuRapid.cc                  |  539   507    26     6 +++++++++++++++++++++++++++++++++++++--
     sudokuRapid.h                   |  172   169     2     1 ++++++++++++
     sudokuRapidCommon.h             |   33    33     0     0 ++
     sudokuRapidConsole/Makefile.am  |    4     3     0     1 
     sudokuRapidConsole/configure.ac |   23    23     0     0 +
     sudokuRapidConsole/getStat.sh   |   54    54     0     0 ++++
     sudokuRapidConsole/main.cc      |  172   171     0     1 ++++++++++++
     sudokuRapidQt/main.cc           |   63    62     0     1 ++++
     sudokuRapidQt/sudokuCell.cc     |  346   256     8    82 ++++++++++++++++++-!!!!
     sudokuRapidQt/sudokuCell.h      |  138   117     0    21 ++++++++!!
     sudokuRapidQt/sudokuForm.cc     |  274   215    24    35 +++++++++++++++--!
     sudokuRapidQt/sudokuForm.h      |   72    68     2     2 +++++
     sudokuRapidQt/sudokuForm.ui     |   97    97     0     0 +++++++
     sudokuRapidQt/sudokuRapidQt.pro |   16    15     0     1 +
     sudokuRapidQt/sudokuScene.cc    |  205   182     7    16 +++++++++++++-
     sudokuRapidQt/sudokuScene.h     |   71    67     1     3 ++++-
     sudokurapid.spec                |  103    91     3     9 ++++++
     21 files changed, 2337 insertions(+), 73 deletions(-), 189 modifications(!)
    В первом столбце указано имя файла, в следующих - общее количество измененных строк, количество вставленных, удаленных и измененных строк, а также псевдографическая гистограмма активности изменений. Стоит заметить, что количество измененных строк (отмеченных символом !) определяется эвристически на основе анализа отдельных чанков, и поэтому в редких случаях баланс вставленных/удаленных/измененных строк может немного не соответствовать действительности.
  4. Можно графически визуализировать процесс изменения репозитория во времени с использованием утилиты gource. gource не поддерживает Subversion напрямую, поэтому нам понадобится дополнительный скрипт svn-gource.py. Приготовления включают создание промежуточных файлов:
    svn log --verbose --xml > sudokurapid.log
    svn-gource.py --filter-dirs sudokurapid.log > sudokurapid-gource.log
    После этого запускаем
    gource --log-format custom sudokurapid-gource.log
    откидываемся на спинку кресла и смотрим замечательный фильм. Вот, например, один из кадров:


пятница, 13 августа 2010 г.

C++ жесткач. Что это значит? :)

Небольшое упражнение. Кто сможет объяснить приведенный ниже код?
typedef int  ( & ( *  fun_t )( void ) )[ 10 ];

int  ( &  fun1( void ) )[ 10 ]
{
    static int  ar[ 10 ];

    return ar;
}


int  main( void )
{
    int ( &  ar1 )[ 10 ]( fun1() );

    fun_t  fun( fun1 );

    int ( &  ar2 )[ 10 ] = fun();
}

среда, 28 июля 2010 г.

Как прикрутить вызов фортрановской функции из интерпретатора CINT

CERN ROOT - программа для анализа больших объемов данных, ориентированная прежде всего на использование в научной среде. Программа обладает широкими возможностями по визуализации данных и имеет встроенный интерпретатор языка C++ CINT. Как известно, в физике до сих пор имеет широкое применение язык программирования FORTRAN, на котором написано значительное количество интересных библиотек. Однажды мой коллега-физик попросил меня помочь разобраться в этом вопросе - ему нужно было вызывать функции фортрановской библиотеки MINUIT (разработанной тоже в CERN) непосредственно из интерпретатора CINT. Благодаря поискам решения этой задачи родился этот пример.
Итак, прежде всего создадим функцию на фортране, которая будет вызываться из CINT. Пусть это будет функция, складывающая два числа, также создадим в ней COMMON блок для того, чтобы иметь возможность поэкспериментировать с изменением глобальных переменных:
       INTEGER FUNCTION SUM( A, B )
       DOUBLE PRECISION GL1
       DOUBLE PRECISION GL2
       INTEGER GL3
       COMMON /GL/ GL1( 1, 2 ), GL2, GL3

       INTEGER A, B
       SUM = A + B
       GL1( 1, 1 ) = 0.1
       GL1( 1, 2 ) = 0.2
       GL2 = 10.89
       GL3 = 8
       RETURN
       END
Разместим этот код в файле Sum.f. Теперь нам нужно создать код на C++, который, во-первых, будет вызывать функцию SUM(), а во-вторых, сможет быть вызван из интерпретатора CINT. Пусть вызов SUM() будет организован в виде функтора (т.е. класса с определенным оператором ()) - это упростит семантику вызова из CINT. Разместим наш код в файлах Sum.h и Sum.c. Это содержимое Sum.h:
#include <iostream>
#include "TObject.h"


extern "C" { extern int  sum_( int*, int* ); }

extern struct  fort_struct { double  a[ 2 ][ 1 ]; double  b; int  c; }  gl_;


class  Sum : public  TObject
{
    public:
        int  operator()( int  a, int  b )
        {
            return sum_( &a, &b );
        }

        void  accessGl( void ) const
        {
            printGl();
            gl_.a[ 0 ][ 0 ] = 34.9;
            gl_.a[ 1 ][ 0 ] = 4.1;
            gl_.b = 0.88;
            gl_.c = 70;
            printGl();
        }

    private:
        void  printGl( void ) const
        {
            std::cout << "gl_ = " << gl_.a[ 0 ][ 0 ] << ", " <<
                         gl_.a[ 1 ][ 0 ] << ", " << gl_.b << ", " << gl_.c <<
                         std::endl;
        }

        ClassDef( Sum, 1 )
};

Это содержимое Sum.c:
#include "Sum.h"

ClassImp( Sum )

Сразу отметим наличие определений ClassDef() и ClassImp() - это то, что позволит сделать из нашего Sum объект, с которым интерпретатору CINT будет комфортно работать.
Теперь замечания по использованию фортрановского кода в коде C++:
  1. Фортрановские функции должны связываться с C++ кодом как extern "C", при этом название функции записывается в нижнем регистре (несмотря на то, что в исходном коде оно может быть в верхнем регистре), а в конце добавляется символ _ - именно так компилятор фортрана записывает названия функций в объектном коде. В нашем случае функция SUM() трансформировалась в sum_().
  2. Аргументы передаются в фортрановскую функцию через указатели, поэтому функция двух целочисленных аргументов SUM() превратилась в sum_( int*, int* ).
  3. Глобальные переменные, объявленные в блоке COMMON, также записываются в нижнем регистре с добавлением символа _. Также обратите пристальное внимание на то, что столбцы и строки многомерных массивов в C++ и фортране меняются местами, так, переменная GL1( 1, 2 ) стала double a[ 2 ][ 1 ] в коде C++.
Исходный код готов, теперь нам нужно создать из него подключаемую библиотеку, ниже приводим содержимое Makefile:
all:
 rootcint -f dict_sum.cxx -c Sum.h
 g++ -fPIC -I$(ROOTSYS)/include/root -c dict_sum.cxx Sum.c
 g77 -fPIC -c Sum.f -o Sum_f.o
 g++ -shared -o Sum.so dict_sum.o Sum.o Sum_f.o

clean:
 rm *.o *.so dict_sum.cxx dict_sum.h
Обратите внимание на использование rootcint. Эта утилита создает вспомогательный код, необходимый для использования Sum в CINT, название файла dict_sum.cxx выбрано произвольно. Фортрановский код компилируется с помощью компилятора g77, но ничто не мешает заменить его на gfortran, если это необходимо. После запуска make создается объект Sum.so, который мы будем загружать в CINT, а затем использовать определенные в нем объекты. Пример сессии ROOT:
root [0] .L Sum.so
root [1] Sum s
root [2] int res = s(2,4)
root [3] .p res
(int)6
root [4] s.accessGl()
gl_ = 0.1, 0.2, 10.89, 8
gl_ = 34.9, 4.1, 0.88, 70
root [5]

понедельник, 26 июля 2010 г.

Тонкости передачи объектов через константные ссылки в C++

Может быть, для кого-то это это покажется очевидным с первого взгляда. Так вышло, что не для меня. Итак, вернемся к моему предыдущему посту о применении boost::spirit для построения простого пользовательского языка. Для вычисления уже готового AST был создан класс CexmcAST::BasicEval. Вот его определение:

    class  BasicEval
    {
        protected:
            typedef variant< int, double >  ScalarValueType;

        protected:
            virtual ~BasicEval();

        public:
            bool  operator()( const Subtree &  ast ) const;

        protected:
            ScalarValueType          GetScalarValue( const Node &  node ) const;

            virtual ScalarValueType  GetFunScalarValue( const Subtree &  ast )
                                                                        const;

            virtual ScalarValueType  GetVarScalarValue( const Variable &  var )
                                                                        const;

            ScalarValueType          GetBasicFunScalarValue(
                                        const Subtree &  ast, bool &  result )
                                                                        const;
    };
Для раскрытия проблемы нам понадобится также определение типа CexmcAST::Variable:
    struct  Variable
    {
        Variable() : index1 ( 0 ), index2( 0 ), addr( ( const int * ) NULL )
        {}

        std::string                             name;

        int                                     index1;

        int                                     index2;

        variant< const int *, const double * >  addr;
    };
BasicEval::operator() принимает в качестве аргумента константную ссылку на объект типа Subtree. В свою очередь, он вызывает рекурсивную функцию BasicEval::GetScalarValue(), которой передается этот объект. Однако, по определению, GetScalarValue() принимает константную ссылку на объект типа Node, который определяется как вариант:
    typedef variant< Tree, Leaf >          Node;
Наверное, кто-то уже предугадал дальнейший ход событий...
Унаследовавшись от BasicEval, я решил замапить адреса переменных addr объектов типа Variable, которые были созданы в деревьях ParseResult::expression (собственно, эти деревья и передаются через константные ссылки в BasicEval::operator()) в результате вызова phrase_parse(). По замыслу, маппинг происходил при первом вызове BasicEval::operator(), в дальнейшем, для получения значения переменной использовался бы уже замапленный адрес. Маппинг происходил где-то в глубинах вызовов BasicEval::operator() -> BasicEval::GetScalarValue() -> ... . Eстественно, для изменения объектов ParseResult::expression мне пришлось использовать const_cast. Адреса успешно присваивались, но при следующем вычислении выражения они также успешно забывались (снова оказывались неинициализированными).
Прблема (ну это же очевидно!) скрывалась в первом вызове BasicEval::GetScalarValue(). BasicEval::operator() не передавал ей константную ссылку на объект Subtree как я ожидал, вместо этого GetScalarValue() создавал новый объект типа Node, инициализированный переданным объектом типа Subtree. Последующие изменения относились именно к этому временному объекту, а исходный объект не менялся.
Выводы:
  1. Передача объекта по константной ссылке в C++ может иметь двойственную семантику: во-первых это передача существующего объекта без возможности его изменения, во-вторых - создание нового временного объекта также без возможности его изменения. Иногда от программиста требуется значительное внимание, чтобы понять какой из вариантов сработает. В данном случае я ожидал, что будет работать первый вариант, но за счет неявного вызова конструктора Node сработал второй - был создан временный объект, изменение полей которого никак не влияло на исходный объект. Даже если этот недосмотр не отразится на правильном выполнении программы, он может незапланированно снизить ее производительность из-за затрат на создание временных объектов.
  2. Будьте внимательны с const_cast - его использование вероломно нарушает семантику передачи объекта по ссылке и может явиться причиной подобных проблем. Если бы я не использовал const_cast, компилятор просто не пропустил бы такой код.

пятница, 23 июля 2010 г.

Использование boost::spirit для создания встроенного языка с поддержкой переменных, функций и безопасным преобразованием типов

Необходимость в использовании собственного языка появилась в одном проекте, связанном с моделированием физических процессов (для посвященных - программа основана на библиотеке Geant4, разрабатываемой командой CERN). Суть задачи состояла в написании фильтра для удаления событий из двух результирующих файлов, удовлетворяющих определенным критериям. Проблема заключалась в том, что выбор критериев должен полностью определяться конечным пользователем и запрограммировать все варианты не представлялось возможным да и не имело смысла. Результирующие файлы представляют из себя бинарные архивы, созданные с помощью библиотеки boost::serialization и хранят большие коллекции объектов-событий, тип событий в первом файле назовем tpt, во втором - edt.

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

    delete edt if Sum( clEDcol ) > 350 * MeV
    keep edt if clEDcol[2,2] + clEDcol[2,3] > Sum( Outer( clEDcol ) )
    delete edt if Sum( Inner( crEDcol ) ) > 200 * MeV
    delete tpt if event > 500
    delete tpt if ! tpt
Как видно из приведенного фрагмента, выражение представляет из себя пару
    действие условие
и должно умещаться в одну строку. Действие указывает нужно ли удалить событие данного типа или же сохранить. Условие представляет из себя пару
    if логическое_выражение
Поскольку выражения будут обрабатываться последовательно сверху вниз, то будет выбрано действие, соответствующее первому условию, вычисленному как true.
Условие представляет из себя логическое выражение. Оно может содержать любые арифметические операции, логические операции, функции с одним аргументом и переменные, в том числе элементы n-мерного вектора типа:
    a[n1,n2]
Под безопасностью преобразования типов я имею ввиду следующее:
  1. Должны правильно соблюдаться целочисленные и вещественные преобразования, например 3/2 должно вычисляться как целое значение 1, а 3/2.0 должно вычисляться как вещественное значение 1.5.
  2. Операторы и функции, ожидающие контекст определенного типа, не должны получать аргументы другого типа (например, при попытке передать в функцию, ожидающую 2-мерный вектор, скалярное значение должно быть сгенерировано исключение).
Вычисление условного выражения выполняется в 2 этапа:
  1. Парсинг условного выражение и создание абстрактного синтаксического дерева (AST).
  2. Рекурсивное вычисление (эвалюация) AST. В процессе вычисления AST выполняется подстановка переменных (а еще лучше, их заранее замапленных адресов) и вычисление функций. Переменные связаны с полями прочитанного объекта либо с помощью вычисления, либо прямым маппингом адреса. В предложенном примере реализовано вычисление двух скалярных функций: Sqr() и Sqrt(). Ни одна переменная не определена. Для добавления функций и переменных достаточно создать новый класс, наследующий CexmcAST::BasicEval и переопределить в нем виртуальные функции GetFunScalarValue() и GetVarScalarValue().
Парсинг условия достаточно выполнить только один раз в начале выполнения программы. Второй этап вычисления нужно выполнять отдельно для каждого прочитанного объекта, так как переменные в выражении фактически связаны (вычисляются или замаплены) с полями объекта. Исключения, связанные с безопасностью преобразования типов, могут быть сгенерированы только на втором этапе вычисления условного выражения, поскольку на первом этапе еще ничего не известно о типах встречающихся функций и переменных.

Абстрактное синтаксическое дерево представляет собой рекурсивный тип, содержащий узлы и листья. Узлы могут содержать 1 (унарные операторы и функции) или 2 (бинарные операторы) детей. Верхушка AST всегда узел, так, для выражения типа
    delete tpt if 0
будет постороено дерево с верхушкой - унарным оператором Top, который возвращает вычисленное значение своего единственного ребенка. Для реализации AST был использован рекурсивный вариант из boost.

Для хранения результатов парсинга выражения я использовал следующий тип:
    struct  ParseResult
    {
        ParseResult() : action( KeepTPT )
        {}

        void  Initialize( void )
        {
            action = KeepTPT;
            expression.children.clear();
            expression.type = Uninitialized;
        }

        Action   action;

        Subtree  expression;
    };
где Action и Subtree определены как
    enum  Action
    {
        KeepTPT,
        KeepEDT,
        DeleteTPT,
        DeleteEDT
    };

    struct  Subtree
    {
        Subtree() : type ( Uninitialized ), hasRLAssoc( false )
        {}

        void  Print( int  level = 0 ) const;

        void  PrintLeaf( const Leaf *  leaf, int  level = 0 ) const;

        std::vector< Node >  children;

        NodeType             type;

        bool                 hasRLAssoc;

        static const int     printIndent = 4;
    };
Структура Subtree - это и есть реализация AST. Node предоставляет выбор между узлом и листом (т.к. дети могут являться и тем и другим), NodeType предоставляет выбор между оператором и функцией:
    typedef std::string                    Function;

    typedef variant< int, double >         Constant;

    typedef variant< Variable, Constant >  Leaf;

    typedef recursive_wrapper< Subtree >   Tree;

    typedef variant< Tree, Leaf >          Node;

    typedef variant< Operator, Function >  NodeType;
Для парсинга выражения необходимо определить формальную грамматику и семантические действия, которые в нашем случае будут заключаться в построениии AST. В boost::spirit для определения семантических действий можно использовать библиотеку boost::phoenix, которая была создана в недрах проекта spirit.
    template  < typename  Iterator >
    struct  Grammar : grammar< Iterator, ParseResult(), space_type >
    {
        Grammar();

        rule< Iterator, ParseResult(), space_type >            statement;

        rule< Iterator, Action(), space_type >                 action;

        rule< Iterator, Subtree(), space_type >                condition;

        rule< Iterator, Node(), space_type >                   expression;

        rule< Iterator, Node(), space_type >                   primary_expr;

        rule< Iterator, Node(), space_type >                   function1;

        rule< Iterator, std::string(), space_type >            identifier;

        rule< Iterator, Leaf(), space_type >                   leaf_operand;

        rule< Iterator, Leaf(), space_type >                   constant;

        rule< Iterator, Leaf(), space_type >                   variable;

        rule< Iterator, Node(), space_type >                   or_expr;

        rule< Iterator, Node(), space_type >                   and_expr;

        rule< Iterator, Node(), space_type >                   relation;

        rule< Iterator, Node(), space_type >                   addition;

        rule< Iterator, Node(), space_type >                   multiplication;

        rule< Iterator, Node(), space_type >                   unary_expr;

        rule< Iterator, Operator(), space_type >               unary_op;

        rule< Iterator, Operator(), space_type >               mult_op;

        rule< Iterator, Operator(), space_type >               add_op;

        rule< Iterator, Operator(), space_type >               rel_op;

        real_parser< double, strict_real_policies< double > >  strict_double;

        function< Compiler >                                   op;
    };


    template  < typename  Iterator >
    Grammar< Iterator >::Grammar() : Grammar::base_type( statement )
    {
        statement = action[ op( _val, _1 ) ] >>
                                        *( condition[ op( _val, _1 ) ] );

        action = lit( "keep" ) >>
                ( lit( "tpt" )[ _val = KeepTPT ] |
                  lit( "edt" )[ _val = KeepEDT ] ) |
                lit( "delete" ) >>
                ( lit ( "tpt" )[ _val = DeleteTPT ] |
                  lit ( "edt" )[ _val = DeleteEDT ] );

        condition = lit( "if" ) >> expression[ op( _val, _1 ) ];

        expression %= or_expr;

        identifier %= lexeme[ alpha >> *( alnum | lit( '_' ) ) ];

        primary_expr = function1[ op( _val, _1 ) ] |
               lit( '(' ) >> expression[ op( _val, _1 ) ] >> lit( ')' ) |
               leaf_operand[ _val = _1 ];

        leaf_operand %= constant | variable;

        constant %= strict_double | int_;

        variable = identifier[ op( _val, _1 ) ] >>
                -( lit( '[' ) >> ( uint_[ op( _val, _1, 0 ) ] - lit( '0' ) ) >>
                   -( lit( ',' ) >> ( uint_[ op( _val, _1, 1 ) ] -
                      lit( '0' ) ) ) >> lit( ']' ) );

        function1 = ( identifier >> lit( '(' ) >> expression >> lit( ')' ) )
                    [ op( _val, _2, _1 ) ];

        or_expr = ( and_expr >> lit( '|' ) >> or_expr )
                  [ op( _val, _1, _2, Or ) ] |
                  and_expr[ _val = _1 ];

        and_expr = ( relation >> lit( '&' ) >> and_expr )
                   [ op( _val, _1, _2, And ) ] |
                   relation[ _val = _1 ];

        relation = ( addition >> rel_op >> addition )
                   [ op( _val, _1, _3, _2 ) ] |
                   addition[ _val = _1 ];

        addition = ( multiplication >> add_op >> addition )
                   [ op( _val, _1, _3, _2 ) ] |
                   multiplication[ _val = _1 ];

        multiplication = ( unary_expr >> mult_op >> multiplication )
                         [ op( _val, _1, _3, _2 ) ] |
                         unary_expr[ _val = _1 ];

        unary_expr = ( unary_op >> primary_expr )[ op( _val, _2, _1 ) ] |
                     primary_expr[ _val = _1 ];

        unary_op = lit( '-' )[ _val = UMinus ] | lit( '!' )[ _val = Not ];

        mult_op = lit( '*' )[ _val = Mult ] | lit( '/' )[ _val = Div ];

        add_op = lit( '+' )[ _val = Plus ] | lit( '-' )[ _val = Minus ];

        rel_op = lit( "<=" )[ _val = LessEq ] |
                 lit( ">=" )[ _val = MoreEq ] |
                 lit( "!=" )[ _val = NotEq ] |
                 lit( '<' )[ _val = Less ] |
                 lit( '>' )[ _val = More ] |
                 lit( '=' )[ _val = Eq ];
    }
Грамматические правила выражаются через вложенные правила, связанные между собой посредством операторов >> и |, и описания семантических действий, заключенных в квадратные скобки после вложенных правил. В простейшем случае правило может представлять собой лексему вида lit( "if" ). Оператор >> создает цепочку сканирования. Для получения соответствия сканируемого текста данному правилу необходимо чтобы вся цепочка соответствовала сканируемому тексту. Оператор | может применяться к цепочкам сканирования или отдельным правилам в выражении и предоставляет парсеру возможность выбора между его операндами: если левый операнд оператора | соответствует сканируемому участку, то будет выбран именно он, иначе парсер попытается выбрать правый операнд.
Правила могут быть рекурсивными, то есть ссылаться в своем определении на самих себя, например правило multiplication содержит в своем определении multiplication. Однако здесь присутствует одно очень важное ограничение: вложенность не должна создавать левую рекурсию, т.е. выражения, в которых явно или неявно определяемое правило входит в собственное выражение слева (причем неважно в какой из ветвей, разделенных оператором |), недопустимы. Например, правило
        multiplication = ( multiplication >> mult_op >> unary_expr )
                         [ op( _val, _1, _3, _2 ) ] |
                         unary_expr[ _val = _1 ];
недопустимо. Его использование не приведет к ошибке компиляции, однако в рантайме программа обязательно упадет.
Самое простое семантическое действие может быть выражено как [ _val = _1 ], здесь используются определенные в boost переменные, _val связана с результирующим правилом и представляет собой ссылку на его сигнатуру. Если посмотреть на определения правил, например правила multiplication:
        rule< Iterator, Node(), space_type >                   multiplication;
то можно заметить, что они могут создаваться с использованием конструкторов некоторых типов - сигнатур, в данном случае сигнатурой является Node(), это означает, что к правилу будет привязан объект типа Node. Переменная _1 - это ссылка на сигнатуру того правила, к которому применяется семантическое действие. Если семантическое действие применяется к выражению, заключенному в круглые скобки (например, цепочке сканирования), то могут быть задействованы переменные _2 и _3, ссылающиеся на сигнатуры правил, входящих в выражение. В простейшем случае, если сигнатуре результирующего правила нужно присвоить сигнатуру всего выражения справа (или, как частный случай, подвыражений, выбранных оператором |), можно использовать оператор %=, например:
        expression %= or_expr;
Более сложные семантические правила реализуются с помощью вспомогательного объекта op типа Compiler, который определен как
    struct  Compiler
    {
        template < typename  A, typename  B = boost::fusion::unused_type,
                   typename  C = boost::fusion::unused_type,
                   typename  D = boost::fusion::unused_type >
        struct  result { typedef void  type; };

        void  operator()( ParseResult &  parseResult, Action  value ) const;

        void  operator()( ParseResult &  parseResult, Subtree &  value ) const;

        void  operator()( Subtree &  ast, Node &  node ) const;

        void  operator()( Node &  self, Node &  left, Node &  right,
                          Operator  value ) const;

        void  operator()( Node &  self, Node &  child, Operator  value ) const;

        void  operator()( Node &  self, Node &  primary ) const;

        void  operator()( Node &  self, Node &  child, std::string &  value )
                                                                        const;

        void  operator()( Leaf &  self, std::string &  name ) const;

        void  operator()( Leaf &  self, int  value, size_t  index ) const;
    };
Реализацию операторов можно увидеть в прилагаемом коде.

Для того, чтобы запустить парсер и заполнить объект типа ParseResult, нужно вызвать функцию phrase_parse():
    phrase_parse( begin, end, customFilterGrammar,
                  CexmcCustomFilter::space, parseResult )
Здесь begin и end - итераторы на начало и конец обрабатываемого текста, customFilterGrammar - объект типа Grammar, parseResult - ссылка на объект типа ParseResult, который будет заполнен в результате действия phrase_parse(). Объект грамматики связан с типом ParseResult через Grammar::base_type, который соответствует правилу statement, сигнатурой которого в свою очередь является ParseResult(). Функция phrase_parse() ограждает пользователя от заботы о пробельных символах во время парсинга. Она возвращает false в случае неудачи, также неудачей можно считать случай частичного совпадения, когда begin не равен end после возвращения из phrase_parse()(итератор begin смещается в процессе парсинга и в случае успеха должен совпасть с end).
Некоторые тонкости реализации грамматики:
  1. Как видно из определений правил, все арифметические и логические правила выражены через правила более высокого приоритета и сами себя - например операциии сложения addition определены через multiplication и addition. Определение операции через операции более высокого приоритета позволяет соблюдать приоритет операций при построении AST. Определение операции через саму себя позволяет выстраивать цепочки этой операции в выражениии, например 4 + 8 + 10 - 2. Исключением являются операции отношения - правило relation, они выражаются только через операции более высокого приоритета, это не позволяет выстраивать их в цепочки. В самом деле, выражение a > 7 > b недопустимо.
  2. Проблема левой рекурсии и левоассоциативности. Взгляните еще раз на определение правила multiplication. Оно выражается через само себя и unary_expr. Запрет левой рекурсии означает, что unary_expr должен стоять левее, чем операнд multiplication. В свою очередь, это означает, что выражение 2 * 5 / 7 будет вычислено как 2 * (5 / 7), что даст неверный результат, так как операции умножения и деления левоассоциативны. Проблему можно решить двумя способами: привести леворекурсивное правило к праворекурсивному (это всегда возможно), или учесть изменение порядка операций в соответствующем операторе () типа Compiler, не изменяя синтаксическое правило. Я решил пойти вторым путем, соответствующий оператор выглядит следующим образом:
  3.     void  Compiler::operator()( Node &  self, Node &  left, Node &  right,
                                    Operator  value ) const
        {
            Subtree &  ast( boost::get< Subtree >( self ) );
    
            ast.children.push_back( left );
            ast.type = value;
    
            Subtree *  astRight( boost::get< Subtree >( &right ) );
    
            if ( ! astRight )
            {
                ast.children.push_back( right );
                return;
            }
    
            bool        hasLessPriorityThanRightOp( false );
            Operator *  rightOp( boost::get< Operator >( &astRight->type ) );
    
            if ( rightOp )
            {
                hasLessPriorityThanRightOp = OperatorPriority::IsLessThan(
                                                                value, *rightOp );
            }
    
            if ( astRight->hasRLAssoc || hasLessPriorityThanRightOp )
            {
                ast.children.push_back( right );
                return;
            }
            ast.children.push_back( astRight->children[ 0 ] );
            Subtree    astResult;
            astResult.children.push_back( self );
            astResult.children.push_back( astRight->children[ 1 ] );
            astResult.type = astRight->type;
            self = astResult;
        }
    
  4. Взгляните на определение правила constant:
            constant %= strict_double | int_;
    
    Правило strict_double - это видоизмененное правило double_, которое не позволяет парсить как double целочисленные значения:
            real_parser< double, strict_real_policies< double > >  strict_double;
    
    Проблема поднималась на форумах (см. например здесь) и было предложено это решение.
После успешного завершения парсинга мы имеем набор (в прилагаемом примере только один) объектов типа ParseResult. Все что остается сделать - это рекурсивно пройтись по выражениям expression этих объектов, подставляя значения переменных и выполняя функции. Для этого предназначен класс CexmcAST::BasicEval:
    class  BasicEval
    {
        protected:
            typedef variant< int, double >  ScalarValueType;

        protected:
            virtual ~BasicEval();

        public:
            bool  operator()( const Subtree &  ast ) const;

        protected:
            ScalarValueType          GetScalarValue( const Node &  node ) const;

            virtual ScalarValueType  GetFunScalarValue( const Subtree &  ast )
                                                                        const;

            virtual ScalarValueType  GetVarScalarValue( const Variable &  var )
                                                                        const;

            ScalarValueType          GetBasicFunScalarValue(
                                        const Subtree &  ast, bool &  result )
                                                                        const;
    };
Единственная публичная функция данного класса: operator(), которая принимает объект типа Subtree в качестве аргумента и вызывает рекурсивную функцию GetScalarValue(). Кроме того, BasicEval предоставляет две виртуальные функции GetFunScalarValue() и GetVarScalarValue(), которые можно использовать в классах-наследниках для определения дополнительных функций и переменных. Также, в классе-наследнике можно определить соответствующие функции, связанные с обработкой функций и переменных нескалярных типов.

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