Основные свойства и возможности Lua — популярный мощный скриптовый язык программирования. Lua в переводе с португальского означает "луна", и это слово не является аббревиатурой или акронимом. Lua разработан подразделением Tecgraf Католического университета Рио-де-Жанейро в Бразилии (Computer Graphics Technology Group of Pontifical Catholic University of Rio de Janeiro), история языка ведёт отсчёт с 1993 года. Lua распространяется свободно, с открытыми исходными текстами на языке Си. Основные особенности языка:
это зрелый, стабильный и популярный язык; Lua используется в промышленных приложениях, и в настоящее время это ведущий язык сценариев в играх;
это один из самых производительных скриптовых языков в принципе;
Lua существует для Linux, Windows и MacOS, а также может быть запущен и на мобильных устройствах;
Lua — компактный и легко встраиваемый язык; имеется документированный API, который позволяет осуществить сильную интеграцию с кодом, написанным на других языках; Lua использовался, чтобы расширить программы, написанные на C, C++, Java, C#, Smalltalk, Фортране, Ада, и даже Perl и Ruby; точно так же можно расширить Lua библиотеками, написанными на других языках;
хотя Lua — не объектно-ориентированный язык в строгом понимании этого термина, он обеспечивает специальные мета-механизмы для того, чтобы реализовать классы и наследование; эти мета-механизмы экономят сущности и сохраняют язык маленьким, позволяя при этом расширить его семантику;
поставка языка компактна; например, поставка Windows x86 Executables имеет размер менее 900 Кб;
Изначально язык задумывался как описательный, для удобства формирования больших массивов данных при решении задач машинного моделирования. Сейчас язык часто позиционируется как лучший встраиваемый язык, что, впрочем, не мешает его использовать отдельно. Список программных продуктов, в которых Lua используется как встраиваемый язык, вы можете увидеть здесь или здесь.
Чтобы установить Lua на Windows, просто распакуйте бинарный дистрибутив. Можете ассоциировать расширение .lua с интерпретатором lua5.1.exe, а также прописать путь к интерпретатору в PATH.
Основные возможности языка:
есть интерактивный интерпретатор;
можно компилировать скрипты в байт-код;
существует LuaJIT — свободный Just-In-Time компилятор (компилятор "на лету") байт-кода в машинный код (это отдельный продукт).
модульность; код может быть вынесен в отдельные модули (файлы), которые можно подгружать с помощью оператора "require" и обращаться к содержащимся в них переменным и функциям; можно динамически перезагрузить модуль, в случае, если он был изменён с момента загрузки;
мощный механизм ассоциативных массивов (в терминах Lua — таблиц), встроенные функции и удобные синтаксические приёмы для работы с таблицами;
мощный механизм расширения языка с помощью так называемых метатаблиц, позволяющий фактически ввести полноценные объекты и изменить поведение некоторых важнейших стандартных типов данных (например, тех же таблиц);
базовые встроенные функции, позволяющие работать с отладочными сообщениями (assert), выполнить указанный файл скрипта, работать с метатаблицами и модулями, работать с таблицами напрямую (в обход их метатаблиц);
встроенные функции для работы со строками, позволяющие получить массив кодов байтов строки, получить строку из массива кодов байтов, работать с регулярными выражениями, искать вхождения подстрок и получать срезы строк, форматировать строки аналогично printf в Си, получить длину строки, преобразовать регистр символов строки, получить результат конкатенации указанного количества копий исходной строки, осуществить реверсинг строки;
встроенные математические функции: модуль, целая часть (ceil, floor), (гиперболические) (арк)синус, (арк)косинус и (арк)тангенс, остаток от деления, перевод угла из радиан в градусы и обратно, степень, экспонента, натуральный и десятичный логарифм, случайные числа, квадратный корень, константа pi;
встроенные функции для ввода-вывода на консоль и в файлы, работы с датой-временем, вызова команд системы, работы с файлами;
встроенные функции для интерактивной отладки.
Некоторые полезные ресурсы:
Русскоязычный сайт по Lua (документация, форум);
небольшой учебник по Lua (рус.);
уроки по использованию Lua совместно с С++ (рус.);
Lua-Alchemy — порт языка программирования Lua на платформу Adobe Flash при помощи Alchemy (англ.);
LuaForge — сайт сообщества Lua, каталог проектов (англ.);
небольшой FAQ по Lua (англ.).
Hello, World!
Создайте текстовый файл test.lua в кодировке cp866 следующего содержания:
print 'Привет, Lua!'
print("Привет, Lua!") -- комментарий
--[[ многострочный
комментарий]]
Запустите его на исполнение командой наподобие следующей:
lua5.1.exe test.lua
Вы можете скомпилировать скрипт в байт-код командой наподобие следующей:
luac5.1.exe test.lua
В результате вы получите файл luac.out (рядом с компилятором), который вы можете запустить на исполнение командой наподобие следующей, точно так же, как обычный скрипт:
lua5.1.exe luac.out
Примечательно, что расширения имён файлов скриптов (в т.ч. откомпилированных в байт-код) могут быть произвольными — интерпретатор автоматически анализирует, что ему передали.
Программа lua5.1.exe при запуске без параметров является интерактивным интерпретатором. Запустите её и введите код наподобие следующего:
dofile("C:\\Temp\\test.lua")
Приведённый код запустит на исполнение указанный файл скрипта. Для выхода наберите код: os.exit().
Пример вычисления факториала (рекурсия):
function f(n)
if n == 0 then
return 1
else
return n * f(n - 1)
end
end
print(f(170))
Консольная игра "угадай число":
function prompt_read(prompt)
print(prompt)
return tonumber(io.read())
end
math.randomseed(os.time())
math.random()
num = math.random(1, 100)
tries = 0
print('Угадайте число от 0 до 100.')
repeat
guess = prompt_read("Попытка: ")
if (guess > 100) or (guess < 0) then
print("Неверно!")
elseif guess > num then
print("Нет, нужно меньше!")
elseif guess < num then
print("Нет, нужно больше!")
else
print("Вы угадали!")
end
tries = tries + 1
until num == guess
print(string.format("Вам потребовалось %i попыток!", tries))
Особенности языка
Эта часть статьи рассчитана на тех, кто уже немного знаком с каким-либо языком программирования, и здесь мы приведём только некоторые особенности, которые в каком-то смысле определяют уникальное "лицо" языка. Полностью синтаксис Lua описан в документации к нему.
Lua — регистрочувствительный (регистрозависимый) язык.
Появление локальной переменной осуществляется с помощью ключевого слова local. Все остальные переменные — глобальные. Область видимости локальной переменной распространяется на всю порцию (chunk) кода, в котором эта переменная была объявлена. Такой порцией является, например, файл скрипта. Эквивалентом порции кода является блок. В общем случае блок специфицируется с помощью двух ключевых слов следующим образом:
do
-- тело блока
end
Арифметические операторы, операторы сравнения переменных и логические операторы (and, or, not) вполне интуитивны, за исключением нескольких моментов. Оператор сравнения "не равно" записывается так:
a = 1; b = 2
if a ~= b then
print "не равны"
end
Конкатенация строк записывается так:
print('один ' .. 'два')
Обмен содержимым двух переменных может быть записан так:
a, b = b, a
Если нужно присвоить значение переменной только в том случае, если этой переменной нет (или она равна nil, что одно и то же), можно поступить следующим образом:
var = var or 0
Управляющие структуры Lua достаточно интуитивны. Цикл с условием:
while выражение_условия do тело_блока end
Цикл с постусловием:
repeat тело_блока until выражение_условия
Цикл for:
for переменная=начальное_значение, конечное_значение, шаг do тело_блока end
Другой вариант цикла for, для обхода итерируемых объектов:
for переменная_цикла, доп_переменная in итеративное_выражение do тело_блока end
Lua поддерживает концепцию замыканий (особый вид функции, определённой в теле другой функции), например:
function makeaddfunc(x)
return function(y) -- анонимная функция
return x + y
end
end
plusThree = makeaddfunc(3)
print(plusThree(5)) -- выведет 8
Поскольку Lua — язык с динамической типизацией, тип данных связывается не с переменной, а с её значением в данный момент. Любой переменной может быть присвоено значение любого типа, вне зависимости от того значения, которое она содержала раньше. До первого присваивания переменная содержит значение nil (если к ней обратиться). В Lua восемь основных типов данных: nil (неопределенный), boolean (логический), number (числовой), string (строковый), function (функция), userdata (пользовательские данные), thread (поток) и table (таблица).
Таблицы, функции, потоки и userdata являются объектами, т.е. переменная содержит не непосредственное значение, а ссылку на объект, со всеми вытекающими последствиями. Присваивание, передача параметров и возврат результата из функции оперируют только ссылками на значения, и эти операции никогда не ведут к созданию копий.
Lua приводит типы автоматически, но не совсем так, как это делает большинство других языков:
print(type('1' + '2')) -- результат - число
print(type(1 .. 2)) -- результат - строка
При сравнении, в отличие от арифметических операций, не происходит явного преобразования, и, если что-то не так, интерпретатор выдаст ошибку.
Способы задания строк:
print(type("String double-quotes styled"))
print(type('String single-quotes styled'))
print(type([[ Универсальная строка.
Здесь возможны любые символы, в т.ч. непечатаемые. ]]))
Последовательности символов типа "[[" называются "длинными скобками" (long brackets). В "универсальных" (или "длинных") строках действительно возможны любые символы, вплоть до символа NULL (конец строки). Например, если вы вставите в строку символ BEL, то при печати в консоли вы услышите звук системного динамика. В принципе, ничто не помешает иметь в программе строку в несколько десятков мегабайт. Непосредственной поддержки Unicode-строк в Lua нет. Однако, ничто не помешает поместить в строку, например, последовательность символов в кодировке UTF-8.
Способы задания чисел:
print(type(12))
print(type(123.45))
print(type(1.23e-20))
Все числа в Lua являются числами с плавающей точкой двойной точности. Несмотря на это, Lua остаётся очень эффективным в плане производительности.
"Пустой" тип данных:
print(type(Var))
Var = 0
print(type(Var))
Var = nil
print(type(Var))
Булев тип данных:
a = 0; b = 1
print(type(a > b))
a = true
print(type(a))
При проверках только nil и false являются ложными значениями, всё остальное Lua считает истиной.
Так называемые таблицы (динамические ассоциативные массивы), на которых фактически основана вся синтаксическая мощь языка:
tbl = {}
print(type(tbl))
tbl[0] = {} -- вложенная таблица
tbl["str"] = 2
tbl[1.23] = nil -- это удаление элемента (если он есть), а не создание
for i,j in pairs(tbl) do -- обход таблицы
print(i,j)
end
print(tbl[2]) -- нет такого элемента, это nil
tbl2 = tbl -- копирования не происходит!
print(tbl2, tbl) -- выдаст один и тот же адрес, это один объект
Ещё один небольшой пример, дающий представление о синтаксисе работы с таблицами:
tbl = {"one", "two", [234]="yes", word="script"}
print(tbl[1]) -- "one"
print(tbl[234]) -- "yes"
print(tbl["word"]) -- "script"
print(tbl.word) -- "script"
tbl.newfield = 'ещё значение'
print(tbl.newfield) -- "ещё значение"
Несмотря на такие синтаксические "вольности", таблицы реализованы в плане производительности очень эффективно.
Оператор : (двоеточие) позволяет Lua автоматически передать "self" параметр в функцию, реализуя инкапсуляцию:
t = {}
function t:sayhello()
print ("Привет, " .. self.Name)
end
t.Name = "Василий"
t:sayhello() -- вызов "метода"
t.sayhello(t) -- равнозначно предыдущему
t['sayhello'](t) -- равнозначно предыдущему
Далее кратко упомянем об остальных типах данных, чтобы получить представление о них.
Тип userdata служит для хранения произвольных данных (структур Си) в переменных Lua. Фактически значения этого типа соответствуют просто блокам памяти и не имеют никаких предопределённых операций, кроме сравнения и присваивания. Значения этого типа не могут быть непосредственно созданы в программе на Lua, а должны создаваться только через Си API (т.е. в вызывающей скрипт программе). Тип thread соответствует потоку выполнения. Тип function соответствует функциям. Функции в Lua могут быть записаны в переменные, переданы как параметры в другие функции и возвращены как результат выполнения функций.
Lua позволяет перегружать операции для таблиц и userdata с помощью метатаблиц. Метатаблица представляет из себя обычную таблицу, в качестве ключей которой используются строки (события), а в качестве значений — методы (обработчики). C метатаблицей может быть связана еще одна метатаблица (ключ "__metatable"), и, если событие не определено в первой метатаблице, оно ищется во второй, и т.д. Все глобальные переменные являются полями в специальных таблицах Lua, называемых таблицами окружения (environment tables).
Lua как встроенный язык текстового редактора SciTE
Как пример рассмотрим использование Lua в текстовом редакторе SciTE.
SciTE — мощный кросс-платформeнный (Win32, Linux) популярный текстовый редактор с открытым исходным кодом. Русскую сборку SciTE со множеством полезных дополнений и подробной русской документацией вы можете взять здесь (редактор не требует инсталляции в обычном понимании этого слова; дистрибутив является просто самораспаковывающимся архивом). SciTE Lua Scripting Extension в качестве языка сценариев использует Lua 5.1.
Изложенное ниже ни в коем случае не является руководством по настройке SciTE с помощью Lua; это лишь маленький пример, показывающий, как может выглядеть использование Lua как встроенного языка. Для получения информации обращайтесь к документации в поставке SciTE, а также изучайте файлы конфигурации и уже существующие в поставке скрипты. Примеры скриптов Lua для SciTE и документацию по этому вопросу вы можете найти здесь и здесь, а также в вышеупомянутой русской сборке SciTE (там же есть и русская документация). С помощью написания сценариев на Lua вы можете настроить SciTE под ваши потребности, добавляя новое поведение и функциональность.
Вы можете объявить новые команды, которые доступны через меню Tools и клавиатуру. Например, в файл настроек SciTEGlobal.properties в каталоге установки SciTE можно поместить следующие команды:
# имя команды №200 в меню "Tools":
command.name.200.*=Запустить мой скрипт
# подсистема Lua, не запрашивать сохранение текущего файла перед запуском:
command.mode.200.*=subsystem:lua,savebefore:no
# выполнить скрипт Lua из файла:
command.200.*=dofile $(SciteDefaultHome)\tools\myScript.lua
В результате в меню "Tools" появится команда "Запустить мой скрипт", при выборе которой исполнится скрипт \tools\myScript.lua, если он существует по такому пути в папке установки SciTE. Чтобы проверить работу скрипта, сохраните в нём текст наподобие следующего:
print('запущен')
Теперь в любой момент при вызове меню "Запустить мой скрипт" вы будете получать сообщение в нижней панели редактора.
Вы можете обрабатывать события, объявленные в интерфейсе расширений SciTE (SciTE Extension Interface), просто объявляя функции с именами этих событий: OnOpen, OnClose, OnSwitchFile и т.д. Для некоторых событий SciTE передаёт один или несколько аргументов в функцию обработки события. Обработчики событий возвращают булево значение (true показывает, что событие полностью обработано и другие обработчики не будут вызываться; в большинстве случаев обработчик должен вернуть false).
В файле \tools\SciTEStartup.lua в каталоге установки SciTE с помощью функции dofile грузятся скрипты, обрабатывающие события редактора (этот файл стартует при загрузке SciTE). Вы можете загрузить и свой скрипт, добавив в этот файл команду наподобие следующей:
dofile (props["SciteDefaultHome"].."\\tools\\myScript.lua")
Чтобы проверить работу скрипта \tools\myScript.lua, сохраните в нём текст наподобие следующего:
function OnChar(code)
print('Вы ввели: ' .. code)
return false
end
Теперь, после перезапуска SciTE, будет обрабатываться событие OnChar, в результате чего вы будете получать сообщение в нижней панели редактора при вводе каждого символа.
SciTE добавляет в глобальный контекст Lua ряд переменных, функций и объектов (таблиц), которые позволяют управлять редактором и изменять его внешний вид.
Пакет Lua for Windows
Пакет Lua для Windows (LfW) содержит всё, что необходимо для написания, выполнения и отладки самостоятельных сценариев Lua на Windows. В поставку включены многочисленные библиотеки и примеры, готовые к использованию, это пакет с "батарейками в комплекте". LfW поддерживает Windows 2000 и более новые версии Windows. Инсталлятор пакета версии 5.1.4.23 от 06.02.2009 имеет размер порядка 16 Мб.
В пакет включены:
интерпретатор и компилятор Lua в байт-код со стандартными библиотеками; оригинальная документация по языку Lua;
скрипт "Quick Lua Tour", пошагово демонстрирующий синтаксис Lua в консоли;
порядка 250 (двухсот пятидесяти) примеров скриптов;
свыше 30 (тридцати) дополнительных библиотек, существенно расширяющих возможности языка;
подробная документация по дополнительным библиотекам;
настроенный для разработки на Lua мощный редактор SciTE.
Дополнительные библиотеки пакета предоставляют возможности:
вызов функций из динамических библиотек (.so, .dll, и т.п.), в т.ч. с организацией обратного вызова (callback);
организация GUI несколькими способами (включая wxWidgets);
несколько библиотек для работы с графикой, рисунками;
регулярные выражения;
клиент и сервер COM;
функционал веб-обозревателя на основе библиотеки cURL;
специализированные библиотеки для работы с датой-временем, файловой системой;
работа с XML;
библиотека журналирования на основе log4j;
профайлер для поиска "узких мест" в программах;
работа с TCP/IP, HTTP, FTP, SMTP, MIME;
работа с PostgreSQL, ODBC, MySQL, SQLite, Oracle, ADO;
юнит-тестинг;
работа с архивами zip;
криптография MD5;
интеграция с Microsoft .NET;
работа с бинарными данными;
некоторые другие возможности.
Скрипт "Hello, World!" будет выглядеть точно так же, как в одноимённом разделе выше, с той разницей, что для его запуска следует использовать команду наподобие такой:
lua.exe test.lua
Интерпретатор wlua.exe предназначен для запуска скриптов с графическим интерфейсом, без отображения окна командного интерпретатора. Такие скрипты могут иметь расширение .wlua, которое после установки пакета уже ассоциировано с wlua.exe.
Простейший пример работы с COM:
require "luacom"
sh = luacom.CreateObject("WScript.Shell")
res = sh:popup("Текст сообщения", 0, "Текст заголовка", 4+16)
print(res)
Пример работы с ADO, вывод всех констант (перечислений) ADO:
require "luacom"
conn = luacom.CreateObject("ADODB.Connection")
typeinfo = luacom.GetTypeInfo(conn)
typelib = typeinfo:GetTypeLib()
enums = typelib:ExportEnumerations()
for key, val in pairs(enums) do
print(key)
print("============================")
if(type(val)=="table") then
for key, val in pairs(val) do
print(tostring(key) .. " = " .. tostring(val))
end
end
print()
end
Простейшие примеры работы с ADSI и WMI:
require "luacom"
root = luacom.GetObject("ADs:")
print(root:Class())
for index, item in luacomE.pairs(root) do
print(index .. ") " .. item:Name())
end
oWMIService = luacom.GetObject("winmgmts:{impersonationLevel=Impersonate}!\\\\.\\root\\cimv2")
oDrives = oWMIService:ExecQuery("select Name,DriveType from Win32_LogicalDisk")
for index, item in luacomE.pairs(oDrives) do
print(item:Name())
end
Простейшие примеры работы с Win32 API:
require "alien"
f = alien.Kernel32.Beep
f:types{ret = 'long', abi = 'stdcall', 'long', 'long' }
print(f(500, 100))
print(f(550, 100))
print(f(600, 300))
f = alien.Kernel32.ExpandEnvironmentStringsA
f:types{ret = "long", abi = 'stdcall', "string", "pointer", "long" }
local buffer = alien.buffer(512)
f("%USERPROFILE%", buffer, 512)
print("%USERPROFILE% - " .. tostring(buffer))
f = alien.User32.MessageBoxA
f:types{ret = 'long', abi = 'stdcall', 'long', 'string', 'string', 'long' }
print(f(0, "Привет, MessageBoxA (stdcall)!", "Заголовок", 64))
Скрипт выше подаст несколько звуковых сигналов системным динамиком, выведет на консоль значение переменной среды %USERPROFILE%, а затем отобразит окно с приветствием.
Простейшие способы использования классов .NET:
require 'luanet'
luanet.load_assembly "System"
Console = luanet.import_type "System.Console"
Math = luanet.import_type "System.Math"
Console.WriteLine("sqrt(2) равен {0}", Math.Sqrt(2))
require 'CLRPackage'
import "System"
import "System.IO"
Console.WriteLine("Текущий каталог: {0}", Directory.GetCurrentDirectory())
Скрипт выше выведет на консоль значение квадратного корня из двух, а затем текущий каталог.
Ещё один пример с использованием классов .NET:
require 'CLRPackage'
import "System"
import "System.Windows.Forms"
f = Form()
f.Text = 'Привет, Lua!'
b = Button()
b.Dock = DockStyle.Fill
b.Text = DateTime.Now:ToString()
b.Click:Add(function()
b.Text = DateTime.Now:ToString()
end)
f.Controls:Add(b)
f:ShowDialog()