Этот учебник научит вас, как использовать Lua в Cortex Command. Он написан для людей, которые не встречилались раньше с программированием совсем, и поэтому в нем не будут использованы сложные слова, которые есть в других учебниках. Лучше будет, если вы уже будете знать как работать с ini-кодом, прежде чем приступите к изучению программирования.
Начало
В Cortex Command, Lua позволяет объектам делать то, что они обычно не были бы в состоянии сделать или создать особенные эффекты.
Создание Lua файла
Принцип работы скриптов Lua в Cortex Command состоит в том, что скрипты присоединены к объектам, созданным с помощью ini. Чтобы сделать файл Lua в простом блокноте в Windows, просто создайте новый текстовый файл, перейдити в меню «Файл», «Сохранить Как. «, измените «Тип файла:» на «Все файлы» , и измените «.txt» в имени файла на «.lua». Если нет никакого расширения, просто добавьте «.lua» в конце имени файла. Потом нажмите «Сохранить». Вы создали файл Lua.
Как писать JS/LUA-скрипты #1
Совет от переводчика: Используйте Notepad++ вместо стандартного блокнота, так как в нем показывается весь синтаксис Lua, и вообще в нем легче работать.
Прикрепление скрипта к объекту
Чтобы прикрепить Lua скрипт к объекту, созданному в ini файле, просто поместите «ScriptPath = » в блоке кода объекта с соответствующим табулированием.
Основы программирования Lua
Основная статья: Типы Данных
Типы данных — типы данных, которые используются для переменных и свойств. В CC есть 5 основных типов данных: числа, текст, boolean (логическая переменная), вектор, и ноль (nil). Первые 2 — понятно из названия то это. boolean — логическая переменная, т.е. её значения это истина (true) или ложь (false). Вектор в CC — координаты (X, Y). Числовые данные используются, печатая только числа.
Текст используется при наличии пары кавычек («») с текстом между ними. Вектор используется, следуя за словом «Vector » 2 числами разделенными запятой в двух круглых скобках. Ноль — обозначается как «nil» (чтобы не быть перепутанным с числом ноль).
—Числа 1234 —Текст «text» —Логическая переменная true false —Векторы Vector(1,1) Vector(5,2) Vector(-7,6) —Ноль (ничто) nil
Переменные
Переменные — это то в Lua, чему Вы можете дать значение. Они используются для того, чтобы хранить информацию, чтобы Вы могли снова вызвать это позже. Переменная состоит из своего имени, одного знака » local» перед именем переменной, разделив их пробелом.
Установка значения локальной переменной равного значению другой локальной переменной работает подобным способом как и у глобальных переменных.
local varA = 5 local varB = varA
Это действие сделало переменную «varB» равной 5. Заметьте, что слово «local» не использовалось, когда мы выбирали переменную «varA».
Другое полезное свойство переменных — то, что Вы можете использовать переменные, чтобы установить определенное свойство в объект, и хранить объект в переменной. Храня объект в переменной, с ним легче взаимодействовать и легче вызывать указателем. Чтобы использовать переменные, для изменения свойства объекта, Вы нуждаетесь в переменной, которая является определенным указателем на объект (мы разберем это позже), сопровождающаяся точкой, названием свойства, знаком » thing» это указатель на объект, «GetsHitByMOs» это свойство объекта в ini, и «false» это значение.
ЧЕМ ОПАСНЫ LUA СКРИПТЫ? ПОЧЕМУ Я ИХ НЕ ДЕЛАЮ?
Обычно используют указатель, который уже определен как «self». Указатель всегда обращается к объекту, к которому присоединен скрипт.
self.GetsHitByMOs = false
Операторы
Принцип работы Lua (и программирования вообще), это проверка операторов, чтобы увидеть, являются ли их значение истинным (true) и делающим что-либо если это так (или если не так). Давайте посмотрим на код ниже.
if X == 1 then end
Этот код проверяет, если переменная «X» равна «1», то совершает «содержимое» («» не фактическое действие, только указатель места заполнения для других команд, до которых мы доберемся позже), если оператор будет истиной. Обратите внимание на «end» внизу блока кода, который говорит, что «», часть этого оператора, закончено.
Если Вы не напечатаете «end» после оператора (конец оператора после тоже), то Вы получите ошибку. Также заметьте, что есть 2 знака «=» в «строке «if». «==» нужно для того, чтобы проверить равенство, и » if», могут находится операторы «else». Эти операторы проверяют, выполняется ли условие оператора «if», и если оно не истинно (не выполняется), то тогда совершаются действия в их содержимом.
if X == 1 then else end
Условие «Elseif» похоже на второй «if» оператор и работает также:
if X == 1 then elseif X == 2 then end
В операторе могут быть много условий «elseif», но только одно «else».
Таблицы
Списки (также называемые как таблицы и массивы) похожи на переменные, у которых могут быть множество значений. Чтобы создать список, напечатайте название списка сопровождаемое знаком » numbercheck» равное 2.
Чтобы очистить все значения списка, напечатайте имя списка, знак » mw-headline» > Оператор For
Поскольку операторы используются, чтобы сделать множество повторяющихся действий в одном коротком участке памяти скрипта. Формат написания оператора «For» ниже.
Пример использования оператора «for».
for i = 1, 5 do end
Давайте рассмотрим каждую часть подробно:
for — Стартовое ключевое слово
i = 1 — «i» — временная переменная, которая используется оператором «for», для сохранения, сколько раз он совершил действия (включая 1). «i» может быть изменен на любое имя как переменная. «1» это начальное значение для отсчета.
Чтобы прервать оператор «for», включите «break» в оператор «if», который проверяет значение оператора «for» для соблюдения условия.
for i = 1, 5 do if i == 3 then break end end
Cortex Command Lua stuff
Функции скрипта
Функции скрипта — это части скрипта, которые выполняются только, когда объект был создан, когда он существует, или когда уничтожен. Функции тоже требуют «end» как и операторы условий.
— в промежутке между «function Create(self)» и «end» будет работать, только когда объект будет создан. (происходит только один раз) function Create(self) end — в промежутке между «function Update(self) и «end» будет работать, только когда этот объект существует. function Update(self) end — в промежутке между «function Destroy(self) и «end» будет работать, только когда этот объект уничтожается. (происходит только один раз) function Destroy(self) end
Команды
Команды — часть Lua, которые уже встроены в игру и взаимодействуют с содержимым в Cortex Command. Формат команды — название группы команды, двоеточие, имя команды, и её параметры (нужные данные для этого, чтобы это работало) разделяются запятыми в круглой скобке. Большинство названий групп команд Cortex Command могут быть заменены указателем чем-нибудь на то, с чем они смогут взаимодействовать. Пример команды ниже:
anactor:AddImpulseForce(Vector(0,-10),Vector(0,0))
Информация о команде из Википедии Cortex Command Lua:
AddImpulseForce Adds impulse force (or instant momentum) to this MovableObject for the next time Update() is called. Arguments: — An Vector with the impulse force vector that will directly be added to this MovableObject’s momentum next Update(). In kg * m/s. — A Vector with the offset, in METERS, of where the impulse is being applied relative to the center of this MovableObject. Return value: None.
«anactor» — указатель на актора, «AddImpulseForce» — название команды, и содержимое круглой скобки — данные, необходимые для работы команды.
В блоке информации о команде, «Return value», — это то, что выводит команда, когда она выполняется. Большинство команд в Cortex Command может быть найдено в wiki, так что убедитесь проверив это.
Указатели
Хорошо, мы наконец добрались до указателей! В Cortex Command указатели — переменные, которые «хранят» объект в себе. Или Вы можете представить это, как гигантскую стрелку, указывающую на объект. независимо от того, что свойства Вы устанавливаете на указатель или используете функцию на нем, это произведет эффект на тот объект, на который он «казывает.
Есть несколько способов создать указатели. Обычно используют специальный оператор «for», который проверяет все объекты определенного типа и является оператором, который проверяет, является ли объект тем, который вам нужен. Кусок кода скрипта ниже присоединен к объекту, который ищет актора, с именем «Dummy» и затем устанавливает на него указатель, названный «local pointer».
function Update(self) for actor in MovableMan.Actors do if actor.PresetName == «Dummy» then local pointer = actor break end end end
Что делает вышеупомянутый кусок — проверяет всех акторов в игре, пока он не находит агента с «PresetName» как «Dummy». Тогда это устанавливает указатель на этого актора как «local pointer» и останавливает оператор «for» с «break». Термин «актор» является указателем на актора, и передан «local pointer» для более простой справочной информации.
Здесь 2 других строки «for», которые получают указатели на устройствах и частицах.
— Указатель устройств for item in MovableMan.Items do end
— Указатель частиц for particle in MovableMan.Particles do end
Функции и свойства в Cortex Command Wiki
Это — ссылка на раздел Cortex Command wiki, который содержит документацию для многих функций и свойств. Список всех Lua функций
Пример скрипта
Мина
Этот скрипт для простой мины, которая взрывается, когда акторы находятся в пределах определенного расстояния от объекта со скриптом.
— Пока объект существует function Update(self) — Проверка всех акторов for actor in MovableMan.Actors do — Использует функцию и свойство вектора, чтобы сохранить расстояние между актором и объект со скриптом в переменной. local distcheck = SceneMan:shortestDistance(self.Pos,actor.Pos,true).Magnitude — Оператор, проверяющий, если проверяемое расстояние (переменная «local distcheck») меньше чем 10. Если так, совершите действие. if distcheck < 30 then — GibThis() — это функция, которая делает целенаправленную разрушение объекта. self:GibThis() — Остановка оператора break — Конец оператора «if». end — Конец оператора «for». end — Конец «function Update(self)». end
Источник: wiki.datarealms.com
Учебник по Lua для программистов
Lua — простой встраиваемый язык (его можно интегрировать с вашими программами, написанными на других языках), легкий и понятный, с одним типом данных, с однообразным синтаксисом. Идеальный язык для изучения.
Зачем?
Lua может вам пригодится:
- если вы геймер (плагины для World of Warcraft и множества других игр)
- если вы пишете игры (очень часто в играх движок пишут на C/C++, а AI — на Lua)
- если вы системный программист (на Lua можно писать плагины для nmap, wireshark, nginx и других утилит)
- если вы embedded-разработчик (Lua очень быстрый, компактный и требует очень мало ресурсов)
Что надо для того, чтобы читать дальше?
1. Научитесь программировать. Хотя бы немного. Не важно на каком языке. 2. Установите Lua. Для этого либо скачайте здесь версию 5.3 (http://www.lua.org/download.html), либо ищите ее в репозиториях для linux.
Все примеры из статьи запускайте в терминале командой наподобие «lua file.lua».
Первые впечатления
Lua — язык с динамической типизацией (переменные получают типы «на лету» в зависимости от присвоенных значений). Писать на нем можно как в императивном, так и в объектно-ориентированном или функциональном стиле (даже если вы не знаете как это — ничего страшного, продолжайте читать). Вот программа «Привет мир» на Lua:
— my first lua app: hello.lua print «Привет мир!»; print(«Пока мир.»)
Что уже можно сказать о языке:
- однострочные комментарии начинаются с двух дефисов «—«
- скобки и точки-с-запятыми можно не писать
Операторы языка
Набор условных операторов и циклов довольно типичен:
— условные операторы (ветки else может не быть) if a == 0 then print(«a is zero») else print(«a is not zero») end — сокращенная форма if/elseif/end (вместо switch/case) if a == 0 then print(«zero») elseif a == 1 then print(«one») elseif a == 2 then print(«two») else print(«other») end — цикл со счетчиком for i = 1, 10 do print(i) end — цикл с предусловием b = 5 while b > 0 do b = b — 1 end — цикл с постусловием repeat b = b + 1 until b >= 5
ПОДУМАЙТЕ: что может означать цикл «for i = 1, 10, 2 do . end»?
В выражениях можно использовать такие вот операторы над переменными:
- присваивание: x = 0
- арифметические: +, -, *, /, % (остаток от деления), ^ (возведение в степень)
- логические: and, or, not
- сравнение: >, =, = (не-равно, да-да, вместо привычного «!=»)
- склеивание строк (оператор «..»), напр.: s1=»hello»; s2=»world»; s3=s1..s2
- длина/размер (оператор #): s=»hello»; a = #s (‘a’ будет равно 5).
- получение элемента по индексу, напр.: s[2]
Битовых операций в языке долгое время не было, но в версии 5.2 появилась библиотека bit32, которая их реализует (как функции, не как операторы).
Типы данных
Я вам соврал, когда сказал что у языка один тип данных. Их у него много (как и у каждого серьезного языка):
- nil (ровным счетом ничего)
- логические переменные (true/false)
- числа (numbers) — без деления на целые/вещественные. Просто числа.
- строки — кстати, они очень похожи на строки в паскале
- функции — да, переменная может быть типа «функция»
- поток (thread)
- произвольные данные (userdata)
- таблица (table)
Если с первыми типами все понятно, то что же такое userdata? Вспомним о том, что Lua — язык встраиваемый, и обычно тесно работает с компонентами программ, написанными на других языках. Так вот, эти «чужие» компоненты могут создавать данные под свои нужды и хранить эти данные вместе с lua-объектами. Так вот, userdata — и есть подводная часть айсберга, которая с точки зрения языка lua не нужна, но и просто не обращать внимания на нее мы не можем.
А теперь самое важное в языке — таблицы.
Таблицы
Я вам снова соврал, когда сказал, что у языка 8 типов данных. Можете считать что он один: всё — это таблицы (это, кстати, тоже неправда). Таблица — это очень изящная структура данных, она сочетает в себе свойства массива, таблицы «ключ»-«значение» (хэш-таблицы), структуры, объекта.
— Итак, вот пример таблицы как массива: a = — массив из 3-х элементов print(a[2]) — выведет «2», потому что индексы считаются с единицы — А таблица в виде разреженного массива (у которого есть не все элементы) a = <> — пустая таблица a[1] = 1 a[3] = 5
ПОДУМАЙТЕ: чему равно a[2] в случае разреженного массива?
В примере выше таблица ведет себя как массив, но на самом деле — у нас ведь есть ключи (индексы) и значения (элементы массива). И при этом ключами могут быть какие угодно типы, не только числа:
a = <> a[«hello»] = true a[«world»] = false a[true] = 1 — или так: a = < hello = 123, world = 456 >print(a[«hello»]) print(a.hello) — то же самое, что и a[«hello»], хотя выглядит как структура с полями
Кстати, раз уж у таблицы есть ключи и значения, то можно в цикле перебрать все ключи и соответствующие им значения:
t = < a = 3, b = 4 >for key, value in pairs(t) do print(key, value) — выведет «a 3», потом «b 4» end
А как же объекты? О них мы узнаем чуть позже, вначале — о функциях.
Функции
Вот пример обычной функции.
function add(a, b) return a + b end print(add(5, 3)) — напечатает «8»
Функции языка позволяют принимать несколько аргументов, и возвращать несколько аргументов. Так аргументы, значения которых не указаны явно, считаются равными nil.
ПОДУМАЙТЕ: зачем может понадобиться возвращать несколько аргументов?
function swap(a, b) return b, a end x, y = swap(x, y) — кстати, это можно сделать и без функции: x, y = y, x — и если уж функция возвращает несколько аргументов, — а они вам не нужны — игнорируйте их с помощью — специальной переменной-подчеркивания «_» a, _, _, d = some_function()
Функции могут принимать переменное количество аргументов:
— в прототипе переменное число аргументов записывается как троеточие
function sum(. ) s = 0 for _, n in pairs(arg) do — в функции обращаются к ним, как к таблице «arg» s = s + n end return s end
print (sum(1, 2, 3)) — вернет 9 print (sum(1, 2, 3, 4)) — вернет 14
Все аргументы, скрытые за многоточием, собираются в локальную таблицу arg. У этой таблицы есть явное поле n, которое возвращает количество аргументов, скрытых за многоточием: НАПРИМЕР
funcion func(. ) print(arg.n); end
func(1,2,3); — 3 func(1,4,562,6,26,); — 5
—Немного изменим программу с функцией sum(. )
function sum(. ) s = 0 for i=1,arg.n do s=s+arg[i] end return s end print (sum(1, 2, 3))— вернет 6
print (sum(1, 2, 3, 4)) — вернет 10
Замечание
- pairs(arg) — перебирает пары ключ-значение аргументов функции (_ и n)
- значение «ключ» — номер по порядку аргумента канализируется в переменную _
- вновь вводимая переменная n суммируется в s
Поскольку функции — это полноценный тип данных, то можно создавать переменные-функции, а можно передавать функции как аргументы других функций
a = function(x) — функция, умножающая X на 2 return x * 2 end — краткая форма записи b = function(x) return x + 1 end — функция, увеличивающая X на 1 function apply(table, f) result = <> — создание пустой таблицы for k, v in pairs(table) do result[k] = f(v) — заменяем элемент на какую-то функцию от этого элемента print (result[k]) — печать результата применения функции f к элементу массива t end end — ПОДУМАЙТЕ: что вернут вызовы t = print (apply(t, a)) print (apply(t, b))
- 2, 6, 10
- 2, 4, 6
Объекты = таблицы + функции
Раз мы можем сохранять функции в переменных, то и в полях таблиц тоже сможем. А это уже получаются как-бы-методы. Для тех, кто не знаком с ООП скажу, что основная его польза (по крайней мере в Lua) в том, что функции и данные, с которыми они работают находятся рядом — в пределах одного объекта. Для тех, кто знаком с ООП скажу, что классов здесь нет, а наследование прототипное.
Перейдем к примерам. Есть у нас объект, скажем, лампочка. Она умеет гореть и не гореть. Ну а действия с ней можно сделать два — включить и выключить:
— в таблицу lamp заносится ключ=значение on=false lamp = < on = false >function turn_on(lmp) — в таблицу передаваемую переменной lmp записывается пара ключ-значение on = true lmp.on = true end — или в кратком виде function turn_off(lmp) lmp.on = false end — в таблицу передаваемую переменной lmp записывается пара ключ-значение on = false — это просто функции для работы со структурой turn_on(lamp) — в таблицу lamp записывается пара ключ-значение on = true print (lamp.on) — вывод состояния переменной lamp.on (вывод ЗНАЧЕНИЯ переменной on из таблицы lamp) turn_off(lamp) — в таблицу lmp записывается пара ключ-значение on = false print (lamp.on) — вывод состояния переменной lamp.on
А если лампочку сделать объектом, и функции turn_off и turn_on сделать полями объекта, то получится:
lamp = < on = false, turn_on = function(lmp) — в таблице lamp ключу turn_on присваивается значение — функция с аргументом lmp lmp.on = true end, turn_off = function(lmp) lmp.on = false end >lamp.turn_on(lamp) print (lamp.on) — вывод состояния переменной lamp.on lamp.turn_off(lamp) print (lamp.on) — вывод состояния переменной lamp.on
Создание встраиваемых сценариев на языке Lua
Компилируемые языки программирования и языки сценариев имеют свои уникальные преимущества, но что, если вы захотели бы использовать оба языка для создания мощных приложений? Lua — это встраиваемый язык сценариев, который является маленьким, быстрым и очень мощным. Перед созданием еще одного формата конфигурационных файлов или ресурсов (и еще одного синтаксического анализатора для него) попробуйте Lua.
В то время как интерпретируемые языки программирования, такие как Perl, Python, PHP и Ruby, пользуются все большей популярностью для Web-приложений (и уже давно предпочитаются для автоматизации задач по системному администрированию), компилируемые языки программирования, такие как C и C++, по-прежнему необходимы. Производительность компилируемых языков программирования остается несравнимой (она уступает только производительности ручного ассемблирования), поэтому некоторое программное обеспечение (включая операционные системы и драйверы устройств) может быть реализована эффективно только при использовании компилируемого кода. Действительно, всегда, когда программное и аппаратное обеспечение нужно плавно связать между собой, программисты инстинктивно приходят к компилятору C: C достаточно примитивен для доступа к «голому железу» (то есть, для использования особенностей какой-либо части аппаратного обеспечения) и, в то же время, достаточно выразителен для описания некоторых высокоуровневых программных конструкций, таких как структуры, циклы, именованные переменные и области видимости.
Однако языки сценариев тоже имеют четкие преимущества. Например, после успешного переноса интерпретатора языка на другую платформу подавляющее большинство написанных на этом языке сценариев работает на новой платформе без изменений, не имея зависимостей, таких как системные библиотеки функций (представьте множество DLL-файлов операционной системы Microsoft Windows или множество libcs на UNIX и Linux). Кроме того, языки сценариев обычно предлагают высокоуровневые программные конструкции и удобные операции, которые программистам нужны для повышения продуктивности и скорости разработки. Более того, программисты, использующие язык сценариев, могут работать быстрее, поскольку этапы компиляции и компоновки не нужны. В сравнении с С и его родственниками цикл «кодирование, компоновки, связывание, запуск» сокращается до ускоренного «написание, запуск».
Как и любой язык сценариев, Lua имеет свои особенности:
- Типы в Lua . В Lua значения имеют тип, но переменные типизируются динамически. Типы nil , boolean , number и string работают так, как вы могли бы ожидать.
- Nil — это тип специального значения nil ; используется для представления отсутствия значения.
- Boolean — это тип констант true и false (Nil тоже представляет значение false , а любое не nil значение представляет true ).
- Все числа в Lua имеют тип doubles (но вы можете легко создать код для реализации других числовых типов).
- string — это неизменяемый массив для символов (следовательно, для добавления к строке вы должны сделать ее копию).
i = 3 a = i, a[i], a[i+1], b = i+1, a[i+1], a[i] print (i, a[3], a[4], b, I)
Как всегда, выбор между компилируемым и интерпретируемым языком заключается в сравнении всех за и против каждого языка в контексте, оценке альтернатив и поиске компромиссов.
Что, если бы вы могли взять лучшее из обоих миров: производительность работы с «голым железом» и высокоуровневые, мощные абстракции? Более того, если бы вы могли оптимизировать алгоритмы и функции, зависящие от системы и требующие много процессорного времени, так же как и отдельную логику, не зависящую от системы и очень чувствительную к изменениям требований?
Баланс требований для высокопроизводительного кода и высокоуровневого программирования является сутью Lua, встраиваемого языка программирования. Приложения, включающие Lua, представляют собой комбинацию компилируемого кода и Lua-сценариев. Компилируемый код может при необходимости заняться железом, и, в то же время, может вызывать Lua-сценарии для обработки сложных данных. И поскольку Lua-сценарии отделены от компилируемого кода, вы можете изменять сценарии независимо от него. С Lua цикл разработки более похож на «Кодирование, компоновка, запуск, создание сценариев, создание сценариев, создание сценариев …».
Например, на странице «Uses» Web-сайта Lua перечислены некоторые компьютерные игры для массового рынка, включая World of Warcraft и Defender (версия классической аркады для бытовых консолей), которые интегрируют Lua для запуска всего, начиная с пользовательского интерфейса и заканчивая искусственным интеллектом противника. Другие приложения Lua включают в себя механизмы расширения для популярного инструментального средства обновления Linux-приложений apt-rpm и механизмы управления чемпионатом Robocup 2000 «Сумасшедший Иван». На этой странице есть много хвалебных отзывов о маленьком размере и отличной производительности Lua.
Lua версии 5.0.2 на момент написания данной статьи была текущей версией (недавно появилась версия 5.1). Вы можете загрузить исходный код Lua с lua.org, а можете найти различные предварительно откомпилированные двоичные файлы на wiki Lua-пользователей. Полный код ядра Lua 5.0.2, включая стандартные библиотеки и Lua-компилятор, по размерам не превышает 200KB.
Если вы работаете на Debian Linux, то можете быстро и просто установить Lua 5.0 при помощи следующей команды
# apt-get install lua50
с правами суперпользователя. Все приведенные здесь примеры запускались на Debian Linux «Sarge» с использованием Lua 5.0.2 и ядра Linux 2.4.27-2-686.
После установки Lua на вашей системе попробуйте автономный Lua-интерпретатор. Все Lua-приложения должны быть встроены в базовое приложение. Интерпретатор — это просто специальный тип базового приложения, используемого для разработки и отладки. Создайте файл factorial.lua и введите в него следующие строки:
— определяет функцию факториала function fact (n) if n == 0 then return 1 else return n * fact(n-1) end end print(«enter a number:») a = io.read(«*number») print(fact(a))
Код в factorial.lua (точнее, любая последовательность Lua-операторов) называется порцией (chunk), как было описано выше в разделе «Новшества в Lua». Для запуска созданной вами порции выполните команду lua factorial.lua :
$ lua factorial.lua enter a number: 10 3628800
Или, как в других языках сценариев, вы можете добавить строку со знаками ( #! ) («shebang») в начало сценария, делая сценарий исполняемым, а затем запустить файл как автономную команду:
$ (echo ‘#! /usr/bin/lua’; cat factorial.lua) > factorial $ chmod u+x factorial $ ./factorial enter a number: 4 24
Lua обладает многими удобствами, имеющимися в современных языках программирования сценариев: область видимости, управляющие структуры, итераторы и стандартные библиотеки для обработки строк, выдачи и сбора данных и выполнения математических операций.
В Lua тип имеют только значения , а переменные типизируются динамически. В Lua есть восемь фундаментальных типов (или значений): nil , boolean , number , string , function , thread , table и userdata . Первые шесть типов говорят сами за себя (исключения приведены в разделе «Новшества в Lua»); два последних требуют пояснения.
Таблицы — это универсальная структура данных в Lua. Более того, таблицы — это единственная структура данных в Lua. Вы можете использовать таблицу как массив, словарь (называемый также хеш-таблицей или ассоциативным массивом ), дерево, запись и т.д.
В отличие от других языков программирования, содержимое таблицы в Lua не обязательно должно быть однородным: таблица может включать любые типы и может содержать смесь элементов, подобных массиву, и элементов, подобных словарю. Кроме того, любое Lua-значение (в том числе, функция или другая таблица) может служить ключом элемента словаря.
Для исследования таблиц запустите Lua-интерпретатор и введите строки, показанные жирным шрифтом в листинге 1.
$ lua > — создать пустую таблицу и добавить несколько элементов > t1 = <> > t1[1] = «moustache» > t1[2] = 3 > t1[«brothers»] = true > — создать таблицу и определить элементы (употребляется чаще) > all at once > t2 = > t3 = > t4 = <> > t4[t3] = «the marx brothers» > t5 = > t6 = <[«a night at the opera»] = «classic»>> — создать ссылку и строку > i = t3 > s = «a night at the opera» > — индексами могут быть любые Lua-значения > print(t1[1], t4[t3], t6[s]) moustache the marx brothers classic > — фраза table.string эквивалентна фразе table[«string»] > print(t3.horn, t3[«horn»]) harpo harpo > — индексы могут быть также «многомерными» > print (t5[«marks»][«horn»], t5.marks.horn) harpo harpo > — i указывает на то же значение, что и t3 > = t4[i] the marx brothers > — несуществующие индексы возвращают значения nil > print(t1[2], t2[2], t5.films) nil nil nil > — даже функция может быть ключом > t = <> > function t.add(i,j) >> return(i+j) >> end > print(t.add(1,2)) 3 > print(t[‘add’](1,2)) 3 > — и другой вариант функции в качестве ключа > t = <> > function v(x) >> print(x) >> end > t[v] = «The Big Store» > for key,value in t do key(value) end The Big Store
Как вы могли ожидать, Lua также предоставляет несколько функций-итераторов для обработки таблиц. Функции предоставляет глобальная переменная table (да, Lua-пакеты — это тоже просто таблицы). Некоторые функции, например table.foreachi() , ожидают непрерывный диапазон целых ключей, начиная с 1 (цифра один):
> table.foreachi(t1, print) 1 moustache 2 3
Другие, например table.foreach() , выполняют итерацию по всей таблице:
> table.foreach(t2,print) 1 groucho 3 chico 5 harpo > table.foreach(t1,print) 1 moustache 2 3 brothers true
Хотя некоторые итераторы оптимизированы для целых индексов, все они просто обрабатывают пары (ключ, значение).
Ради интереса создайте таблицу t с элементами и выполните команды table.foreach(t, print) и table.foreachi(t, print) .
Поскольку Lua предназначен для встраивания в базовое приложение, написанное на таких языках, как, например, C или C++, для взаимодействия с базовым приложением данные должны совместно использоваться средой C и Lua. Как указано в » Справочном руководстве по Lua 5.0 «, тип userdata позволяет «произвольным C-данным храниться в Lua-переменных». Вы можете рассматривать тип userdata как массив байтов — байтов, которые могут представлять указатель, структуру или файл в базовом приложении.
Содержимое userdata происходит от C, поэтому оно не может быть модифицировано в Lua. Естественно, поскольку userdata происходит от C, в Lua не существует предопределенных операций для userdata. Однако вы можете создать операции, которые работают с userdata , используя еще один механизм Lua, называемый мета-таблицами (metatables).
Из-за такой гибкости типов table и userdata Lua разрешает перегружать операции для объектов каждого из этих типов (вы не можете перегружать шесть остальных типов). Мета-таблица — это (обычная) Lua-таблица, которая отображает стандартные операции в предоставляемые вами пользовательские функции. Ключи мета-таблицы называются событиями (event); значения (другими словами, функции) называются мета-методами (metamethod).
Функции setmetatable() и getmetatable() изменяют и запрашивают мета-таблицу объекта соответственно. Каждый объект table и userdata может иметь свою собственную мета-таблицу.
Например, одним из событий является __add (для добавления). Можете ли вы определить, что делает следующая порция?
— Перегрузить операцию add — для конкатенации строк — mt = <> function String(string) return setmetatable(, mt) end — Первый операнд — это String table — Второй операнд — это string — .. — это операция конкатенации в Lua — function mt.__add(a, b) return String(a.value..b) end s = String(‘Hello’) print((s + ‘ There ‘ + ‘ World!’).value )
Эта порция отображает следующий текст:
Hello There World!
Функция function String() принимает строку ( string ), заключает ее в таблицу ( ) и назначает мета-таблицу mt этой таблице. Функция mt.__add() является мета-методом, добавляющим строку b к строке, находящейся в a.value b раз. Строка print((s + ‘ There ‘ + ‘ World!’).value ) активизирует мета-метод дважды.
__index — это еще одно событие. Мета-метод для __index вызывается всегда, когда ключ в таблице не существует. Вот пример, который запоминает («memoizes») значение функции:
Поместите этот код в Lua-интерпретатор и введите print(color[1], color[2], color[1]) . Вы должны увидеть что-то подобное blue black blue .
Этот код, получающий ключ и узел, ищет цвет узла. Если он не существует, код присваивает узлу новый, выбранный случайно цвет. В противном случае возвращается цвет, назначенный узлу. В первом случае мета-метод __index выполняется один раз для назначения цвета. В последнем случае выполняется простой и быстрый поиск в хеш-таблице.
Язык Lua предлагает много мощных функциональных возможностей, и все они хорошо документированы.
Кроме простого синтаксиса и мощной структуры таблиц, реальная мощь Lua очевидна при использовании его совместно с базовым языком. Как уже говорилось, Lua-сценарии могут расширить собственные возможности базового языка. Но справедливо также и обратное — базовый язык может одновременно расширять Lua. Например, C-функции могут вызывать Lua-функции и наоборот.
Сердцем симбиотического взаимодействия между Lua и его базовым языком является виртуальный стек . Виртуальный стек (как и реальный) является структурой данных «последний вошел — первый вышел» (last in-first out — LIFO), которая временно сохраняет аргументы функции и ее результаты. Для вызова из Lua базового языка (и наоборот) вызывающая сторона помещает значения в стек и вызывает целевую функцию; принимающая сторона достает аргументы из стека (конечно же, проверяя тип и значение каждого аргумента), обрабатывает данные и помещает в стек результаты. Когда управление возвращается вызывающей стороне, она извлекает значения из стека.
Фактически, все С-интерфейсы прикладного программирования (API) для Lua-операций работают через стек. Стек может хранить любое Lua-значение; однако тип значения должен быть известен как вызывающей стороне, так и вызываемой, а конкретные функции помещают в стек и извлекают из него каждый тип (например, lua_pushnil() и lua_pushnumber() ).
Lua — это чрезвычайно легкий в использовании язык, но его простой синтаксис маскирует его мощь: язык поддерживает объекты (аналогичные объектам Perl), мета-таблицы делают его тип table абсолютно гибким, а C API разрешает отличную интеграцию и расширение сценариев и базового языка. Lua может использоваться совместно с языками C, C++, C#, Java и Python.
Перед созданием еще одного формата конфигурационного файла или ресурса (и еще одного синтаксического анализатора для него) попробуйте Lua. Язык Lua (так же как и его сообщество) надежен, изобретателен и готов прийти на помощь.
В листинге 2 показана простая C-программа, реализующая минимальный, но функциональный Lua-интерпретатор.
1 #include 2 #include 3 #include 4 #include 5 6 int main (void) < 7 char buff[256]; 8 int error; 9 lua_State *L = lua_open(); /* открывает Lua */ 10 luaopen_base(L); /* открывает основную библиотеку */ 11 luaopen_table(L); /* открывает библиотеку table */ 12 luaopen_io(L); /* открывает библиотеку I/O */ 13 luaopen_string(L); /* открывает библиотеку string */ 14 luaopen_math(L); /* открывает библиотеку math */ 15 16 while (fgets(buff, sizeof(buff), stdin) != NULL) < 17 error = luaL_loadbuffer(L, buff, strlen(buff), «line») // 18 lua_pcall(L, 0, 0, 0); 19 if (error) < 20 fprintf(stderr, «%s», lua_tostring(L, -1)); 21 lua_pop(L, 1); /* извлечь сообщение об ошибке из стека */ 22 >23 > 24 25 lua_close(L); 26 return 0; 27 >
Строки с 2 по 4 включают стандартные Lua-функции, несколько удобных функций, используемых во всех Lua-библиотеках, и функции для открытия библиотек, соответственно. Строка 9 создает Lua- структуру . Все структуры сначала пусты; вы добавляете библиотеки или функции к структуре при помощи luaopen_. () , как показано в строках с 10 по 14.
В строке 17 luaL_loadbuffer() принимает входную информацию с stdin в виде порции и компилирует ее, помещая порцию в виртуальный стек. Строка 18 извлекает порцию из стека и выполняет ее. Если во время исполнения возникает ошибка, Lua-строка помещается в стек. Строка 20 обращается к вершине стека (вершина стека имеет индекс -1 ) как к Lua-строке, распечатывает сообщение и удаляет значение из стека.
Используя C API, ваше приложение может также «достать» информацию из Lua-структуры. Следующий фрагмент кода извлекает две глобальные переменные из Lua-структуры:
.. if (luaL_loadfile(L, filename) // lua_pcall(L, 0, 0, 0)) error(L, «cannot run configuration file: %s», lua_tostring(L, -1)); lua_getglobal(L, «width»); lua_getglobal(L, «height»); .. width = (int) lua_tonumber(L, -2); height = (int) lua_tonumber(L, -1); ..
Опять же, обратите внимание на то, что передачу разрешает стек. Вызов любой Lua-функции из C аналогичен следующему коду: извлечь функцию при помощи lua_getglobal() , поместить аргументы, выполнить lua_pcall() и обработать результаты. Если Lua-функция возвращает n значений, первое значение находится по индексу -n в стеке, а последнее — по индексу -1 .
Обратное действие (вызов C-функции из Lua) аналогично. Если ваша операционная система поддерживает динамическую загрузку, Lua может загружать и вызывать функции по требованию. В операционных системах, в которых необходима статическая загрузка, расширение Lua-механизма для вызова C-функции потребует перекомпоновки Lua.
Ссылки по теме
01.2007 | ||
Источник: www.interface.ru |