Упражения в программировании: расчеты на языке Lua
08.09.08Мой Компьютер, №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. Позволю себе отметить одно из главных достоинств приведенного учебного примера: он РАБОТАЕТ!!! 🙂
Владимир ДУБИНИН
Web-droid редактор
Не пропустите интересное!
Підписывайтесь на наши каналы и читайте анонсы хай-тек новостей, тестов и обзоров в удобном формате!
Обзор мышки Logitech M196: немного и надолго
Мышка Logitech M196 предназначена для работы, доступная по цене, удобна при своих размерах, беспроводная, и работает целый год от батарейки. Что еще нужно?
Vodafone расширил доступный роуминг для украинцев на все страны ЕС
Vodafone телекомVodafone Украина расширил возможности использования услуг мобильной связи, позволяя своим абонентам пользоваться домашними тарифами в 28 странах Европейского Союза
Nvidia выпустит свои ARM-процессоры для ПК в сентябре 2025 года
Nvidia процессорNvidia планирует выпустить свои ARM-процессоры для персональных компьютеров в сентябре 2025 года, что станет ее конкурентом для линейки Snapdragon X от Qualcomm