Lua API for D language

    Предисловие


    Данная заметка не будет слишком уж объемной, а скорее даже наоборот, небольшой.

    Достаточно продолжительное время я слежу за серией статей о языке D, публикуемой на Хабре. Ознакомившись с рядом источников, начиная от Википедии и заканчивая официальными руководствами по данному языку, пришел к выводу о целесообразности использования оного в своих научных проектах. Главный проект по докторской диссертации зашел в состояние тупика, требовал переработки (всплыл ряд механических вопросов). Переработку проекта и изучение нового для меня языка было решено совместить.

    Сказано сделано — большая часть кода довольно быстро была перенесена с C/C++ на D. Не смотря на различные мнения по поводу D бытующие в среде разработчиков ПО, язык пришелся мне по вкусу.

    Одна беда — в старом варианте проекта для задания параметров модели поезда и изменения логики работы без перекомпиляции использовался Lua-скриптинг. Те кто сталкивается с ним по роду деятельности знают, что существует хорошо разработанный и достаточно документированный API для разработки на C/C++.

    Что касается Lua-скриптинга в D, то существует ряд проектов, например LuaD, привносящий в программы на D возможность работы с Lua. Однако LuaD рассчитан на предыдущую версию 5.1. Попадался мне проект и для 5.2 — DerelictLua, однако навскидку с «легкого пинка» завести его не удалось. При наличии времени можно разобраться, но времени как всегда нет. Пришлось напрячь мыслительные мощности и придумать более быстрое и простое решение. Если читателю интересно, что из этого вышло, добро пожаловать под кат.

    1. Вызов C-функций из программы на D


    Реализуется это не просто, а очень просто — в модуле D пишем прототип функции, указывая что она расположена во внешнем модуле и использует C-соглашение о вызове функций
    extern(C) double my_C_func(int param1, double param2);
    

    Естественно, внешний модуль надо тем или иным способом скомпоновать с программой на D.

    В этой связи возникла первая идея — реализовать работу с Lua на C, а прототипы прописать в модуле на D и скомпилировать всё в одну программу. Был даже переделан скрипт сборки проекта (использую SCons) таким вот образом

    SConstruct для компиляции программы из модулей на C/С++ и D
    #--------------------------------------------------------------------
    #		Project globals
    #--------------------------------------------------------------------
    source_dir = 'src/'
    d_include_path = 'src/D/'
    
    release_target_dir = 'bin/release/'
    debug_target_dir = 'bin/debug/'
    
    target_name = 'train'
    
    #--------------------------------------------------------------------
    #	Release build configuration
    #--------------------------------------------------------------------
    release_env = Environment(
    
    	CC='gcc',
    	CXX='g++',
    	DMD='dmd',
    	DPATH=d_include_path,
    	LINK='gcc',
    	
    	CPPFLAGS='-O3',
    	DFLAGS='-O'
    	)
    
    release_env.VariantDir(release_target_dir,
    					   source_dir,
    					   duplicate=0)
    
    d_sources = Glob(release_target_dir + 'D/*.d')
    c_sources = Glob(release_target_dir + 'C/*.c')
    
    c_obj = release_env.Object(c_sources)
    d_obj = release_env.Object(d_sources)
    
    release_env.Program(release_target_dir + target_name, d_obj + c_obj, LIBS=['phobos2', 'lua'])
    
    #--------------------------------------------------------------------
    #	Debug build configuration
    #--------------------------------------------------------------------
    debug_env = Environment(
    
    	CC='gcc',
    	CXX='g++',
    	DMD='dmd',
    	DPATH=d_include_path,
    	LINK='gcc',
    	
    	CPPFLAGS='-g3',
    	DFLAGS='-g'
    	)
    
    debug_env.VariantDir(debug_target_dir,
    					 source_dir,
    					 duplicate=0)
    
    d_sources = Glob(debug_target_dir + 'D/*.d') 
    c_sources = Glob(debug_target_dir + 'C/*.c')
    
    c_obj = debug_env.Object(c_sources)
    d_obj = debug_env.Object(d_sources)
    
    debug_env.Program(debug_target_dir + target_name, d_obj + c_obj, LIBS=['phobos2', 'lua'])
    



    После успешного чтения тестового Lua-скрипта пришло понимание того, что сущностей слишком много — зачем писать модуль на C, если можно написать его на D, а необходимые функции экспортировать непосредственно из liblua.so?!? К тому же на D можно реализовать уже необходимый мне функционал, оформив его скажем в виде класса (так было сделано в предыдущей C++-версии проекта).

    Вооружившись руководством к Lua 5.2 и лежащими в /usr/include/ заголовочными файлами я приступил. Первоначально была мысль экспортировать только нужные мне функции и остановится на этом. Но потом мне стало стыдно — наверняка результаты этой работы могут пригодится кому-нибудь ещё. Поэтому C API к Lua был практически полностью портирован на D.

    2. D-библиотека для работы с Lua API


    Результат доступен на GitHub

    В архиве содержаться следующие файлы
    1. lua.d — прототипы основных функций
    2. lualib.d — функции для работы с библиотеками Lua
    3. lauxlib.d — дополнительные функции
    4. luaconf.d — описание некоторых типов и констант

    Что касается последнего файла то он портирован в той части, что используется внутри остальных модулей. Эту работу ещё предстоит проделать. В остальном данная библиотека позволяет использовать интерфейс к Lua из программы на D, так, как это делается при разработке на C/C++. Перечисленные модули подключаются к проекту на D и он компонуется с библиотекой liblua.so (ключ линкера -llua, если речь идет GNU/Linux).

    Реализованы все функции, описанные в Lua C API. При написании модулей все макросы в оригинальных заголовках, реализующие упрощенные вызовы базовых функций API, были заменены функциями.

    Для проверки был написан крошечный скрипт на Lua

    test.lua
    --	Глобальная переменная целого типа
    nv = 2
    --	Функция, вычисляющая квадрат аргумента
    my_number_sqr = function(x)
    	return x*x
    end
    

    Для его чтения пишем такой код на D, подключая нужные библиотеки
    main.d
    module	main;
    
    import	std.stdio;
    import	lua;
    import	lualib;
    import	lauxlib;
    //-------------------------------------------------------------------
    //
    //-------------------------------------------------------------------
    void main()
    {
    	// Получаем состояние интерпретатора Lua и грузим все библиотеки
    	lua_State *lua_state = luaL_newstate();
            luaL_openlibs(lua_state);
    
    	// Выполняем тестовый скрипт
    	if (luaL_dofile(lua_state, "test.lua"))
    	{
    		writeln("Error");
    	}
    
    	// Читаем целочисленное значение
    
            // Запоминаем вершину стека Lua
    	int top = lua_gettop(lua_state);
            // Кладем в стек значение nv
    	lua_getglobal(lua_state, "nv");
            // Снимаем полученное значение со стека
    	lua_Integer tmp =  lua_tointeger(lua_state, -1);
            // Восстанавливаем стек
    	while (top - lua_gettop(lua_state))
    		lua_pop(lua_state, 1);
    
    	// Вызываем функцию my_number_sqr
    	top = lua_gettop(lua_state);
    	lua_getglobal(lua_state, "my_number_sqr");
    	double x = 8;
    	lua_pushnumber(lua_state, x);
    	lua_pcall(lua_state, 1, 1, 0);
    	double ret = lua_tonumber(lua_state, 01);
    	while (top - lua_gettop(lua_state))
    		lua_pop(lua_state, 1);	
    	
            // Выводим результат
    	writeln("readed integer nv = ", tmp);
    	writefln("sqr(%f) = %f ", x, ret);
    
    	lua_close(lua_state);
    }
    

    Собираем его, не забыв прилинковать liblua.so (скрипт сборки может быть и проще, но отдельно писать было лень).
    SConstruct для компиляции тестовой программы
    #--------------------------------------------------------------------
    #		Project globals
    #--------------------------------------------------------------------
    source_dir = 'src/'
    d_include_path = 'src/D/'
    
    release_target_dir = 'bin/release/'
    
    target_name = 'test_lua_api'
    
    #--------------------------------------------------------------------
    #	Release build configuration
    #--------------------------------------------------------------------
    release_env = Environment(
    
    	CC='gcc',
    	CXX='g++',
    	DMD='dmd',
    	DPATH=d_include_path,
    	LINK='gcc',
    	
    	CPPFLAGS='-O3',
    	DFLAGS='-O'
    	)
    
    release_env.VariantDir(release_target_dir,
    					   source_dir,
    					   duplicate=0)
    
    d_sources = Glob(release_target_dir + 'D/*.d')
    d_obj = release_env.Object(d_sources)
    
    release_env.Program(release_target_dir + target_name, d_obj, LIBS=['phobos2', 'lua'])
    


    имея на выходе
    readed integer nv = 2
    sqr(8.000000) = 64.000000 
    


    Вместо заключения


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

    P.S.:


    Код придется серьезно поправить. Допустим, для корректной работы со строками пришлось внести некоторые коррективы в lua_tostring(...)
    string lua_tostring(lua_State *L, int idx)
    {
    		return to!string(lua_tolstring(L, idx, null));
    }
    

    добавив преобразование в string. Что ж, по мере использвания коррективы будут внесены
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 13
    • +2
      Так выложите на GitHub. Или вы свой сайт продвигаете ;)?
      • 0
        Неприменно выложу, когда достаточно оттестирую
        • +2
          И не забудьте оформить и зарегистрировать в репозитории code.dlang.org/, думаю многие помянут вас добрым словом.
          • 0
            Оформил, жду теперь когда они там у себя все проверят и обновят информацию о пакете
            • +1
              Собственно вот ссылка

              P.S.: Заодно присмотрюсь к dub
              • +1
                DUB — очень полезная штука.
                Практически стандарт.
                • 0
                  Как я понимаю — стандарт распространения ПО написанного на D?
                  • 0
                    Пакетный менеджер и билдер — два в одном.
          • +1
            Хотя, конечно, Вы правы — на гите будет удобнее развивать эту штуку
            • 0
              Правильно, не надо ждать — так можно никогда не дождаться этого момента. Я, кроме того, замечаю за собой весьма неприятное свойство — стоит рассказать кому-то, что ты сейчас делаешь, как почему-то моментально пропадает интерес заниматься этим дальше :(. Вот чтобы такого не случилось, надо, чтобы все уже было в открытом доступе, там, глядишь, подтянутся единомышленники и можно будет воспрянуть духом.
              • +1
                Просто перед выкладывание обычно старешься из последних сил навести порядок, чтобы не стыдно было. Рассказал — всё, сил больше нет. Пара недель прошла — занялся другими делами и когда соберешься с силами вернутсья и пилить дальше — неизвестно.
            • +1
              Выложил на GitHub. Ссылку в статье поправил
            • 0
              Ранее не пользовался системами контроля версий — считал что для моих любительских задач это слишком. Теперь, опробовав git, считаю что это ОЧЕНЬ удобно. Если интересен код, в котором используется предмет статьи, то вот ссылка на мой проект

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