Упражения в программировании: расчеты на языке Lua

 | 16.18

Мой Компьютер, №12, 17.03.2008

Предыстория

Так случилось, что в процессе обучения в университете я столкнулся с программой FEMM. Что это за программа, в данном случае не важно, нам она интересна другим: в ней есть встроенный интерпретатор языка программирования Lua (в МК уже были статьи на тему самого языка). Этот интерпретатор значительно расширяет функциональность программы. Если говорить конкретно в контексте FEMM, то использование данного ПО без встроенного Lua-интерпретатора вообще крайне затруднительно.

Что он дает? ПО имеет API, который мы можем использовать в Lua-скриптах, соответственно, комбинация «Lua-скрипты + API-функции» может обеспечить значительно более простое выполнение рутинных операций. И вот у меня возникла мысль: как же это реализуется? Пока мы не пошли дальше, отмечу также, что Lua активно применяется при разработке игр. Те, кто поинтересуются количеством игровых проектов, в которых используется рассматриваемый язык, думаю, будут удивлены.

Предложение

Итак, представим ситуацию: у нас есть приложение для нахождения корней уравнения. Уравнение прописывается в коде программы, пользователь же задает границы интервала поиска корней и нажимает кнопку «Решить», после чего результаты выводятся на экран. Понимаю, возможностей, мягко говоря, не густо. Но это сейчас и не принципиально. Рассмотрим два интересных момента, которых можно добиться, используя Lua в нашем приложении. Первый: что, если у пользователя есть не один, а целый набор интервалов, на которых ему нужно проверить наличие корней? Тогда он вынужден по очереди вводить интервалы, жать на кнопку «Решить». А если несколько упростить этот процесс? Например, написать Lua-скрипт, в котором с помощью цикла, меняя интервал поиска, нужное количество раз вызывать функцию решения уравнения, а нашу программу попросить этот скрипт выполнить. Второй: если мы захотим, чтобы наша чудо-программа решала другое уравнение, нам необходимо сначала править код, где задается уравнение, потом перекомпилировать программу.

Что, если мы не хотим каждый раз выполнять эти процедуры? В таком случае предлагается написать функцию определения уравнения на Lua и вынести в отдельный файл. И потом в нашей программе при вызове данной функции будем выполнять соответствующий Lua-код.

Вы можете спросить: какой резон вообще в реализации такой возможности? Попробую ответить: а что, если у нас реально большая программа, для которой компиляция занимает существенное время? Мы меняем всего ничего — и что, каждый раз ждать? Или, к примеру, мы еще сами не определились с исследуемой функцией и экспериментируем?

Отвлекся… Теперь самое главное: реализация в коде.

Ресурсы

Язык программирования для основной (назовем ее так) программы — С++. Среду разработки я возьму Borland C++ Builder 6.0. С сайта www.lua.org возьмем необходимые исходники, версия языка 5.1.3 (именно этой, самой свежей, логически код для других версий отличатся не будет, а вот синтаксически — немного), а также обязательно мануальчик, главный источник информации для людей, имеющих дело с Lua.

Код

Начнем по порядку. Для начала приведу содержимое Lua-скрипта, который автоматизирует подстановку интервалов с последующим решением уравнения.

 —script1.lua

     leftBounds = {-20, -15, -10, -5, 0}

rightBounds = {20, 15, 10, 5, 2}

i = 1

n = 5

repeat

setB(leftBounds[i],rightBounds[i])

Solve()

i = i + 1

until i > n

Поясняю содержимое построчно: комментарий с именем файла; массив левых границ; массив правых границ; счетчик; количество элементов в массиве; цикл, в котором осуществляется установка границ и вызов функции решения уравнения.

Теперь приведу код основной программы. Сразу весь, пояснять буду по ходу. Единственное, что опущу, — код функции уравнения, о нем чуть позже, и код функции решения уравнения — я брал самую простую дихотомию, вам же предоставляю свободу выбора, тем более, что мы же не алгоритмы поиска корней обсуждаем.

#include <iostream.h>

extern «C»{

 #include «lua.h»

 #include «lauxlib.h»

 #include «lualib.h»

 #include «luaconf.h»

}

double lB, rB; // границы интервала

//———

int setBounds(lua_State* myLua){

 lB = lua_tonumber(myLua, -2);

 rB = lua_tonumber(myLua, -1);

 return 1;

}

//———

int solve(lua_State* myLua){

 // тут код решения уравнения

 return 1;

}

int main(int argc, char* argv[])

{

 lua_State* myLua = lua_open();

 if (NULL == myLua){

 cout << «Error!»;

 return 0;

 }

 luaL_openlibs(myLua);

 lua_register(myLua,»setB»,setBounds);

 lua_register(myLua,»Solve»,solve);

 luaL_dofile(myLua, «script1.lua»);

 lua_close( myLua );

return 0;

}

Подключая заголовочные файлы, не забываем про extern «C»{}. Lua написан на С. Далее рассмотрим содержимое функции main(). Для начала нам нужно запустить экземпляр виртуальной машины Lua (я понял из мануала именно так), что мы и делаем в строке lua_State* myLua = lua_open();

Далее, я думаю, и без моего комментария понятно, что осуществляется проверка, все ли у нас хорошо. Стандартные функции Lua доступны из соответствующих библиотек, которые мы и подключаем с помощью luaL_openlibs(myLua);, подключим сразу все стандартные, хотя можно и по отдельности.

Теперь вспомним, что интерфейс в виде функций setB и Solve, который мы предоставили Lua для использования в скриптах, надо же как-то связать с С++, то есть с функциями, его реализующими (setBounds и solve соответственно). Это выполняется на участке кода lua_register(myLua,»setB»,setBounds); lua_register(myLua,»Solve»,solve);.

Далее выполним скрипт с помощью luaL_dofile(myLua, «script1.lua»); и закончим работу с нашей myLua.

Также обратите внимание на тип и параметр функций setBounds и solve, такая ситуация обязательна.

И последнее: все общение С++ — Lua и обратно происходит через специальный стек, нумерация в нем начинается с 1, номер последнего элемента -1; вызывая в Lua-коде функцию setB с двумя параметрами, мы помещаем в стек эти самые два параметра, поэтому в функции setBounds мы и пишем lB = lua_tonumber(myLua, -2);, то есть левой границе присваиваем второе с конца значение в стеке, преобразовывая его к числу.

Поздравляю! Наша программа поддерживает Lua-скрипты.

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

Пускай мы решаем уравнение x*x — 2 = 0. Создадим файл fun.lua, а в нем напишем следующее:

function func(a)

 return a*a — 2

end

Пояснения по коду, думаю, не нужны. С++ функция уравнения примет вид:

double f(double x){

 lua_State* locLua = lua_open();

 if (NULL == locLua){

 cout << «Error!»;

 return 0;

 }

 luaL_openlibs(locLua);

 luaL_dofile(locLua, «fun.lua»);

 lua_getglobal(locLua, «func»); // функция, которая будет вызвана

 lua_pushnumber(locLua, x); // параметр

lua_call(locLua, 1, 1); // вызываем функцию с одним параметром и возвращаем один результат

 double res = lua_tonumber(locLua, -1);

 lua_close( locLua );

 return res;

 }

 

Пояснения по коду я дам в тех моментах, где он отличается от обговоренного выше, начиная со строк с комментариями в коде. Сначала мы находим функцию, которая будет вызвана, потом помещаем в стек значение параметра для этой функции и наконец вызываем. Результат выполнения функции заносится в последний элемент стека, считывая значение которого, мы и получаем возможность предоставить его основной программе.

Еще раз поздравляю! Вторая цель достигнута. Теперь нам не требуется перекомпилировать программу, каждый раз, когда мы изменяем нашу функцию.

Заключение

Может показаться, что я упустил массу важных вещей, нюансов… но ведь ставился вопрос «как?». И на него ответ получен. Для заинтересовавшихся — в сети есть огромное количество информации, специализированные ресурсы и т.д. Спасибо за внимание! Удачи!

P.S. Позволю себе отметить одно из главных достоинств приведенного учебного примера: он РАБОТАЕТ!!! 🙂

Владимир ДУБИНИН

Robo User
Web-droid editor

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *