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© 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='-г'
)

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

Результат завантажити тут

В архіві містяться наступні файли
  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-функцій. Для хорошої перевірки цей код слід безоплатно віддати народові, що я і роблю. Сподіваюся, кому-небудь знадобиться. Дякую за увагу до моєї праці.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.