как расшифровать lua скрипт
Поводом для написания статьи стала эта тема Чем открыть Lua-файл. |
Lua — интерпретируемый язык программирования, разработанный подразделением Tecgraf Католического университета Рио-де-Жанейро (Computer Graphics Technology Group of Pontifical Catholic University of Rio de Janeiro in Brazil). Разработанный интерпретатор является свободно распространяемым, с открытыми исходными текстами на языке Си.
По возможностям, идеологии и реализации язык ближе всего к JavaScript, однако Lua отличается более мощными и гораздо более гибкими конструкциями. Хотя Lua не содержит понятия класса и объекта в явном виде, механизмы объектно-ориентированного программирования, включая множественное наследование, легко реализуются с использованием метатаблиц, которые также отвечают за перегрузку операций и т. п. Реализуемая модель объектно-ориентированного программирования — прототипная (как и в JavaScript).
Язык широко используется для создания тиражируемого программного обеспечения — в частности, на нём написан графический интерфейс пакета Adobe Lightroom. Также получил известность как язык программирования уровней и расширений во многих играх (например, World of Warcraft) из-за удобства встраивания, скорости исполнения кода и лёгкости обучения.
Для написания Lua-скриптов можно воспользоваться любым текстовым редактором и интерпретатором (LuaBinaries), либо воспользоваться сервисами ideone, codepad и т.д.
Напишем тестовый скрипт (назовем файл 1.lua):
Также Lua имеет возможность компиляции скриптов.
Процесс дизассемблирования
Для процесса дизассемблирования воспользуемся дизассемблером luadec.exe.
Используем следующую команду для дизассемблирования:
Русские Блоги
О расшифровке скрипта LUA
Написал статью на форуме Кансюэ в прошлом году.«Анализ шифрования и расшифровки скрипта Lua для мобильной игры Android»Суть статьи: в этом году я написал дополнительную статью, интегрировав часть содержимого декомпиляции и конфронтации Lua, и используя 3 примера в качестве объяснения (включая соревнование Tencent 2018 года и дополнения, связанные с фэнтезийной мобильной игрой Westward Journey), в начале статьи также добавлено Связанная работа, удобная для всех при изучении Lua reverse. Эта статья состоит из 3 статей в 1, так что содержания слишком много.Друзья, которым интересно, должны набраться терпения, конечно, вы также можете прыгать и смотреть. Наконец, дайте мне ваш совет. Наконец, если у вас есть какие-либо вопросы, оставьте сообщение, обменяйтесь и узнайте вместе.
Связанных с работой
Чтобы некоторые студенты могли лучше изучить обратный инжиниринг Lua, я объединил некоторые из собранных материалов в часть работы по шифрованию и дешифрованию Lua для вашей справки. Прежде чем читать этот раздел, вам все еще необходимы базовые знания Lua. Вот «Благодарность за источник Lua» [19] Юньфэна Далу, и его рекомендуется изучать в сочетании с поисковыми системами.
Статья состоит из 2 частей: первая часть знакомит с соответствующими статьями о шифровании и дешифровании lua, а вторая часть знакомит с соответствующими инструментами lua.
Введение в статью
В этом разделе представлены различные статьи, связанные с Lua в Интернете, включая шифрование и дешифрование Lua, такие как анализ формата файлов, игры и соревнования на основе Lua, технология перехвата Lua и т. Д.
1. Начало работы с шифрованием и дешифрованием Lua:
Не-Bug 4 написал 4 статьи о формате и байт-коде файлов luac и luajit, а также о 010Editor с открытым исходным кодом, разбирающем код шаблона luac и luajit. Одноклассник Ганлв [7] написал 7 серий руководств по шифрованию и расшифровке Lua в My Love Cracking. Tencent gslab [9] написал вводное введение в реверс-инжиниринг игр на Lua, которое представляет собой более раннюю статью о расшифровке игр на Lua. Одноклассник INightElf [10] написал статью о введении декомпиляции lua-скриптов.
2. Мобильные игры на основе Lua:
Lua можно использовать не только для конечных игр, но и для мобильных игр, и из-за популярности мобильных игр он стал стимулом для обмена статьями по обратному анализу lua. Одноклассник wmsuper [11] расшифровал lua-скрипт игры Happy Xiaoxiaole от Tencent на платформе Android, а затем модифицировал lua-скрипт с целью обмана. Студенты Unity [8] расшифровали и изменили поток мобильной игры Lua «Place Jianghu» с помощью метода перехвата для достижения цели изменения игрового вознаграждения. Одноклассник LittleNA [12] расшифровал сценарии lua 3 мобильных игр 3 способами и исправил порядок кодов операций lua в мобильных играх в стиле фэнтези.
3. Соревнование на основе Lua:
Введение в инструмент
У игр lua и luajit с обратным дешифрованием есть связанные инструменты. В этом разделе представлены некоторые основные инструменты.
luadec [16]: Это декомпилятор Lua с открытым исходным кодом, написанный на языке C в сочетании с исходным кодом движка Lua. Он анализирует весь файл байт-кода Lua и восстанавливает его до исходного кода в максимально возможной степени. Конечно, поскольку восстановление является языком высокого уровня, совместимость является общей. Когда вы декомпилируете большое количество файлов, вы обязательно столкнетесь с ошибками. В это время вам нужно вручную исправить ошибки самостоятельно; и это легко сделать так, чтобы декомпиляция не удалась. В настоящее время поддерживаются версии lua5.1, 5.2 и 5.3.
chunkspy: очень полезный инструмент анализа lua, написанный на самом языке lua. Он анализирует весь байтовый файл lua, и, поскольку его вывод представляет собой сборочную форму lua, совместимость очень высока, а также вызывает определенные трудности с чтением. Chunkspy может не только анализировать файлы luac, но и включает интерактивную команду, которая может преобразовывать входной сценарий lua в форму сборки байт-кода lua, что очень полезно для изучения байт-кода lua. Этот скрипт интегрирован в инструмент luadec, и в настоящее время поддерживаются версии также lua5.1, 5.2 и 5.3.
unluac: это также декомпилятор lua с открытым исходным кодом, написанный на языке java и имеющий более низкую совместимость, чем инструменты luadec. Как правило, он используется редко и поддерживает только lua5.1. Вы можете попробовать его, когда вышеперечисленные инструменты не работают.
2. Связанные с Luajit:
luajit-decomp [17]: Инструмент декомпиляции luajit с открытым исходным кодом на github, написанный на языке au3. Сначала преобразуйте файл байт-кода luajit в сборку через собственный exe-файл luajit, а затем инструмент преобразует сборку luajit в язык lua. Поскольку в дизассемблированном байт-коде luajit отсутствует много информации, такой как имена переменных, имена функций и т. Д., Результат декомпиляции читается нечетко, подобно F5 в IDA. Но совместимость очень хороша, поскольку его можно разобрать, его можно декомпилировать, поэтому вам нужно заменить соответствующую версию движка luajit при его использовании (для удовлетворения потребностей разборки). В настоящее время поддерживаются все версии luajit.
ljd [18]: это также инструмент декомпиляции luajit с открытым исходным кодом на github. Он написан на python. Он отличается от того, как luajit-decomp декомпилирует сборку luajit. Он анализирует весь файл luajit с самого начала и может получить дополнительную информацию и степень восстановления. Выше, но из-за большей точности совместимость будет слабее. Проверьте вилку этого проекта, чтобы получить больше совместимых версий. В настоящее время поддерживаются версии luajit2.0, luajit2.1 и т. Д.
Конфронтация декомпиляции
Используйте отладчик для отладки, почему инструмент декомпиляции анализирует ошибку и устраняет причину.
Используйте отладчик для отладки того, как исходный механизм анализирует файл.
Используйте инструмент анализа формата файла, чтобы проанализировать файл, чтобы увидеть, в какой точке анализ не удался.
Следующие три примера будут использованы для борьбы и исправления декомпиляции Lua.
Пример 1: простой вопрос
Это пример проблемы, замеченной на форуме Xuexue. Проблема в том, что игра (возможно, мобильная игра в дороге) изменила длину строки lua int32 на int64, что вызвало пример сбоя декомпиляции. Содержимое относительно простое. См. Способ восстановления. Мой ответ в посте, адрес: https://bbs.pediy.com/thread-217033.htm
Пример 2: Конкурс Tencent Game Security 2018
Проблема с кодом операции и ее решение
Цель исправления кода операции состоит в том, что при вводе файла luac субъекта инструмент дизассемблирования Chunkspy и инструмент декомпиляции luadec могут выводить правильные результаты.
Сначала мы анализируем файл tmgs.dll движка lua в ida, а затем находим функцию luaV_execute (ищем строку «предел для ‘должен быть числом») и обнаруживаем, что параметр case под переключателем (код операции lua) не работает Да, здесь мы можем подтвердить, что код операции виртуальной машины lua для этого вопроса был изменен.
Функция luaV_execute обычного движка lua | Функция luaV_execute расширенного движка lua |
---|---|
Экспорт текста 2 версий luaV_execute через ida
Извлеките таблицу восстановления кода операции через скрипт Python
После того, как инструменты (Chunkspy и luadec) инициализируют файл lua, замените код операции таблицей восстановления
Тестовый запуск, исправление других ошибок
Наконец, замените полученную таблицу восстановления в инструмент. Точка восстановления Chunspy находится в функции DecodeInst. Измененный результат будет следующим:
Тест обнаружил ошибку, результат ошибки:
Из результата ошибки видно, что номер версии файла luac неверен. Версия lua 11 не может быть распознана здесь, на самом деле проблема, специально разработанная для того, чтобы инструмент распознал ошибку. Мы изменили 4-й байт (номер версии lua) 11 файла на 53 в порядке. Правильный результат:
Точка восстановления Luadec находится в функции f_parser файла ldo.c, и добавьте функцию RepairOpcode, восстановление будет следующим:
Запустите его, найдите ошибку и оставайтесь в функции StringBuffer_add, где str указывает на неправильное место, вызывая ошибки чтения строки:
Здесь мы исправили код операции, и Chunkspy успешно разобрался, но с декомпиляцией luadec все еще есть проблемы, которые мы проанализируем в следующем разделе.
Проблема декомпиляции и ее устранение
Я просмотрел записи нескольких крупных парней и обнаружил, что ни один из них не устранил эту проблему.В процессе решения проблемы непосредственно анализировался ассемблерный код Lua. Давайте посмотрим на причину ошибки и посмотрим на стек вызовов vs:
Наконец, luadec работает отлично, и декомпиляция прошла успешно.
Пример 3: Мобильная игра Fantasy Westward Journey
В этом разделе рассказывается о некоторых проблемах, обнаруженных при обучении взламывать код Lua мобильной игры Fantasy Westward Journey в прошлом году. Они организованы и распространены сегодня, поэтому могут не подходить для текущей версии мобильной игры в стиле фэнтези. Мы все еще используем его для справки.
На тот момент при декомпиляции мобильной игры Fantasy Westward Journey возникло около 12 проблем. После модификации исходный код Lua можно было полностью воспроизвести. Здесь использовалась версия luadec5.1.
Исправить один
Вопрос 1: Поскольку код операции Lua мобильной игры мечты был изменен, предыдущее решение заключалось в том, чтобы найти код операции мечты, заменить исходный код операции инструмента декомпиляции и изменить режим операции перед декомпиляцией. Проблема в том, что результаты некоторых тестов в порядке, но когда байт-код luac всей мобильной игры будет декомпилирован, появятся различные ошибки. Причина в том, что luadec5.1 по умолчанию использует порядок кодов операций во многих местах и делает специальные Обработка, поэтому вам нужно найти эти специальные места обработки и изменить их одно за другим. Но это очень проблематично, поэтому я думаю о другом способе, вместо изменения исходного кода операции и режима операции, но когда luadec анализирует байт-код, восстанавливает код операции до исходного кода операции.
Исправить два
Исправить три
Решение 3. Анализ показал, что параметр c OP_NEWTABLE здесь представляет размер ключа в хэш-таблице, а параметр c был неправильно преобразован в декомпилированном коде, что привело к ошибке синтаксического анализа. Измененный код выглядит следующим образом:
Исправить четыре
Вопрос 4: Ошибка инструмента декомпиляции и выход.
Решение 4. Отслеживание обнаружило, что в функции AddToTable PrintTable вызывается, когда ключ равен 0, а PrintTable освобождает таблицу, и при следующем вызове таблицы происходит сбой доступа к памяти. Измените код следующим образом:
Исправить пять
Вопрос 5: Когда функция возвращает результат с несколькими значениями и назначается нескольким переменным, ошибка декомпиляции выглядит следующим образом (дизассемблирование lua):
Когда приведенный выше код анализируется до строки 27, выдается сообщение об ошибке при извлечении R3 из регистра. Причина в том, что, когда предыдущий вызов возвращает несколько значений, он только помечается в F-> Rcall и не отмечается в регистре. Скомпилированный результат должен быть :
Решение 5. Если reg пуст, а Rcall не пуст, добавьте отметку return more и измените 2 функции:
Исправить шесть
Вопрос 6: Ошибка декомпиляции возникает, когда функция имеет только один оборот.
Исправить семь
Вопрос 7: Произойдут некоторые ошибки инициализации таблицы.
Исправить восемь
Вопрос 8: Произошла ошибка при разборе части переменных параметров, но инструмент не сообщает об ошибке при декомпиляции.
Решение 8. Когда is_vararg равно 7, F-> freeLocal добавляет еще раз:
Исправить девять
Вопрос 9: Инструмент декомпиляции выводит на китайском языке символы типа url (аналогично «\ 230 \ 176 \ 148 \ 231 \ 150 \ 151 \ 230 \ 156 \ 175»), а не китайские.
Решение 9. В функции DecompileString в файле proto.c закомментируйте строковую функцию преобразования по умолчанию:
Затем добавьте условия ограничения для оценки в следующих 3 местах, потому что байт char является отрицательным числом для китайских символов, поэтому функции isalpha и isalnum будут работать неправильно, поэтому добавьте условия ограничения, меньшие или равные 127:
Исправить десять
Вопрос 10: Не удалось разобрать. Поскольку некоторые файлы содержат очень длинные строки, вызов функции sprintf не выполняется.
Решение 10. Увеличьте размер кеша:
Исправить одиннадцать
Вопрос 11: Когда op_setlist opcode b == 0, декомпиляция не выполняется.
Решение 11. При обнаружении оператора lua, подобного следующему, инструмент декомпиляции завершится ошибкой. Ситуация находится в файле @ lib_ui.lua:
Во-вторых, существует проблема с сетлистом. Когда b == 0, это фактически означает, что значения от регистра a + 1 до вершины стека (вершины) все присвоены таблице, и декомпилятор не оценивает b == 0, плюс Вот и все. Так что измените его следующим образом:
Цикл for, добавленный StartTable, указывает, что если выполняется newtable (r 0 0), а последующая неинициализированная табличная операция перезаписывает регистр r (покрывает таблицу), это указывает, что новая таблица пуста, и в таблице нет последующей таблицы. Присвоение; если регистр r инициализируется позже, это доказывает, что новая таблица не пуста, а является таблицей переменных параметров.
Исправить двенадцать
Первая строка local a, b, c не будет декомпилирована, что приведет к различным ошибкам в следующем коде.
Когда startpc переменной равен текущему pc, количество переменных равно 0, а текущий pc равен 0, это означает, что переменная объявлена в первой строке, а добавленное else if должно разрешить эту ситуацию (это оказывается прямой ошибкой без разрешения).
подводить итоги
Вышеупомянутое сначала суммирует статьи, связанные с обратным проектированием Lua, и связанные инструменты, которые были опубликованы в последние годы, а затем объясняет противостояние между дизассемблированием и декомпиляцией Lua и использует 3 примера в качестве иллюстраций. Первый пример иллюстрирует восстановление мобильной игры Zhengtu. Второй пример восстанавливает код операции виртуальной машины lua и успешно декомпилирует lua-скрипт. Третий пример отлично исправляет большое количество ошибок при декомпиляции lua-скрипта мобильной игры мечты.
Технология шифрования и дешифрования Lua будет продолжать развиваться, но на этом статья заканчивается. Затем я могу написать подробный отчет об анализе конкурса Tencent Game Security Competition 2018 (с подробным описанием каждого байта), включая, помимо прочего, обратный инжиниринг STL, анализ алгоритма AES, анализ сценария Blueprint и т. Д. Следите за обновлениями.
Справочная статья
[1] Фей Чонг «Анализ формата файла Luac для обратного проектирования Lua» https://www.anquanke.com/post/id/87006
[2] Летающие насекомые «Байт-код Luac и дизассемблирование реверса программы Lua» https://www.anquanke.com/post/id/87262
[3] Летающие насекомые «Формат файла Luajit для обратного проектирования Lua» https://www.anquanke.com/post/id/87281
[5] Nick Cano 《Hooking LuaJIT》 https://nickcano.com/hooking-luajit
[6] Желудок вызван интересом «Посмотрите, как я атакую LuaJIT через хук» https://www.anquanke.com/post/id/86958
[7] Расшифровка lua-скрипта Ganlv 1: строка загрузки https://www.52pojie.cn/thread-694364-1-1.html
[8] unity «[Место Цзянху] Мобильная игра LUA на основе процесса модификации расшифровки HOOK» https://www.52pojie.cn/thread-682778-1-1.html
[9] Лаборатория безопасности игр «Введение в методы реверсирования и взлома игр на Lua» http://gslab.qq.com/portal.php?mod=view&aid=173
[10] INightElf «[Оригинал] Введение в первую декомпиляцию скрипта Lua» https://bbs.pediy.com/thread-186530.htm
[11] wmsuper «Счастливая расшифровка скрипта Xiaoxiaole Lua» https://www.52pojie.cn/thread-611248-1-1.html
[12] littleNA «Анализ шифрования и расшифровки Lua-скрипта мобильных игр Android» https://litna.top/2018/07/07/Analysis of the encryption and Decryption of Android Mobile Games Lua Script /
[13] «Kanxue 2016CrackMe, вопрос 2» https://ctf.pediy.com/game-fight-3.htm
[14] «Kanxue 2017CrackMe, вопрос 15» https://ctf.pediy.com/game-fight-45.htm
[15] «Конкурс технологий Tencent Game Security» https://www.52pojie.cn/forum-77-1.html
[16] luadec https://github.com/viruscamp/luadec
[17] ljd https://github.com/NightNord/ljd
[18] luajit-decomp https://github.com/bobsayshilol/luajit-decomp
(Полный текст окончен, спасибо, что прочитали)
Интеллектуальная рекомендация
Учебник по началу работы с ASP.NET Core 9, Промежуточное ПО ASP.NET Core (промежуточное ПО) Начало работы
Elasticsearch Psychology Query
vscode настройки китайского языка
1. После установки vscode перейдите вИнтерфейс запуска наCtrl + Shift + P Появится окно команд, показанное ниже, и введите «display», Введите; как показано ниже 2. Удалите содержимое перед д.
Идея установки Tomcat, сервер Jetty
Веб-сервер установки IDEA часто встречает установку и неудачи, запустить сбой. В сочетании с IDEA используется, расчесывание процесс установки веб-сервера, рекомендуется для загрузки серверов Tomcat и.
Отслеживание кода приложения камеры Android9.0
Доброе утро всем, на этот раз я предлагаю вам прочитать код приложения камеры Android9.0, которое не только для рабочих нужд, но и для личных заметок, чтобы помочь вам учиться. Местоположение исходног.
Основы декларативного программирования на Lua
Луа (Lua) — мощный, быстрый, лёгкий, расширяемый и встраиваемый скриптовый язык программирования. Луа удобно использовать для написания бизнес-логики приложений.
Отдельные части логики приложения часто бывает удобно описывать в декларативном стиле. Декларативный стиль программирования отличается от более привычного многим императивного тем, что описывается, в первую очередь, каково нечто а не как именно оно создаётся. Написание кода в декларативном стиле часто позволяет скрыть лишние детали реализации.
Луа — мультипарадигменный язык программирования. Одна из сильных сторон Луа — хорошая поддержка декларативного стиля. В этой статье я кратко опишу базовые декларативные средства, предоставлямые языком Луа.
Пример
В качестве наивного примера возьмём код создания диалогового окна с текстовым сообщением и кнопкой в императивном стиле:
function build_message_box ( gui_builder )
local my_dialog = gui_builder:dialog ( )
my_dialog:set_title ( «Message Box» )
local my_label = gui_builder:label ( )
my_label:set_text ( «Hello, world!» )
local my_button = gui_builder:button ( )
В декларативном стиле этот код мог бы выглядеть так:
build_message_box = gui:dialog «Message Box»
Гораздо нагляднее. Но как сделать, чтобы это работало?
Основы
Чтобы разобраться в чём дело, нужно знать о некоторых особенностях языка Луа. Я поверхностно расскажу о самых важных для понимания данной статьи. Более подробную информацию можно получить по ссылкам ниже.
Динамическая типизация
Важно помнить, что Луа — язык с динамической типизацией. Это значит, что тип в языке связан не с переменной, а с её значением. Одна и та же переменная может принимать значения разных типов:
Таблицы
Таблицы (table) — основное средство композиции данных в Луа. Таблица — это и record и array и dictionary и set и object.
Для программирования на Луа очень важно хорошо знать этот тип данных. Я кратко остановлюсь лишь на самых важных для понимания деталях.
Создаются таблицы при помощи «конструктора таблиц» (table constructor) — пары фигурных скобок.
Создадим пустую таблицу t:
Запишем в таблицу t строку «one» по ключу 1 и число 1 по ключу «one»:
Содержимое таблицы можно указать при её создании:
Таблица в Луа может содержать ключи и значения всех типов (кроме nil). Но чаще всего в качестве ключей используются целые положительные числа (array) или строки (record / dictionary). Для работы с этими типами ключей язык предоставляет особые средства. Я остановлюсь только на синтаксисе.
Во-первых: при создании таблицы можно опускать положительные целочисленные ключи для идущих подряд элементов. При этом элементы получают ключи в том же порядке, в каком они указаны в конструкторе таблицы. Первый неявный ключ — всегда единица. Явно указанные ключи при выдаче неявных игнорируются.
Следующие две формы записи эквивалентны:
Во-вторых: При использовании строковых литералов в качестве ключей можно опускать кавычки и квадратные скобки, если литерал удовлетворяет ограничениям, налагаемым на луашные идентификаторы.
При создании таблицы следующие две формы записи эквивалентны:
Аналогично для индексации при записи…
Функции
Функции в Луа — значения первого класса. Это значит, что функцию можно использовать во всех случаях, что и, например, строку: присваивать переменной, хранить в таблице в качестве ключа или значения, передавать в качестве аргумента или возвращаемого значения другой функции.
Функции в Луа можно создавать динамически в любом месте кода. При этом внутри функции доступны не только её аргументы и глобальные переменные, но и локальные переменные из внешних областей видимости. Функции в Луа, на самом деле, это замыкания (closures).
function make_multiplier ( coeff )
return function ( value )
return value * coeff
local x5 = make_multiplier ( 5 )
Важно помнить, что «объявление функции» в Луа — на самом деле синтаксический сахар, скрывающий создание значения типа «функция» и присвоение его переменной.
Следующие два способа создания функции эквивалентны. Создаётся новая функция и присваивается глобальной переменной mul.
function mul ( lhs, rhs ) return lhs * rhs end
mul = function ( lhs, rhs ) return lhs * rhs end
Вызов функции без круглых скобок
В Луа можно не ставить круглые скобки при вызове функции с единственным аргументом, если этот аргумент — строковый литерал или конструктор таблицы. Это очень удобно при написании кода в декларативном стиле.
print ( «Shopping list:» )
for name, qty in pairs ( items ) do
Цепочки вызовов
Как я уже упоминал, функция в Луа может вернуть другую функцию (или даже саму себя). Возвращённую функцию можно вызвать сразу же:
chain_print ( 1 ) ( «alpha» ) ( 2 ) ( «beta» ) ( 3 ) ( «gamma» )
В примере выше можно опустить скобки вокруг строковых литералов:
chain_print ( 1 ) «alpha» ( 2 ) «beta» ( 3 ) «gamma»
Для наглядности приведу эквивалентный код без «выкрутасов»:
local tmp1 = chain_print ( 1 )
local tmp2 = tmp1 ( «alpha» )
local tmp3 = tmp2 ( 2 )
local tmp4 = tmp3 ( «beta» )
local tmp5 = tmp4 ( 3 )
Методы
Объекты в Луа — чаще всего реализуются при помощи таблиц.
За методами, обычно, скрываются значения-функции, получаемые индексированием таблицы по строковому ключу-идентификатору.
Луа предоставляет специальный синтаксический сахар для объявления и вызова методов — двоеточие. Двоеточие скрывает первый аргумент метода — self, сам объект.
Следующие три формы записи эквивалентны. Создаётся глобальная переменная myobj, в которую записывается таблица-объект с единственным методом foo.
function myobj:foo ( b )
function myobj.foo ( self, b )
myobj [ «foo» ] = function ( self, b )
print ( self [ «a_» ] + b )
Примечание: Как можно заметить, при вызове метода без использования двоеточия, myobj упоминается два раза. Следующие два примера, очевидно, не эквивалентны в случае, когда get_myobj() выполняется с побочными эффектами.
Чтобы код был эквивалентен варианту с двоеточием, нужна временная переменная:
local tmp = get_myobj ( )
При вызове методов через двоеточие также можно опускать круглые скобки, если методу передаётся единственный явный аргумент — строковый литерал или конструктор таблицы:
Реализация
Теперь мы знаем почти всё, что нужно для того, чтобы наш декларативный код заработал. Напомню как он выглядит:
build_message_box = gui:dialog «Message Box»
Что же там написано?
Приведу эквивалентную реализацию без декларативных «выкрутасов»:
local tmp_1 = gui : label ( «Hello, world!» )
local tmp_2 = gui : button ( «OK» )
local button = tmp_2 ( < >)
local tmp_3 = gui : dialog ( «Message Box» )
Интерфейс объекта gui
Как мы видим, всю работу выполняет объект gui — «конструктор» нашей функции build_message_box(). Теперь уже видны очертания его интерфейса.
Опишем их в псевдокоде:
Декларативный метод
В интерфейсе объекта gui чётко виден шаблон — метод, принимающий часть аргументов и возвращающий функцию, принимающую остальные аргументы и возвращающую окончательный результат.
Для простоты, будем считать, что мы надстраиваем декларативную модель поверх существующего API gui_builder, упомянутого в императивном примере в начале статьи. Напомню код примера:
function build_message_box ( gui_builder )
local my_dialog = gui_builder:dialog ( )
my_dialog:set_title ( «Message Box» )
local my_label = gui_builder:label ( )
my_label:set_text ( «Hello, world!» )
local my_button = gui_builder:button ( )
Попробуем представить себе, как мог бы выглядеть метод gui:dialog():
return function ( element_list )
return function ( gui_builder )
local my_dialog = gui_builder:dialog ( )
element_list [ i ] ( gui_builder )
Ситуация с [gui_element] прояснилась. Это — функция-конструктор, создающая соответствующий элемент диалога.
Функция build_message_box() создаёт диалог, вызывает функции-конструкторы для дочерних элементов, после чего добавляет эти элементы к диалогу. Функции-конструкторы для элементов диалога явно очень похожи по устройству на build_message_box(). Генерирующие их методы объекта gui тоже будут похожи.
Напрашивается как минимум такое обобщение:
function declarative_method ( method )
return function ( self, name )
return function ( data )
return method ( self, name, data )
Теперь gui:dialog() можно записать нагляднее:
gui.dialog = declarative_method ( function ( self, title, element_list )
return function ( gui_builder )
local my_dialog = gui_builder:dialog ( )
element_list [ i ] ( gui_builder )
Реализация методов gui:label() и gui:button() стала очевидна:
gui.label = declarative_method ( function ( self, text, parameters )
return function ( gui_builder )
local my_label = gui_builder:label ( )
if parameters.font_size then
gui.button = declarative_method ( function ( self, title, parameters )
return function ( gui_builder )
local my_button = gui_builder:button ( )
— Так сложилось, что у нашей кнопки нет параметров.
Что же у нас получилось?
Проблема улучшения читаемости нашего наивного императивного примера успешно решена.
В результате нашей работы мы, фактически, реализовали с помощью Луа собственный предметно-ориентированный декларативный язык описания «игрушечного» пользовательского интерфейса (DSL).
Благодаря особенностям Луа реализация получилась дешёвой и достаточно гибкой и мощной.
В реальной жизни всё, конечно, несколько сложнее. В зависимости от решаемой задачи нашему механизму могут потребоваться достаточно серьёзные доработки.
Например, если на нашем микро-языке будут писать пользователи, нам понадобится поместить выполняемый код в песочницу. Также, нужно будет серьёзно поработать над понятностью сообщений об ошибках.
Описанный механизм — не панацея, и применять его нужно с умом как и любой другой. Но, тем не менее, даже в таком простейшем виде, декларативный код может сильно повысить читаемость программы и облегчить жизнь программистам.
Полностью работающий пример можно посмотреть здесь.