Lua: как перестать встраивать и начать жить

в 23:57, , рубрики: freeware, iup, Lua, shareware, метки: , , ,

Lua: как перестать встраивать и начать жить

За Lua прочно закрепилась слава полуязыка — инструмента, который при случае можно встроить, чтобы заскриптовать приложение, написанное на компилируемом языке вроде С++. Тем не менее Lua является вполне самостоятельным языком, имеющим свой интерпретатор, возможность создания модулей, большое число библиотек, и при этом данный ЯП обладает минимальным размером среди аналогов. Проще говоря у нас есть все, чтобы создавать такие же приложения как на perl, python, и вообще любом другом распространенном языке программирования.

Я могу предложить вам следующие доводы в пользу Lua:

  • — приложения будут легко переносимы между Windows и Linux (не факт что код будет работать без изменений, но портирование правда пройдет безболезненно, если не были использованы платформоспецифичные библиотеки)
  • — малый оверхед создаваемых программ
  • — высокая скорость работы и загрузки приложений
  • — возможность оперативно «приклеить» к вашему приложению любую С-библиотеку — лучшего «клея» для библиотек вы не найдете
  • — приятный минималистичный синтаксис языка, с возможностью реализации на нем современных парадигм программирования
  • — программы на Lua очень легко развертывать
  • — малое потребление памяти

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

В качестве графического тулкита будем использовать iup — кроссплатформенную библиотеку, изначально созданную с расчетом использования из Lua.

Установка Lua SDK

В рамках идеи использования Lua как самостоятельного ЯП, была создана сборка Lua for Windows, которая содержит себе библиотеки, необходимые в повседневных задачах, возникающих при программировании под указанную ОС: работы с БД, GUI, парсинг XML и т.д. Пусть вас не смущает, что версия Lua в сборке 5.1, а не 5.2 — особой разницы в нашем случае между ними нет.

Скачайте и установите сборку.

Краткое описание концепции iup

Я долго думал, как же расписать процесс создания программы, не вдаваясь подробно в устройство iup. И решил коротко описать его основные принципы:

  • — iup.dialog является корневым элементом интерфейса программы — в этом контейнере размещаются все элементы
  • — позиционирование элементов в контейнере производится при помощи layout-ов: задания правил размещения элемента в контейнере. Iup сам расположит и отрисует элемент согласно правилам. Основные контейнеры — фрейм, вертикальный сайзер, горизонтальный сайзер.
  • — обработчики событий задаются в виде функций, прикрепленных к виджету
  • — после создания диалога запускается цикл обработки событий

Если вы ранее писали для GUI при помощи Tk, WxWidgets или WinAPI, то все это покажется знакомым. Если нет, то программа довольно подробно покрыта комментариями.

Код программы
-- подключение библиотек iup
require("iuplua" )
require("iupluacontrols")
require("iuplua_pplot")

-- библиотека для работы с Canvas, чтобы сохранять график в файл
require("cdlua")
require("iupluacd")


require("string")


-- глобальные переменные для виджетов и настроек программы

-- максимальное число графиков
plots_number = 5

-- виджеты вкладок, где будут размещаться виджеы ввода данных для каждого графика
tabs = {}

-- контейнеры для виджетов
vboxes = {}

-- чекбоксы для выбора того, какие графики строить
checkboxes = {}

-- здесь храним виджеты с текстом данных о точках
coords = {}

-- виджеты подписи для каждого графика
legends = {}

-- виджеты обозначения осей координат
global_legend = {}


-- к величайшему стыду, в Lua нет стандартной функции split
function string:split(sep)
        local sep, fields = sep or ":", {}
        local pattern = string.format("([^%s]+)", sep)
        self:gsub(pattern, function(c) fields[#fields+1] = c end)
        return fields
end


-- функция рисует на плоттере график по указаным точкам
function draw_plot(pwidget, pnum, data)
   x = data[1].value:split(",")
   y = data[2].value:split(",")

   if checkboxes[pnum].value == "OFF" then return end

   if not (#x == #y) or #x == 0 then
	  iup.Message("Ошибка", 
				  "Задано неверное число точек для графика " .. pnum)
      return
   end
   
   iup.PPlotBegin(pwidget, 0)
   iup.PPlotAdd(pwidget, 0, 0)
   for i = 1,#x do
      iup.PPlotAdd(pwidget, x[i], y[i])
   end
   iup.PPlotEnd(pwidget)
end


-- виджет отвечающий за кнопку построения графика
plot_btn = iup.button{ title = "Построить"}

-- колбэк для кнопки "построить график"
function plot_btn:action()

   -- создать виджет графопостроителя
   plot = iup.pplot
   {
      expand="YES",
      TITLE = "Simple Line",
      MARGINBOTTOM="65",
      MARGINLEFT="65",
      AXS_XLABEL = global_legend[1].value,
      AXS_YLABEL = global_legend[2].value,
      LEGENDSHOW="YES",
      LEGENDPOS="TOPLEFT",
      size = "400x300"
   }

   -- этот блок для обхода бага - без него подпись к первому графику отображаться не будет
   iup.PPlotBegin(plot, 0)
   iup.PPlotAdd(plot,0,0)
   plot.DS_LEGEND = ""
   iup.PPlotEnd(plot)

   -- обходим виджеты с данными
   for i = 1, plots_number do
      -- чтобы свеженарисованный графи отобразился с правильной подписью
	  print(legends[i].value)
      plot.DS_LEGEND = legends[i].value
      -- рисуем график
      draw_plot(plot, i, coords[i])
   end


   -- кнопка сохранения графика в картинку на диске
   save_btn = iup.button{ title = "Сохранить" }

   -- теперь создаем само окно, где будет отображаться график
   plot_dg = iup.dialog
   {
      iup.vbox -- это вертикальный сайзер, помести в него графопостроитель и кнопку
      {
	 plot,
	 save_btn
      },
   }

   -- обработчик кнопки сохранения графика
   function save_btn:action()

	  -- создаем диалог выбора имени файла ля сохранения
	  -- в связи с ограничениями библиотеки сохранять можно только в EMF
	  fs_dlg = iup.filedlg{DIALOGTYPE = "SAVE", FILTER = "*.emf" }
	  iup.Popup(fs_dlg)

	  -- если файл выбран
	  if tonumber(fs_dlg.STATUS) >= 0 then

		 -- дописать при необходимости нужное расширение
		 pic = fs_dlg.value
		 if not (string.sub(pic, string.len(pic)-3) == ".emf") then
			pic = pic .. ".emf"
		 end

		 -- создаем псевдо-холст, ассоциированный с файлом
		 tmp_cv = cd.CreateCanvas(cd.EMF, pic .. " 400x300")
		 -- выводим график на холст
		 iup.PPlotPaintTo(plot, tmp_cv)
		 -- сохраняем данные в файл
		 cd.KillCanvas(tmp_cv)
	  end
   end

   -- отображаем диалог с графиком
   plot_dg:showxy(iup.CENTER, iup.CENTER)

   -- запускаем петлю обработки событий для диалога
   if (iup.MainLoopLevel()==0) then
      iup.MainLoop()
   end

end


-- в цикле создаем вкладки, в которых мы будем размещать виджеты 
-- для сбора данных
for i=1,plots_number do

   -- создание текстовых виджетов, куда будут вводиться координаты точек
   coords[i] = {}
   for j = 1,2 do
      coords[i][j] = iup.text 
      {
		 expand="HORIZONTAL",
		 multiline = "YES",
		 VISIBLELINES = 5
      }
   end

   -- виджет для редактирования подписи к графику
   legends[i] = iup.text{ expand = "HORIZONTAL" }

   -- создаем контейнер вкладки и заполняем его элементами
   vboxes[i] = iup.vbox
   {
	  iup.hbox
	  {
		 iup.label { title = "Подпись графика:" },
		 legends[i]
	  },
	  iup.hbox 
	  {
		 iup.label
		 {
			title="X : ", 
		 },
		 coords[i][1]
	  },
	  iup.hbox 
	  {
		 iup.label
		 {
			title="Y : ", 
		 },
		 coords[i][2]
	  };
	  expand="YES",

   }

   -- меняем заголовк вкладки
   vboxes[i].tabtitle = "График " .. i

   -- создаем чекбокс, который будет указывать на то, нужно ли строить
   -- график по данным из указанной вкладки
   checkboxes[i] = iup.toggle{ title= "График" .. i, value = "ON" }
end

-- теперь из заполненных нами контейнеров создаем вкладки
tabs = iup.tabs{unpack(vboxes)}


-- создаем текстовые виджеты для редактирования подписей осей
global_legend[1] = iup.text{}
global_legend[2] = iup.text{}

-- создаем фрейм для общих настроек графика
frame = iup.frame
{
   iup.vbox
   {      
      iup.label{
		 title="Использовать данные:", 
		 expand="HORIZONTAL"
			   },
      iup.vbox
      {
		 unpack(checkboxes)
      },
      iup.label{}, -- пустую подпись можно использовать как распорку
      iup.label{title = "Подписи"},
      iup.hbox { iup.label{ title = "Ось X "}, global_legend[1] },
      iup.hbox { iup.label{ title = "Ось Y "}, global_legend[2] },
      iup.label{},
      plot_btn
   };
   expand = "VERTICAL",
}

-- создаем главное окно программы и наносим на него настройки и табы
dg = iup.dialog
{
   iup.hbox
   {
      frame, tabs
   },
   title="Строим график",
   size = "HALF"
}


-- показываем главное окно и запускаем обработку событий
dg:showxy(iup.CENTER, iup.CENTER)
if (iup.MainLoopLevel()==0) then
  iup.MainLoop()
end
Пара слов о развертывании

Скрипт можно запустить при помощи команды:

lua plotter.exe

В данном случае библиотеки будут подключаться из поддиректории clibs/, которая находится в директории, куда был установлен Lua for Windows. Чтобы максимально компактно упаковать скрипт и библиотеки для переноса на другую машину, достаточно скопировать в одну папку следущие файлы(указаны с относительными путями от директории установки Lua):

lua.exe
lib/lua5.1.dll
clibs/cd.dll
clibs/cdlua51.dll
clibs/iup.dll
clibs/iup_pplot.dll
clibs/iupcd.dll
clibs/iupcontrols.dll
clibs/iupgl.dll
clibs/iuplua51.dll
clibs/iuplua_pplot51.dll
clibs/iupluacd51.dll
clibs/iupluacontrols51.dll

Не забудьте поместить в эту папку и сам скрипт с программой. Теперь вы можете перенести эту папку на другую машину и запустить вашу программы командой, указанной выше. При этом никакие другие действия по установке библиотек и рантайма не нужны.

К сожалению файлы cd.dll, cdluad51.dll и iupcd.dll в данной версии Lua for Windows могут работать некорректно, поэтому рекомендую взять их из архива по ссылке ниже.

Итоги

Архив с рабочей версией тут, для удобства добавлена пускалка app.bat.

Скриншоты:

Lua: как перестать встраивать и начать жить

Lua: как перестать встраивать и начать жить

В результате получили, пусть и неказистую, утилиту, имеющую такой же функционал, как и если бы она была написана на «серьезном» языке программирования. При этом простую в развертывании и суммарным весом менее 2 мб. Потребление памяти — около 7 мб. Исходный код доступен для редактирования, сам Lua интерактивно понятен, что упрощает доработку подобного софта на местах.

На мой взгляд, это отличный выбор для написания учебного софта для школ и институтов, а также для внутреннего использования на предприятиях. Так как слабые машины до сих пор в изобилии присутствуют в подобных местах по всему СНГ, то использование Luа подобным образом целесообразно, особенно в свете постепнного прихода Linux на десктопы. К тому же тенденцию потери исходников самописного софта при жуткой его же забагованности можно приравнять к национальному бедствию.

Автор: PerlPower

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js