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

    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
    clibs/freetype6.dll
    


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

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

    Итоги

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

    Скриншоты:





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

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

    Подробнее
    Реклама
    Комментарии 42
    • +1
      > lua plotter.exe
      Не совпадает с app.bat.

      А по теме — lua действительно удачный язык (сам на нём пробовал писать), но почему он чаще встраиваемый, чем Python скажем — потому что инструментов у самого языка меньше, да и насколько знаю, библиотек.
      • –10
        Такое ощущение, что автор столкнулся с lua в рамках какого-то учебного задания и решил поделится с нами этой радостью, а также собственно результатом выполнения этого задания. Но зачем?
        • +3
          Для себя я ставил 2 цели:
          1) Сделать так, чтобы программисты от игр, которые пишут аддоны для того же WoW знали, что их знаний хватит для создания полноценных программ.
          2) Просветить хоть некоторое количество людей, которые пишут софт для предприятий и госучреждений, чтобы они отказались от использования порочной практики создания нетребовательных к вычислительным ресурсам программ на компилируемых языках. Зачастую используется Delphi 7, Vba, VC 6. Только в последнее время происходит постепенная миграция прикладников на C#, я про госсектор и институты, если что. Я не раз и не два сталкивался с тем, что софт написан криво, что исходников нет, что авторов не найти, что dll, к которым привязан софт отказываются работать на соседней машине.

          Я выбрал Lua именно потому что он максимально прост в развертывании и переноске, а также потому, что этот язык является объективно проще, чем тот же питон.
        • +5
          Спасибо, мне интересно было почитать.
          • –8
            все-таки главное для языка — наличие качественной ide с дебаггером. как с этим у пациента?
            • +13
              Абсолютно не главное :)
              • +1
                Раньше можно было дебажить в intellij idea. Если плагин не усох, то всё нормально. Вполне вероятно, что под eclipse аналогично.
                • +1
                  поскольку не до всех доехал мой комментарий видимо — поясняю

                  считаю что для популяризации языка, среда — это главное.
                  людей, которые устраняют ошибки в сложных приложениях усилием взгляда на исходники в фаре или mc — не так много.
                  для слабаков, как я (которым скорость разработки важнее чсв) — хорошая ide это необходимость.

                  другое дело если её нет вообще, а язык использовать необходимо для однострочных программ. в которых все равно есть минимум одна ошибка, как писали классики.
                  • +1
                    Раньше вместе с SDK, из коробки, была SciTEлла c отладкой, подсветкой и тому подобным. Для слабаков вполне удобно.
                  • 0
                    Наличие ide для lua смысла особо не имеет — это же по сути клей для других библиотек, так что 90% времени придется проводить за чтением документации к библиотекам, автонавигация по коду и отладчик тут будут мало полезны.
                    • 0
                      как клей — наверное. но посмотреть что возвращает сторонняя библиотека — все равно удобно, тут документация мало поможет, если параметры только через логи можно посмотреть

                      я ж с точки зрения «В рамках идеи использования Lua как самостоятельного ЯП» на это все смотрю.
                      • +1
                        Если вам неудобно работать без отладчика, то используйте
                        www.unknownworlds.com/decoda/
                        или
                        luaedit.sourceforge.net/

                        Если вы вообще не собираетесь писать на Lua, то к чему затеяли эту дискуссию? :)
                        • 0
                          нет, не затевал :)

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

                          спрашивал в качестве приобретения знаний, а не флейма. просто чтобы быть в курсе
                    • 0
                      Забыл когда в последний раз пользовался дебагером. С навигацией по коду, автодополнением и стат.анализом посложнее, но отсутсвие этих фич не повод отказываться от языка. Скорее важно количество проверенных временем решений.
                      • 0
                        www.eclipse.org/koneki/ldt/
                        Koneki уже около года как в эклипс принят.
                        • 0
                          https://studio.zerobrane.com/
                        • +8
                          Я использую Lua уже 5 лет. И все же мне кажется lua больше подходит для встраивания нежели как самостоятельный язык. Прикручиваю Lua к C++ проектам для написания части логики (например формирование отчетов). Как самостоятельный язык, мне кажется, лучше подходят: python, ruby, php… Возможно я ошибаюсь, но это мое мнение.
                          • +2
                            Не придирки ради, а могли бы вы обосновать почему?
                            • 0
                              Как уже подчеркивал, это мое субъективное мнение. А если обосновать, то под lua меньше библиотек, lua очень просто встраивается в приложения на C/C++, буквально пару строк. И именно lua мне чаще всего встречался как встроенный язык в приложение. Помню как первый раз познакомился с lua, заглянув во внутренности игры Far Cry.
                              • 0
                                Как хорошая встраиваемость мешает самостоятельному использованию?
                          • 0
                            В lua практически ничего нету кроме базового функционала ЯП — в этом его главный плюс и минус. Развернуть приложение с кучей сторонних библиотек для lua не тривиальная задача, особенно для linux.
                            • 0
                              В плане интеграции с внешними C-библиотеками lua столь же удобен как python. За ruby не скажу, не пробовал его расширять на C.
                              • +1
                                Они все в этом смысле равны, тк есть www.swig.org
                                • 0
                                  Есть, то есть. Но и написание биндингов ручками для lua и python не составляет труда.
                                  Актуально, когда библиотека чужая и нет возможности править её хидеры.
                                • 0
                                  А в питоне есть аналог Lua Alien? Чтобы можно было сходу подгружать библиотеку без сторонних инструментов.
                                  • 0
                                    В python есть FFI-модуль, называется ctypes.
                                  • +1
                                    А в Руби есть ещё вот такая, почему-то малоизвестная утилитка: zenspider.com/projects/rubyinline.html
                                    Единственный минус — чтобы запустить руби-код на машине без gcc, придётся видимо постараться.
                                    Зато:
                                    require "inline"
                                    class MyTest
                                      inline do |builder|
                                        builder.c "
                                          long factorial(int max) {
                                            int i=max, result=1;
                                            while (i >= 2) { result *= i--; }
                                            return result;
                                          }"
                                      end
                                    end
                                    
                                    factorial_5 = MyTest.new.factorial 5
                                    
                                    • 0
                                      А смысл?
                                      • +1
                                        Локально-оптимизировать конкретное место в уже отлаженом коде.
                                      • 0
                                        А подгружать сишный код в рантайме можно?
                                        И я так понимаю, компилируется каждый раз при запуске?
                                        • 0
                                          Это же Руби. Кроме рантайма ничего и нету у него. Определения классов в нём просто прочитываются построчно интерпретатором и из них конструируются ин-мемори представления класов. Как правило, определения классов встречаются интерпретатору в самом начале работы, но можно и потом их в процессе работы доконструировать.

                                          RubyInline вкратце работает вот как.
                                          Когда интерпретатор доходит до директивы builder.c, он берёт переданную строку и вычисляет от неё MD5.
                                          Далее в папочке ~/.rubyinline (или как-то так, непринципиально) находит файл мд5хэш-от-кода.so
                                          Если файл нашёлся, подгружает из него скомпилированные бинарники функций и будет вызывать их при вызове соответствующих методов класса. Если файл не нашёлся — он скомпилируется (ну а дальше — будет подгружаться столько раз сколько нужно).
                                          • 0
                                            Не-не, я не это имел в виду под «подгружать в рантайме». Я имел в виду можно ли сишные вставки не «хардкодить» внутри lua-кода, а, скажем, читать из файла на этапе исполнения программы и потом скармливать этому билдеру? Т.е. этакое «скриптование наоборот» :)

                                            >Если файл не нашёлся — он скомпилируется

                                            Т.е. когда программа запускается в первый раз, она может «застрять» на этом месте на неопределённое время (в зависимости от размера сишного куска)?
                                            • 0
                                              Так, 20 секунд отступления — это Руби-код, а не Луа. Про подобные механихмы в Луа ничего не смогу сказать, увы.

                                              Так вот, в Руби аргумент для билдера — это просто строка. Которая может хоть по сети прийти с удалённой машины, хоть из файла прочитаться, хоть при помощи руби-кода предварительно сгенерироваться динамически.

                                              Да, программа в момент прогрева подобного кеша бинарей “залипает”. Для долгоживущего сервера это в общем-то не страшно, я думаю.
                                              Ну и понятно что не надо файрфокс там компилировать или какую-то большую вставку. Этот инструмент не для того. Он для того, чтобы по-быстрому запатчить медленное место локальное.
                                    • 0
                                      Могу ошибаться, но мне кажется также немалую популярность именно встраиваемости этого языка обеспечил World Of Warcraft — ведь там все аддоны именно на LUA и пишутся =)
                                      • 0
                                        Спасибо за статью.
                                        Луа отличный язык, писал на нем некоторые вещи, очень понравился минимализм языка.
                                        Может, оффтоп, но подскажите, как вы перегоняете строки из одной кодировки в другую, например, из cp1251 в cp866?
                                        Есть бинд для iconv, но его нет в сборке Lua for windows.
                                        • 0
                                          Посмотри в Scintilla, по крайней мере в русской сборке видел такой или подобный скрипт, для смены кодировки, просто подготовленная таблица использовалась.
                                        • +1
                                          Lua замечательный язык. С помощью LuaJIT скорость его исполнения становится очень высокой.
                                          Для ярых поклонников Python есть MoonScript — как Python, только транслируется в Lua. Причём весьма неплохо.
                                          Использую Lua для написания мобильных игр.
                                          Из IDE сейчас активно разрабатывается Lua Glider — мне нравится. Есть и другие, более заточенный под Corona SDK или другие платформы.
                                          В качестве бэкенда на сервере Lua тоже хорошо справляется, есть сервис Moai Cloud, где это хорошо реализовано.
                                          • 0
                                            Кстати ещё есть Luvit (Lua + libUV + jIT). По сути Node.js, но для lua. Раньше у проекта был сайт luvit.io, но сейчас он, вроде, недоступен. А жаль. Проект интересный.
                                            • 0
                                              Для Corona есть еще Cider, хорошая IDE, хотя я сам пишу в Sublime Text 2 с соответствующим плагином.

                                              А еще есть движок Löve2D, на котором написан небезизвестный MarioPortal. Тоже очень легко можно писать ккросплатформенные 2D игры на Win/Mac/Nix
                                              • 0
                                                Cider теперь называется Lua Glider.
                                            • 0
                                              По рабочим проектам не приходилось иметь дело с Lua, также в вакансиях практически не видел упоминания такового.

                                              Уважение к нему заслуживает тот факт что он на 40% используется в Adobe Lightroom (40% of Photoshop Lightroom is written using the Lua scripting language).

                                              На сколько я понимаю устройство Adobe Photoshop Lightroom, все фильтры и вся графическая обработка написана на C++, а вот GUI и вся обвязка на Lui. Я бы не сказал что Lightroom работает быстро, ему всегда не хватает CPU, оно и понятно, на лету надо обрабатывать сырые файлы, но ведь это как раз и есть мотивация к тому, чтобы писать всё на “быстром” ЯП.

                                              Вот собственно и вопрос к знатокам. Чем руководствовался Adobe при выборе Lua? Скорость разработки? Может быть то, что они делают продукт под Windows и Mac? Тогда выходит, что порт под Linux им не трудно сделать, но видимо маркетинг говорит что нет продаж под Linux.
                                              • 0
                                                По логике все ресурсоёмкие операции должны выполняться на более низкоуровнем языке. Lua выступает как координатор всех низкоуровних вызовов. В таком случае удобство разработки возрастает в разы с очень малым проигрышем по производительности.

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