crossplatform.ru

Здравствуйте, гость ( Вход | Регистрация )

 
Ответить в данную темуНачать новую тему
> ANTLR и С++, использование классов в парсерах ANTLR
Iron Bug
  опции профиля:
сообщение 12.3.2011, 19:24
Сообщение #1


Профессионал
*****

Группа: Модератор
Сообщений: 1611
Регистрация: 6.2.2009
Из: Yekaterinburg
Пользователь №: 533

Спасибо сказали: 219 раз(а)




Репутация:   12  


Вот, ещё одна моя заметка из раздела "химия и жизнь", я бы так сказала :)
В общем, наверняка кому-то пригодится.

Задача: есть парсер 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.

Сообщение отредактировал Iron Bug - 12.3.2011, 19:29
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
smartchecker
  опции профиля:
сообщение 5.12.2012, 16:13
Сообщение #2


Студент
*

Группа: Участник
Сообщений: 83
Регистрация: 7.9.2011
Пользователь №: 2853

Спасибо сказали: 0 раз(а)




Репутация:   0  


Не совсем понятно, что нужно сделать, если нужен только lexer.
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
Iron Bug
  опции профиля:
сообщение 5.12.2012, 18:38
Сообщение #3


Профессионал
*****

Группа: Модератор
Сообщений: 1611
Регистрация: 6.2.2009
Из: Yekaterinburg
Пользователь №: 533

Спасибо сказали: 219 раз(а)




Репутация:   12  


бери лексер и собирай проект с ним.
вызов стандартный, как описано на сайте ANTLR. я не работала отдельно только с лексерами, ибо смысла в этом особого нет. так что не могу в деталях сказать, как употреблять их результаты. наверное, какая-то последовательность лексем на выходе должна быть.
вообще, создаётся лексер так (этот кусок читает данные из файла):
    
    string input_file_name="some_file.txt";
    pANTLR3_UINT8        fName = (pANTLR3_UINT8)input_file_name.c_str();
    pANTLR3_INPUT_STREAM        input = 0;
    script_nameLexer                       lxr = 0;
    pANTLR3_COMMON_TOKEN_STREAM        tstream = 0;

    input    = antlr3AsciiFileStreamNew(fName);

    if ( input == NULL )
    {
        throw string("Cannot open input file.\n");
    }

    lxr        = script_nameLexerNew(input);        // script_nameLexerNew is generated by ANTLR

    if ( lxr == NULL )
    {
    throw string("Unable to create the lexer due to malloc() failure1\n");
    }

    tstream = antlr3CommonTokenStreamSourceNew(ANTLR3_SIZE_HINT, TOKENSOURCE(lxr));

    if (tstream == NULL)
    {
    throw string("Out of memory trying to allocate token stream\n");
    }


А вот удаление объектов:
    if(tstream != 0)
    {
        tstream->free(tstream);
        tstream    = 0;
    }
    if(lxr != 0)
    {
        lxr->free(lxr);
        lxr    = 0;
    }
    if(input != 0)
    {
        input->close (input);
        input= 0;
    }

тут script_name - имя скрипта (script_name.g) ANTLR. реализация в С создаёт фабрику script_nameLexerNew для генерации лексера. это описано в документации к libantlr3c.
как работать с полученным tstream - я сказать не могу. я его скармливаю парсеру. код libantlr3c открыт - можешь там посмотреть, что парсер с ним делает. или в документации поискать. ещё есть рассылка по ANTLR - там автор libantlr3c тоже участвует и отвечает на вопросы.

P.S. насчёт макросов переопределения _cplusplus - это нужно не для всех компиляторов. похоже, что для современных вообще не нужно. у меня относительно недавно собирался проект с парсером и спокойно всё собралось без лишних препроцессорных команд. только опции компиляции сишных файлов как С++ нужны были.

Сообщение отредактировал Iron Bug - 5.12.2012, 18:50
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение
ViGOur
  опции профиля:
сообщение 8.12.2012, 22:05
Сообщение #4


Мастер
******

Группа: Модератор
Сообщений: 3296
Регистрация: 9.10.2007
Из: Москва
Пользователь №: 4

Спасибо сказали: 231 раз(а)




Репутация:   40  


Разделил тему: Ошибка при использовании ANTLR, Java
Перейти в начало страницы
 
Быстрая цитата+Цитировать сообщение

Быстрый ответОтветить в данную темуНачать новую тему
Теги
Нет тегов для показа


2 чел. читают эту тему (гостей: 2, скрытых пользователей: 0)
Пользователей: 0




RSS Текстовая версия Сейчас: 29.11.2024, 10:05