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, однако навскидку с «легкого пинка» завести его не удалось. При наличии времени можно разобраться, но времени как всегда нет. Пришлось напрячь мыслительные мощности и придумать более быстрое и простое решение. Если читателю интересно, что из этого вышло, добро пожаловать под кат.

Реализуется это не просто, а очень просто — в модуле D пишем прототип функции, указывая что она расположена во внешнем модуле и использует C-соглашение о вызове функций extern© 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.

Результат можно скачать здесь

В архиве содержаться следующие файлы

lua.d — прототипы основных функций lualib.d — функции для работы с библиотеками Lua lauxlib.d — дополнительные функции 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-функций. Для хорошей проверки данный код следует безвозмездно отдать народу, что я и делаю. Надеюсь, кому-нибудь пригодится. Благодарю за внимание к моему труду.

© Habrahabr.ru