В общем, наверняка кому-то пригодится.
Задача: есть парсер ANTLR3 с целевым языком С (используется libantlr3c) и очень лень(или просто некогда) писать интерфейс к нему на чистом С (это медленно и для больших проектов, обычно написанных на С++, это лишний геморрой).
Решение: сгенерированный сишный ANTLR парсер можно компилить как CPP!
Но для этого нужны некоторые хитрости, которые я и попытаюсь изложить. Я рассмотрю пример, в котором в парсере используется глобальная область переменных (можно аналогично использовать и локальные области), которая содержит указатель на С++ класс. (Про области переменных см. книжку Теренса Парра и вики на сайте ANTLR, я это тут пояснять не буду).
Пример доработанного для работы с C++ ANTLR парсера:
Раскрывающийся текст
grammar My;
options
{
language = C;
}
scope MyGlobalScope // определяем глобальную область видимости
{
MyScopeClass *pInstance; // это указатель на наш CPP класс
}
@preincludes
{
// тут мы обманываем компилятор, сообщая ему, что у нас не CPP-компиляция, чтобы он не ругался на некоторые конструкции
#undef __cplusplus
}
@postincludes
{
// возвращаемся к нормальному режиму CPP после включения заголовка
#def __cplusplus
}
@header
{
#include "antlr3.h"
#include "MyScopeClass.h"
}
....
My_rule // какое-то правило ANTLR
scope MyGlobalScope; // используем глобальную область видимости
@init
{
$MyGlobalScope::pInstance = new MyScopeClass(); // создаём представитель класса
$MyGlobalScope::pInstance->init(); // инициализируем его, если нужно
}
@after
{
delete $MyGlobalScope::pInstance; // удаляем объект после работы
}
: variant1 { $MyGlobalScope::pInstance->reportVariant(1); } // сообщаем классу о том, что мы тут нашли
| variant2 { $MyGlobalScope::pInstance->reportVariant(2); }
;
Примечания: в области видимости ANTLR (scope) могут использоваться только переменные фиксированного размера. Т.е. мы не можем использовать, например, строку (string), а можем использовать только указатель на неё. Аналогично и с классами: обязательно используются только указатели. Ну либо типы фиксированного размера (например, int, char).
Для инициализации указателей и удаления объектов после работы используются блоки @init и @after. Там же можно выполнить какие-то другие действия, например, инициализацию созданного объекта.
Из этого парсера мы получим файлы: MyLexer.h,MyLexer.c,MyParser.h,MyParser.c
Подключаем их к проекту и указываем компилятору компилировать их как CPP.
Пишем сам класс MyScopeClass:
MyScopeClass.h
Раскрывающийся текст
#ifndef __MYSCOPECLASS_H__
#define __MYSCOPECLASS_H__
class MyScopeClass
{
MyScopeClass() {}
~MyScopeClass() {}
void init();
void reportVariant(int var);
}
#endif
MyScopeClass.cpp
Раскрывающийся текст
#include "MyScopeClass.h" // включаем наш заголовок класса
#include "MyLexer.h" // включаем код ANTLR
#include "MyParser.h"
.... // прописываем методы класса
Собственно, всё. Всё это работает и под вендой, и под линюксом.
Однако, есть ещё некоторые нюансы:
Если в заголовочник MyScopeClass.h нужно включить другой заголовочный CPP файл, то в его начало нужно поместить что-то вроде:
#ifndef __cplusplus
#define __cplusplus
#define __remove_cplusplus
#endif
а в конец
#ifdef __remove_cplusplus
#undef __cplusplus
#undef __remove_cplusplus
#endif
Это чтобы компилятор обошёл наши объявления из парсерного кода и воспринял заголовок как код CPP.
Далее, если в проект включены сразу несколько парсеров, то нужно придерживаться строгого разделения пространств имён. Проблема в том, что libantlr3c генерит все имена правил и лексем в общем пространстве имён и нельзя использовать в разных парсерах одни и те же имена для правил и лексем, иначе потом при включении заголовков они будут перекрывать друг друга. В этом случае лучше именовать все правила как-то так: My_rule1,MY_LEXEM1.
Всё. Теперь класс CPP можно смело использовать из кода ANTLR.