Журнал LinuxFormat - перейти на главную

LXF122:LUA

Материал из Linuxformat
Перейти к: навигация, поиск
Lua Язык программирования сценариев, встраиваемый в ваши приложения

Содержание

Lua: Что в нем такого?

LUA
Часть 1: Стоит ли изучать новый язык программирования сценариев, если уже есть Python? Да, считает Андрей Боровский, и он может обосновать свою точку зрения.

Знаете, что общего у игр SimCity, World of Warcraft, Far Cry, приложения Adobe Lightroom, web-сервера Apache и «железного» робота Crazy Ivan? Все эти программы и устройства, такие большие и такие разные, используют Lua в качестве встроенного языка сценариев.

Lua (что в переводе с португальского означает «Луна») разрабатывается на факультете компьютерных наук Епископального католического университета Рио-де-Жанейро (Pontifical Catholic University of Rio de Janeiro). Коварные программисты-католики, пытающиеся захватить мир с помощью языка программирования сценариев – чем не тема для очередной нетленки Дэна Брауна? Дарю ему эту идею, а мы с вами займемся вещами куда менее мистическими.

Познакомимся ближе

LXF122 76 1.jpg Логотип Lua. Нет ли здесь скрытого подтекста?

Тот факт, что Lua предназначен для встраивания в другие программы, серьезно повлиял на структуру самого языка. Здесь нет понятий главной функции вроде main() и основной программы, вместо этого говорят о «среде», где хранится то, что должно быть доступно глобально. Базовой структурной единицей Lua является набор операторов – chunk (для ясности, будем говорить «фрагмент»), который представляет собой... просто набор операторов. Фрагмент Lua не имеет специального оформления начала и конца. Он начинается с первым оператором и оканчивается с последним. При этом фрагмент может вводить локальные переменные и возвращать значения с помощью оператора return. Каким же образом один фрагмент отделяется от другого? Естественным разделителем служит сама программа-хозяин. Например, сценарии, загружаемые ею в ответ на действия пользователя, могут быть оформлены как фрагменты Lua.

Помимо фрагмента, в Lua есть понятие блока [block]. Блок – это фрагмент, границы которого выделены специальными операторами, например, do и end. Блоки используются там, где требуется вложить один фрагмент Lua в другой, и управляют видимостью локальных переменных, а также действием операторов вроде break. Операторы ветвления и цикла тоже используют блоки.

Почему игры?

Игровые проекты уже давно достигли той степени сложности, когда требуется разделение «базиса» и «надстройки» – движка, определяющего свойства игрового мира, и правил, которым подчиняются его элементы. Обычно движок является наиболее стабильной частью игры, а система, описывающая правила поведения персонажей и предметов, наоборот, подвержена частым изменениям. И если определять правила с помощью языка программирования сценариев, разработчикам не придется переделывать движок при каждом изменении правил поведения персонажа или свойств артефакта. Эти же аргументы применимы и к роботам, причем не обязательно к большим и человекоподобным.

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

Переменные объявляются без указания типа (он устанавливается динамически в момент присваивания значения). Всего в Lua насчитывается восемь типов: логический, строка, число, функция, поток, пользовательские данные, таблица и тип nil (указывает, что переменной не присвоено никакое значение; эквивалентен false в логических выражениях).

Численный тип в Lua амбивалентен. Число может быть целым, с плавающей точкой и шестнадцатеричным. По умолчанию для внутреннего представления чисел применяется double, но при желании нетрудно заменить его на любой другой тип (гибкость подстройки – одна из очень полезных отличительных характеристик Lua). Строки в Lua представляют собой массивы восьмибитовых символов произвольной длины; '\0' не имеет специального значения и может встречаться в любом месте строки.

Тип «функция» – это почти то же самое, что и указатель на функцию в C. Потоки используются для реализации так называемых со-процедур, которые могут выполнятся параллельно. Следует сразу отметить, что многозадачность со-процедур в Lua носит добровольный (корпоративный) характер, то есть переключение с одной процедуры на другую осуществляется явным образом, с помощью вызова специальной функции yield. Физически код Lua выполняется в одном потоке (так, по крайней мере, обстоит дело в реализации по умолчанию). Ничто, однако, не мешает запустить параллельно несколько интерпретаторов Lua в одной программе-хозяине.

Пользовательский тип данных предназначен для обмена информацией с «родителем». Обычно данные этого типа имеют смысл только для программы-хозяина и непрозрачны для Lua. Над ними можно выполнять только две операции: присваивание и проверку тождества.

На таблицы следует обратить особое внимание, поскольку это единственный доступный в Lua сложный тип данных. Таблицы Lua представляют собой ассоциативные массивы (то есть в качестве индекса в них можно использовать любые значения). Учитывая абсолютный полиморфизм переменных Lua, элементы ассоциативных массивов также могут содержать любые значения. Все это делает таблицы Lua чрезвычайно мощным средством для эмуляции таких метатипов, как структуры и объекты.

Правила выполнения операций с данными определяются в Lua с помощью метатаблиц. Вы можете создавать метатаблицы для определенных вами таблиц, но не можете менять метатаблицы, определенные для простых типов данных Lua (изменить правила обращения с последними можно через C API).

Все вышесказанное приводит нас к еще одной важной особенности Lua – гибкому, расширяемому синтаксису языка.

Единственными перечисляемыми типами в стандартном Lua являются численный и логический; char здесь отсутствует. Выражение «A» – это строка из одного символа, а не значение типа «символ». Эти ограничения не являются фатальными, но если вы привыкли писать программы на C/C++, вам придется несколько изменить стиль программирования.

Наша первая программа

Пришла пора взглянуть на Lua в действии. Любой дистрибутив Lua – это, прежде всего, набор библиотек, подключив которые к своему приложению, вы получаете возможность использовать в нем сценарии Lua. В дистрибутив также входит программа lua, которую можно рассматривать как интерпретатор Lua (хотя, строго говоря, она ничем не отличается от других программ, использующих Lua как встроенный язык), и luac – компилятор Lua, но его мы пока рассматривать не будем.

Почему Lua?

Хотя Lua может использоваться как обычный интерпретируемый язык программирования, он создавался именно для встраивания в приложения C/C++. Неудивительно, что многие разработчики выбрали его для реализации системы сценариев в своих проектах. От других аналогичных языков (например, Python) Lua отличается компактностью и высоким быстродействием интерпретатора и компилятора времени выполнения. Еще одно преимущество Lua – простота, с которой к программе можно «прицепить» сторонние библиотеки, написанные на C/C++.

Библиотек в дистрибутиве обычно великое множество. У Lua есть своя система для создания графического интерфейса IUP, использующая (в зависимости от ОС) Motif, GTK+ или GDI+. Библиотека Canvas Draw позволяет работать с двумерной графикой на любой из поддерживаемых платформ. Для обработки растровых изображений в популярных форматах служит библиотека IM. LuaCURL, как подсказывает название, является оберткой Lua для CURL (http://curl.haxx.se). С ее помощью программы, написанные на Lua, можно без труда наделить поддержкой клиентской части популярных интернет-протоколов, а с помощью Copas программу Lua можно превратить и в TCP/IP-сервер. Библиотека LuaSQL предназначена для взаимодействия с распространенными СУБД, а LPeg позволяет выполнять сравнение с образцом, используя специальный язык описания синтаксиса.

Богатство библиотек Lua не исчерпывается перечисленными. Особого внимания заслуживает также Alien, предназначенная для взаимодействия со сторонними разделяемыми библиотеками.

В интерпретируемых языках программа «Hello World» может состоять из одной-единственной строчки; Lua здесь не является исключением:

print (“Hello World!”)

Сохраните этот текст в файле helloworld.lua и скомандуйте

lua helloworld.lua

В результате на экране консоли вы увидите то, что и ожидали.

Между прочим, в Unix-системах интерпретатор lua можно активировать с помощью #!-строки. Если переписать программу «Hello World» в виде

#!/usr/bin/lua
print (“Hello World!”)

то файл helloworld.lua можно отметить как исполняемый и запускать самостоятельно.

Рассмотрим более сложный пример:

io.write (“Как вас зовут? “)
name = io.read ()
io.write ('Привет, ' .. name .. “!”)

Программа сначала просит вас ввести свое имя, а потом вежливо здоровается. Разберем ее построчно. Функция write(), объявленная в стандартной библиотеке io (имя библиотеки указывается как префикс, отделенный точкой), подобна print(). Одно из отличий заключается в том, что write() не выполняет автоматический перевод строки по окончании вывода. Функция read() считывает данные из стандартного потока ввода. Обратите внимание: мы ничего не говорим read() о типе данных, которые она должна считать. Ее задача – получить символы, а интерпретатор Lua разбирается с тем, что они означают. Введенная строка сохраняется в переменной name. Для объявления переменной достаточно просто ввести ее имя в соответствующем контексте. Как было отмечено выше, тип переменной не указывается. Подобно C, имена переменных в Lua регистро-зависимы ('name и Name – разные вещи); то же самое относится и к другим синтаксическим элементам языка. Оператор .. позволяет объединить две строки или строку и число в одну. Обратите внимание, что при задании строки можно использовать как двойные, так и одиночные кавычки. Строки Lua поддерживают тот же набор спецсимволов (\n и так далее), что и C. Как вы могли заметить, символы, разделяющие выражения Lua, необязательны, но можно использовать оператор ;, как это делается в C:

io.write (“Как вас зовут? “);
name = io.read ();
io.write ('Привет, ' .. name .. “!”);

Пустой оператор ;; здесь недопустим. Давайте сразу договоримся не использовать ; в статьях этой серии.

Понравилось? Вот вам немного синтаксического сахара на закуску:

a, b, c = 1, 2, 3
print(a)
print(b)
print(c)

В результате выполнения этой программы будут напечатаны числа

1
2
3

Множественное присваивание, когда слева от оператора = перечислено несколько имен переменных, а справа – несколько значений, одна из характерных «фишек» Lua. Одновременное присваивание сделано не ради пустого оригинальничания. Вот как в Lua можно выполнить обмен значениями между двумя переменными (другие языки в общем случае требуют для этого третью, временную):

a, b = b, a

Число переменных слева от оператора = и число значений справа от него могут различаться. Если переменных слева больше, «лишним» будет присвоено значение nil; если справа больше значений, «лишние» будут проигнорированы. В отличие от C, операция присваивания в Lua не возвращает значений, иначе говоря, нельзя использовать конструкцию

if (c = a + b) == x then...

Немного математики

Посмотрим теперь, как можно написать на Lua мою любимую программу для вычисления чисел Фибоначчи:

i = 0
a = 1
b = 0
print('fib('..i..')='..a)
while i < 10 do
  a, b = a+b, a
  i = i+1
  print('fib('..i..')='..a)
end

С пониманием цикла while не должно возникнуть проблем (если, конечно, Lua – не ваш первый язык программирования). Фрагмент программы, заключенный между операторами do и end (напомню, он называется блоком), будет повторяться до тех пор, пока истинно проверочное условие цикла. Способность Lua выполнять одновременно несколько присваиваний позволила нам записать рекуррентную формулу вычисления очередного числа Фибоначчи в одну строку. Можно сделать это еще лаконичнее:

a, b, i = a+b, a, i + 1

Любопытно, что строка

b, a, i = a, a+b, i + 1

даст тот же самый результат. То есть одновременное присваивание в Lua выполняется следующим образом: сначала вычисляются все выражения, стоящие справа от = (это происходит в порядке их перечисления), а затем производится присваивание значений. Из этого следует, что при обмене значений между переменными

a, b = b, a

на самом деле используется две скрытых переменных для хранения правых значений (а не одна явная, как в классическом алгоритме). Одновременное присваивание – не такое уж одновременное! На самом деле это просто синтаксическое удобство. Кстати, в Lua нет аналогов операторов C, ++ и --.

Иногда полиморфизм переменных Lua способен привести к неожиданным и неприятным результатам. Рассмотрим такой фрагмент программы:

x = “ab”
print(#x)

Оператор # позволяет узнать размер переменной, которой может быть присвоено значение произвольной длины (например, строка). В нашем случае фрагмент программы напечатает число 2 (длина строки, присвоенной переменной x). Если по аналогии мы напишем

x = “1”
print(#x)

интерпретатор выдаст сообщение об ошибке – попытке вычислить длину числовой переменной. Все дело в том, что строка, состоящая из одних цифр, автоматически преобразуется в процессе присваивания в число, а к переменным, содержащим числа, оператор # неприменим. С ним нужно обращаться осторожно и не применять его в том случае, если вы не уверены, какое значение содержит переменная. Если подобное поведение оператора # представляется вам нелогичным, наберитесь терпения. Далее мы покажем вам, как, погрузившись в дебри C, вы сможете изменить его (при этом, конечно, у вас появится своя собственная версия языка Lua, не совсем совместимая с другими). Вот еще пример:

x =”1”
y = “2”
print(x..y, x+y);

В результате выполнения этого фрагмента будут выданы два значения: 12 и 3. Первое представляет собой результат конкатенации двух переменных (напомню, что оператор .. одинаково работает со строками и числами), второе – их сумму. Если же вместо этого мы напишем

x =”a”
y = “2”
print(x..y, x+y);

то попытка вычислить выражение x+y приведет к ошибке «применение арифметического оператора к строковым значениям».

Все переменные, которые мы объявляли до сих пор, были глобальными, то есть видимыми во всех фрагментах программы, выполняемой данным экземпляром интерпретатора. С помощью ключевого слова local можно объявлять локальные переменные, доступные только внутри текущего фрагмента или блока.

Дела табличные

Теперь познакомимся с самым интересным типом данных – таблицами. Вообще-то мы займемся ими вплотную в следующей статье, а сегодня рассмотрим, как с их помощью объявлять простые массивы. Перепишем программу вычисления чисел Фибоначчи следующим образом:

fib = {[0] = 1; [1] = 1 }
for i = 2, 10, 1 do
  fib[i] = fib[i-1] + fib[i-2]
  print(fib[i])
end

Конструкция fib = {} сообщает, что переменная fib представляет собой таблицу, то есть ассоциативный массив. Объявляя ее, мы сразу же задаем две пары «ключ–значение» – fib[0] = 1, fib[1] = 1. В объявлении переменной fib ничто не указывает ни длину массива, ни тип хранимых ключей и значений (он может быть любым). Тот факт, что при объявлении мы присвоили переменной fib какие-то данные, никак не ограничивает нашей свободы в дальнейших манипуляциях с fib, что мы сейчас и покажем.

Но сначала несколько слов об операторе for. Как вы уже поняли, это еще одна разновидность операторов цикла, используемых в Lua. В нашем примере i – переменная-итератор, 2 – начальное значение переменной, 10 – конечное значение, 1 – инкремент. Таким образом, следующий за оператором for блок операторов будет повторен 9 раз. В представленной выше форме оператора for переменная-итератор может быть только числом. В Lua существует и другой вариант оператора for, который позволяет работать с произвольными итераторами с помощью специальных функций. Мы рассмотрим его позже.

Строка

fib[i] = fib[i-1] + fib[i-2]

делает две вещи: создает новый элемент ассоциативного массива с ключом i и присваивает ему значение (таким образом, до выполнения цикла for массив fib состоит из 2‑х элементов, а после выполнения цикла – из 11‑ти). Это очень важная особенность таблиц Lua. Если переменная var содержит значение типа «таблица», то любая конструкция вида var[exp] (где exp – выражение, результатом которого является значение одного из простых типов) является легальной, независимо от того, существует соответствующий элемент массива или нет. Выражение

fib[‘bignumber’]=1000000000000

создаст новую пару «ключ–значение», а выражение fib[3.14] вернет значение nil, если, конечно, с этим ключом не было уже связано какое-то значение. Если ключ данного элемента массива является строковым значением, то вместо

print(fib['bignumber'])

мы можем записать

print(fib.bignumber)

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

Выше я уже жаловался вам на поведение встроенного оператора #. Пожалуюсь еще раз: он применим к таблицам, но работает с ними по несколько странным правилам. Если t – переменная, содержащая таблицу, операция #t возвращает целочисленное значение i, такое, что t[i] не равно nil, а t[i+1] равно nil. Перебор подходящих значений начинается с единицы. Для таблицы

t = {[1] = x; [2] = y; [3] = z}

выражение #t вернет значение 3, что соответствует числу элементов массива. А вот для таблицы

t = {[0] = x; [1] = y; [300] = z}

это же выражение вернет значение 1, то есть посчитан будет только элемент t[1], хотя все три элемента таблицы существуют. Элементы с нецелочисленными ключами тоже, естественно, игнорируются. Если таблицы могут динамически увеличиваться, то, наверное, могут и уменьшаться? Разумеется. Чтобы удалить элемент таблицы, достаточно присвоить значение nil соответствующему ключу:

fib[bignumber] = nil -- удаляем элемент “bignumber”

В заключение знакомства с массивами рассмотрим один «каверзный» пример. Такие конструкции едва ли встречаются в реальном коде, по крайней мере, у вменяемых программистов, но их очень любят авторы всевозможных тестов на знание языка. Пусть u и v – две таблицы. Как будет выполняться присваивание в следующем примере?

i = 0
i, u[i+1] = i+1, v[i]

Каверза в том, что, как мы знаем, сначала вычисляются выражения, расположенные справа от оператора присваивания – из чего можно сделать вывод, что значение индекса при переменной u будет равно 2; но это не так. Присваивание переменным новых значений происходит после вычисления всех выражений, в том числе и выражений индексов (как слева, так и справа от оператора =), поэтому на момент вычисления индекса u[i+1] значение i равно 0. Таким образом, после выполнения приведенной выше операции элементу u[1] будет присвоено значение v[0], а переменная i получит значение 1.

На закуску

LXF122 79 1.jpg Калькулятор на Lua считает без ошибок.

Дабы у вас не складывалось впечатление, что Lua – скучный и своенравный язык программирования, рассмотрим некоторые его серьезные возможности. При такой легкости интеграции с C/C++ неудивительно, что для Lua сделано множество оберток и привязок. Программы, написанные на Lua, могут использовать для построения интерфейса wxWidgets, GTK+ (напрямую, минуя IUP), Qt (следует особо отметить библиотеку QtLua, которая может использоваться как альтернатива QtScript с его JavaScript-подобным языком ECMAScript), FLTK, FOX и даже ncurses. Этим набором интерфейсы Lua отнюдь не ограничиваются. Более подробную информацию о дополнениях и расширениях стандартной поставки Lua вы найдете на сайте http://lua-users.org. В качестве примера приведем расширение Lua для wxWidgets – wxLua (http://wxlua.sourceforge.net). В состав пакета wxLua входит интегрированная среда разработки для Lua и множество примеров программ с интерфейсом wxWidgets. Результат его работы можно видеть на рисунке.

Хотя в основе своей Lua не является объектно-ориентированным языком, взаимодействие с объектами wxWidgets получается у него отлично.

Персональные инструменты
купить
подписаться
Яндекс.Метрика
Интересное