Lua сравнение. Создание встраиваемых сценариев на языке Lua. Оператор присваивания в Lua

=== Урок №5 ===

Brown paper packages tied up with strings,
These are a few of my favorite things.

В прошлых уроках мы мельком касались строк, и вы уже должны себе представлять, что значение типа строка (string ) есть некий текст, который можно, например, вывести на экран; или, говоря более строго, строка - это цепочка символов, каждый из которых представлен в памяти одним или двумя байтами. Строку в Lua можно записать одним из следующих образов:

200?"200px":""+(this.scrollHeight+5)+"px");">
-- В одинарных кавычках
str = "Mickey Mouse"

В двойных кавычках
str = "stack pile heap"

Между так называемыми [i]длинными скобками.
-- Количество знаков равно "=" между двумя открывающими "["
-- и двумя закрывающими "]" знаками, вообще говоря, произвольно.
str1 = [==]
str2 = []
str3 = [==========]

Большой разницы между ними нет, однако в строке, заключённой в одинарные кавычки разрешено использовать символ ", в строке, заключённой в двойные кавычки разрешён символ ", а в строке в длинных скобках разрешены вообще любые символы, включая перенос строки. Таким образом, длинные скобки лучше всего подходят для записи больших форматированных кусков текста (в следующих примерах за знаком --> будет следовать текст, который выведется на экран ).

200?"200px":""+(this.scrollHeight+5)+"px");">
print "Mah good ol" pal"
--> Mah good ol" pal

Print ""This is ridiculous!" he said."
--> "This is ridiculous!" he said.

Print [====[
O come, O come, Emmanuel,
and ransom captive Israel
that mourns in lonely exile here
until the Son of God appear.
]====]
-- Выведет именно то, что видно

Если очень нужно использовать в строке и ", и ", но не хочется писать длинные скобки (вас можно понять: длинные скобки действительно некрасиво выглядят), то желаемый символ экранируется знаком \ (обратная косая черта ):

200?"200px":""+(this.scrollHeight+5)+"px");">
print "Can use both \"s and \"s."
--> Can use both "s and "s

Также можно экранировать перенос строки.
-- Если убрать \ в строке ниже, программа не будет работать.

Print "Hi,\
Eugene."

--> Hi,
--> Eugene.

Если требуется записать в строке непосредственно символ \,
-- он пишется два раза подряд. Это не очень удобно, но по-другому
-- нельзя: либо так, либо используйте длинные скобки.

Print "C:\\Lua\\lua.exe"
--> C:\Lua\lua.exe

Print []
--> D:\Photos\smile.jpg

Записи типа \", \" и \\ называются экранирующими последовательностями или escape-последовательностями . Lua поддерживает и другие экранирующие последовательности, которые вы можете использовать в ваших строках:

200?"200px":""+(this.scrollHeight+5)+"px");">
-- \n - переход на новую строку, как если бы вы
-- нажали Enter в текстовом редакторе:

Print "Great\nNews!"
--> Great
--> News!

-- \b - символ backspace.
-- Работу этого символа можно рассмотреть как перемещение
-- виртуального указателя на одну позицию влево, так что дальнейший
-- текст выводится с новой позиции, замещая все последующие символы.
-- Замещаемые символы фактически никуда не деваются, но на экран не
-- выводятся.

Print "You are dumb\b\b\b\b\b nice"
--> You are nice

-- \r - возврат каретки.
-- Виртуальный указатель как бы перемещается в самое начало строки и
-- последующие символы выводятся на экран оттуда.

Print "Nobody hid the facts\rBush "
--> Bush hid the facts

-- \t - табуляция, как если бы вы нажали Tab в
-- текстовом редакторе. С его помощью удобно оформлять таблицы.

Print "Hewey\tDewey\tLouie"
print "100$\t200$\t150$"
--> Hewey Dewey Louie
--> 100$ 200$ 150$

-- \v - вертиакальная табуляция, почти как если бы вы нажали
-- клавишу "Вниз" в текстовом редакторе.
-- Используется редко, удобна для вывода стихов Маяковского.

Print "Listen,\v if stars are lit\v it means - there is someone who needs it"
-- Стих выведется лесенкой.

-- \f - символ form feed. Аналогичен \v, но может не сработать
-- (собственно, как и сам \v).

-- \a - звуковой сигнал (англ. alert). Предполагается, что попытка
-- вывести этот символ на экран заставит встроенный динамик вашего компьютера
-- издать короткий писк, но так как в современных ОС не принято разрешать
-- звук из PC спикера, то \a в большинстве случаев просто не даст никакого
-- эффекта.

-- \xOO - символ с заданным шестнадцатиричным кодом, где вместо ОО
-- вы должны проставить некое двузначное шестнадцатиричное число.
-- Чтобы узнать код желаемого символа, обратитесь к таблице кодов ASCII,
-- в Интернете её довольно легко найти.

Print "\x48\x65\x6C\x6C\x6F\x20\x77\x6F\x72\x6C\x64"
-- Тайная запись на языке хакеров!

-- \dOOO - символ с заданным десятичным кодом. То же самое, что и
-- \xOO, только здесь код символа вводится десятичными цифрами. Диапазон
-- допустимых чисел - от 0 до 255.

Print "\d52\d50"
-- Знаменитая программа, записаная строкой выше, на одном суперкомпьютере
-- исполнялась миллион лет.

Если вам всё ещё не совсем понятна работа некоторых экранирующих последовательностей - ничего страшного, потому что вам, скорее всего, в жизни пригодится только \n. Учтите, что запись экранирующих последовательностей в строках, заключённых в длинные скобки, не даст никакого эффекта - экранирующие последовательности между длинными скобками игнорируются!

200?"200px":""+(this.scrollHeight+5)+"px");">
print [[\n\t\v\b\a]]

--> \n\t\v\b\a

Что же, теперь-то мы умеем записать в строке всё, что угодно, но не слишком ли это скучно? В строках самое интересное не то, что мы можем в них записать, а то, что мы можем с ними сделать .

Одна из ключевых операций над строками - конкатенация , или присоединение. Обозначается в тексте программы знаком "..". Суть работы очень проста:

200?"200px":""+(this.scrollHeight+5)+"px");">
-- Берём строку...

И ещё одну...

Соединяем...

Str3 = str1 .. str2

-- [s]Варим три минуты Смотрим, что получилось:

Print(str3)
--> mishmash

С помощью оператора ".." к строке можно присоединять не только другие строки, но и числа, что даёт нам удобное средство форматирования вывода:

200?"200px":""+(this.scrollHeight+5)+"px");">
kittens = 3
print("I have "..kittens.." kittens.")
--> I have 3 kittens

Код ниже выведет на экран весьма длинную песенку.
for i = 100, 1, -1 do
print(i .. " bottles of beer on the wall.")
print(i .. " bottles of beer.")
print("Take one down and pass it around.")
print((i-1) .. " bottles of beer on the wall.")
end

Print [==[
No more bottles of beer on the wall.
No more bottles of beer.
Go to the store, buy some more!]==]

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

Ещё одна полезная операция над строками - сравнение, осуществляемая, как и сравнение чисел, с помощью операторов >, <, ==, ~=, >= и <=. Тут может возникнуть замешательство: если с равенством/неравенством строк всё ясно (в равных строках последовательность символов совпадает), то как проверить, какая строка больше? По длине? Или по сумме кодов символов? Но в первом случае будут считаться равными строки "Jesus" и "Satan", а во втором - "spoon" и "snoop". Непростая задача, выходит!

Понятно, что строке, в отличие от числа, нельзя поставить в соответсвие какое-либо значение, чтобы можно было однозначно, взглянув на него, сказать: какая строка больше, а какая меньше. Поэтому математики, вдохновляясь великими составителями словарей, придумали лексикографическое сравнение . Словари тут притом, что логика лексикографического сравнения примерно такая: большей считается та строка, которая относительно другой стояла бы ниже, если бы их вносили, например, в алфавитный указатель. Более строгое определение порядка сравнения из Википедии :

Слово a предшествует слову b (a < b ), если первые m символов совпадают, а m + 1 символ слова a меньше m + 1 символа слова b .

А вот символы запросто сравниваются по величине ASCII-кода, так что сравнение строк теперь полностью определено. Примеры:

200?"200px":""+(this.scrollHeight+5)+"px");">
print ("banana" > "apple")
--> true

Print ("banana" > "banona")
--> false

Print ("Argonaut" > "argonaut")
--> false
-- ASCII-коды прописных букв меньше ASCII-кодов строчных букв

Print ("bit" < "bitter")
--> true
-- Если символы в неравнодлинных строках совпадают, меньшей считается
-- более короткая.

Print ("Satan" == "Satan")
--> true

Print ("Jesus" ~= "Satan")
--> true

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

Большинство оставшихся вохможных манипуляций со строками в Lua возложено на модуль string: он, как и модуль math, содержит некоторые функции, обращаться к которым необходимо с предшествующим именем модуля, отделённым точкой. Существование модуля string помимо всего прочего обязывает вас не называть ни одну переменную именем string, иначе доступ к модулю для программы будет потерян ценой создания одной маленькой переменной. А ведь, казалось бы, так хочется!

Многие из функций для работы со строками весьма сложны для понимания и использования и не так часто необходимы (например, string.gsub и string.match, а также string.format), поэтому нашего краткого обзора на них ни в коем случае не хватит, но я их всё равно упоминаю здесь, чтобы разжечь ваше любопытство и подтолкнуть вас к самостоятельному изучению их работы с помощью официального руководства , так как в практике программирования на Lua такие знания вполне могут пригодиться. А сейчас мы вкратце рассмотрим простейшие функции модуля string:

1. string.len (<строка>) - возвращает длину строки.
Пример работы:

200?"200px":""+(this.scrollHeight+5)+"px");">
string.len("")
-- Пустая строка, вернёт 0.

String.len("Yes we can")
-- Вернёт 10.

Длина строки в Lua измеряется в байтах, а не в символах, что на самом деле прискорбно. Между байтом и символом гарантированно нет никакой разницы, если вы используете только символы ASCII, и, в частности, не употребляете русских букв .

200?"200px":""+(this.scrollHeight+5)+"px");">
string.len("Зело дивно")
-- В кодировке UTF-8 вернёт 19, хоть нам и хотелось бы видеть 10. Зело дивно!
-- Дело в том, что один русский символ представляется двумя байтами, а
-- не одним.
-- Такое поведение может обескуражить программистов на Python, привыкших,
-- что подсчитываются именно действительные символы, в Python подобная
-- функция возвращает 10.

Другой случай, способный вызвать затруднение - экранирующие последовательности. Хоть вы и записываете их двумя символами, в памяти они, скорее всего, будут представлены одним байтом.

200?"200px":""+(this.scrollHeight+5)+"px");">
string.len("\a\t\b")
-- Возвратит 3.

Исключительное положение в этом смысле у последовательности \n. Дело в том, что перенос строк на разных платформах представлен разными байтами. В Linux это ASCII-символ подача строки с кодом 0xA, в компиьютерах Apple Macintosh - символ возврат каретки с кодом 0xD, а в ОС Windows - это сразу два символа 0xD и 0xA, следующие друг за другом, так что длина строки с последовательностью \n в ней будет иметь разное значение в зависимости от компьютера, на котором запускается программа:

200?"200px":""+(this.scrollHeight+5)+"px");">
string.len("what a\nhorrible\nnight to\nhave a\ncurse.")
-- Возвратит 39 в Linux и 43 в Windows.

2. string.lower (<строка>) и string.upper (<строка>) - возвращают новую строку, в которой все буквы исходной заменены соответсвенно на строчные либо на заглавные. Заметьте, что исходная строка при этом остаётся в сохранности.

200?"200px":""+(this.scrollHeight+5)+"px");">
string.upper("A quick brown fox jumps over the lazy dog.")
-- Возвращает: "A QUICK BROWN FOX JUMPS OVER THE LAZY DOG."

String.lower("The NASDA System Was Developed To Protect US")
-- "the nasda system was developed to protect us"

Учтите, что эти функции обычно работают только с латинскими буквами, так что не стоит полагаться на них при работе с русскоязычными символами.

String.toupper и string.tolower удобно применять, если вы хотите сравнить две строки без учёта регистра: просто приведите их к одному регистру и затем производите сравнение.

200?"200px":""+(this.scrollHeight+5)+"px");">
-- comparestrings.lua --

Function compare(str1, str2)
return (string.toupper(str1) == string.toupper(str2))
end

Print(copmpare("StrANgER", "sTRanGer"))
--> true

3. string.reverse (<строка>) - возвращает строку, состоящую из последовательности символов исходной строки, расположенных в обратном порядке.
Грубо говоря, string.reverse "переворачивает" строку.

200?"200px":""+(this.scrollHeight+5)+"px");">
print(string.reverse("Happy taskbar, can"t try."))
--> .yrt t"nac, tabksat yppaH

Print(string.reverse("Radar"))
--> radaR

Опять же, так как многобайтовые символы Lua обрабатывает некорректно, использование русских букв в переворачиваемой строке может привести к непредсказуемым результатам. Также в Windows под вопросом корректное переворачивание строк, содержащих символ \n. Следовательно, string.reverse стоит применять с аккуратностью: не думайте, что ("булка" == string.reverse("аклуб")) обязательно возвратит true.

4. string.rep (<исходня строка>, , [строка-разделитель]) - присоединяет друг к другу N копий исходной строки, разделённых строкой-разделителем, если последняя дана.

200?"200px":""+(this.scrollHeight+5)+"px");">
str = "WOBBLE"
sep = ", "

Вызов с разделителем:
print(string.rep(str, 4, sep))
--> WOBBLE, WOBBLE, WOBBLE, WOBBLE

Вызов без разделителя:
print(string.rep(str, 4))
--> WOBBLEWOBBLEWOBBLEWOBBLE


Полезность функции string.rep неочевидна, но мне за всю жизнь удалось пару раз успешно применить её. Правда, лучше бы я этого не делал.

5. string.sub (<строка>, <индекс начального символа>, [индекс конечного символа]) - возвращает подстроку , определяемую заданными индексами.
Упрощённо говоря, так можно обрезать строку.

200?"200px":""+(this.scrollHeight+5)+"px");">
str = "John Emmanuel Doe"

Указав только один индекс, мы велим функции обрезать строку слева до
-- символа с этим индексом:

String.sub(str, 6)
-- Возвратит "Emmanuel Doe"

Если указанный индекс меньше нуля, то мы указываем оставить только
-- некоторое число последних символов, а остальное - обрезать:

String.sub(str, -3)
-- Возвратит "Doe"

Если указать два индекса, символы, заключённые в позициях между ними,
-- будут оставлены, оставшаяся часть строки - отброшена:

String.sub(str, 6, 13)
-- Возвратит "Emmanuel"

6. string.byte (<строка>, [индекс начального символа], [индекс конечного символа]) - возвращает численные значения байтов, составляющих строку. Индексы возвращаемых символов указываются точно таким же образом, как в функции string.sub, только string.byte можно вообще не передавать никаких индексов, и тогда будет возвращено значение только первого байта.
Так как строки бывают достаточно длинными (средняя строка редко бывает короче десяти символов), возвращаемых значений может быть очень много, поэтому разумнее собирать их не в переменные, а в таблицу , которую мы рассмотрим в следующем уроке.

200?"200px":""+(this.scrollHeight+5)+"px");">
str = "John Emmanuel Doe"

String.byte(str, 1, 4)
-- Возвратит числа: 74, 111, 104, 110 - числовые
-- коды символов "J", "o", "h", "n"

String.byte(str)
-- Возвратит 74, код символа "J"

Получаем полное представление строки в кодах ASCII.

String.byte(str, 1, string.len(str))
-- Получаем 74, 111, 104, 110, 32, 69, 109, 97, 117,
-- 101, 108, 32, 68, 111, 101

7. string.char ([число1, число2, ...]) - "собирает" строку из переданной последовательности кодов. Удобна в связке string.byte, когда вы с её помощью разбиваете строку на коды, как-то обрабатываете их, а потом собираете обратно в строку.
Учтите, что числа должны находиться в пределах от 0 до 255, иначе функция завершит работу с ошибкой.

200?"200px":""+(this.scrollHeight+5)+"px");">
string.char(0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64)
-- Возвращает строку "Hello world"

String.char()
-- Возвращает пустую строку "".

String.char(74, 111, 104, 110)
-- Возвратит "John"

String(1024, 768, 111)
--! Вызовет ошибку, работать не будет.

Напоследок хочется дать вам какую-нибудь интересную программу, включающую в себя манипуляции со строками, но - беда! - занятных задач на обработку строк очень мало, классическая задача о палиндроме в общем виде на Lua, по крайней мере, с помощью известных нам средств, решается слишком громоздко, а в частном случае - без пробелов, пунктуации и специальных символов - слишком тривиально и, следовательно, скучно (но, может, вы попробуете? Условие такое: напишите функцию, которая проверяет, читается ли фраза слева направо точно так же, как и справа налево. Для упражнения точно пригодятся string.reverse и string.upper или string.lower).

Вместо этого я расскажу вам о преобразованиях между строками и другими значениями и как иногда полезно применять эти преобразования.

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

200?"200px":""+(this.scrollHeight+5)+"px");">
-- reversenumber.lua --

Function reversenumber(num)
local str = tostring(num) -- преобразуем число в строку
str = string.reverse(str) -- переворачиваем строку
return tonumber(str) -- преобразуем обратно в число и возвращаем
end

Print(rn(123), rn(150000), rn(1.33), rn(0.1))
--> 123 51 33.1 1

Ключевыми функциями в работе reversenumber являются tonumber и tostring.

1. tonumber принимает один или два аргумента, первый из которых - строка, содержащая число, а второй, опциональный - основание системы счисления, в которой оно записано (от 2 до 36). Последнее по умолчанию считается равным 10. Системы счисления с основанием большим, чем 10, образуются подобно шестнадцатиричной: недостающие цифры записываются в виде латинских букв. Если строка представляет собой некорректно записанное число, tonumber возвращает nil .

200?"200px":""+(this.scrollHeight+5)+"px");">
tonumber("700")
-- Возвратит 700

Tonumber("ff", 16)
-- 255

Tonumber("steiner", 36)
-- 62727809283

Tonumber("one hundred")
tonumber("300 Spartans")
-- Вернут nil

2. tostring работает в обратном направлении: она возвращает строковое представление переданного в качестве параметра значения. Причём значением может быть:

200?"200px":""+(this.scrollHeight+5)+"px");">
-- Число
tostring(128)
-- Вернёт "128"

Логическое значение
tostring(1 > 0)
-- "true"

Nil
tostring(nil)
-- "nil" (строка "nil", а не значение nil)

Функция
tostring(tostring)
-- "function: 0x418a20"

Поздравляю! Теперь вы стали настоящим волшебником строк, и можете творить с ними почти всё, что вам вздумается. Веселитесь, потому что, как говорится:

200?"200px":""+(this.scrollHeight+5)+"px");">
print(string.sub(_VERSION, 1, 3).." is "..string.sub(tostring(print), 1, 3))

Резюме:
1. Мы узнали, как записываются строки в тексте программы.
2. Узнали про экранирующие последовательности, как и для чего их применять.
3. Узнали про лексикографическое сравнение.
4. Выяснили, почему Lua плохо работает с русскими символами, и в каких ситуациях их следует избегать.
5. Узнали о целом ряде полезных функций модуля string.
6. Научились превращать числа и строки друг в друга с помощью функций tonumber и tostring.

Упражнения:
1. Взгляните на функцию reversenumber из reversenumber.lua . При каких значениях параметра она не будет работать?
2. Как вы думаете, как можно написать свою версию string.sub с помощью string.char и string.byte?
3. Напишите программу, которая сосчитывает, сколько раз в строке встречается символ "a".

Ваша проблема связана с непониманием оператора or , который является общим для людей, изучающих языки программирования, подобные этому. Да, ваша непосредственная проблема может быть решена путем написания x ~= 0 and x ~= 1 , но я расскажу немного подробнее о том, почему ваша попытка решение не работает.

Когда вы читаете x ~=(0 or 1) или x ~= 0 or 1 , естественно разобрать это, как и предложение «x не равно нулю или одному». В обычном понимании этого утверждения «х» является субъектом, «не равно» - это предикатная или глагольная фраза, а «ноль или один» - это объект, набор возможностей, соединенных конъюнкцией. Вы применяете объект с глаголом к ​​каждому элементу в наборе. Р>

Однако Lua не анализирует это на основе правил грамматики английского языка, он анализирует его в бинарных сравнениях двух элементов на основе его порядка операций. Каждый оператор имеет приоритет , который определяет порядок, в котором он будет оцениваться. or имеет более низкий приоритет, чем ~= , так же как добавление в математике имеет более низкий приоритет, чем умножение. Все имеет более низкий приоритет, чем круглые скобки. Р>

В результате при оценке x ~=(0 or 1) интерпретатор сначала вычислит 0 or 1 (из-за круглых скобок), а затем x ~= результат первого вычисления, а во втором примере он вычислит x ~= 0 , а затем применить результат этого вычисления к or 1 .

логический оператор or "возвращает свой первый аргумент, если это значение равно отличается от nil и false, иначе, или возвращает свой второй аргумент ». реляционный оператор ~= является обратным оператору равенства == ; он возвращает true, если его аргументы являются разными типами (x является числом, правильно?) и в противном случае обычно сравнивает его аргументы.

Используя эти правила, x ~=(0 or 1) разложится до x ~= 0 (после применения оператора or), и это вернет "true", если x - это что-то другое, кроме 0, включая 1, что нежелательно. Другая форма, x ~= 0 or 1 , сначала оценит x ~= 0 (которая может вернуть true или false, в зависимости от значения x). Затем он разложится на один из false or 1 или true or 1 . В первом случае оператор вернет 1 , а во втором случае оператор вернет true . Поскольку структуры управления в Lua рассматривают nil и false как false, а что-то еще, чтобы быть правдой, это всегда будет включать оператор if , который не является тем, что вы хотите.

Невозможно использовать бинарные операторы, такие как предоставленные в языках программирования, для сравнения одной переменной со списком значений. Вместо этого вам нужно сравнить переменную с каждым значением по одному. Есть несколько способов сделать это. Самый простой способ - использовать законы Де Моргана , чтобы выразить утверждение «не одно или равно нулю» (что не может оцениваться с помощью двоичных операторов) как «не один, а не нуль», который тривиально можно записать с помощью двоичных операторов:

If x ~= 1 and x ~= 0 then print("X must be equal to 1 or 0") return end

В качестве альтернативы вы можете использовать цикл для проверки этих значений:

Local x_is_ok = false for i = 0,1 do if x == i then x_is_ok = true end end if not x_is_ok then print("X must be equal to 1 or 0") return end

Наконец, вы можете использовать реляционные операторы для проверки диапазона, а затем проверить, что x является целым числом в диапазоне (вы не хотите 0.5, верно?)

If not (x >= 0 and x <= 1 and math.floor(x) == x) then print("X must be equal to 1 or 0") return end

Обратите внимание, что я написал x >= 0 and x <= 1 . Если вы поняли приведенное выше объяснение, вы должны теперь объяснить, почему я не написал 0 <= x <= 1 и что это ошибочное выражение вернется!

Скрипты на языке Lua

Написанный на Lua скрипт не имеет какой-либо специальной функции, с которой начиналось бы его выполнение. Скрипт можно рассматривать просто как набор команд (инструкций), который выполняется, начиная с первой инструкции.

Скрипт может быть как очень простым, состоящим всего из одной команды, так и весьма сложным, содержащим десятки, сотни и даже тысячи инструкций. Следующие друг за другом инструкции могут разделяться точкой с запятой (;). Однако это требование не является обязательным, поэтому весь приведённый ниже код является корректным с точки зрения синтаксиса:

Работа с переменными в Lua

Переменные используются для хранения значений в процессе выполнения скрипта.

Имена переменных в Lua

Именами (идентификаторами) переменных в Lua могут быть любые последовательности из букв, цифр и символа подчеркивания, начинающиеся не с цифры.

Обратите внимание

Язык Lua различает регистр символов, поэтому abc, Abc, ABC являются различными именами.

В таблице ниже приведены слова, которые зарезервированы языком Lua и не могут использоваться в именах переменных:

and break do else elseif

end false for function if

in local nil not or

repeat return then true until

Кроме того, все имена, начинающиеся с символа подчеркивания, за которым идут заглавные буквы (например, _VERSION) также являются зарезервированными.

Какие переменные бывают в Lua?

Переменные в Lua могут быть глобальными и локальными. Если переменная не объявлена явно как локальная, она считается глобальной.

Глобальные переменные Lua

Глобальная переменная появляется в момент присваивания ей первого значения. До присваивания первого значения обращение к глобальной переменной даёт nil.

MsgBox(tostring (g)) --> nil

MsgBox(tostring (g)) --> 1

Глобальная переменная существует до тех пор, пока существует среда исполнения скрипта и доступна любому Lua-коду, выполняемому в этой среде.

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

g = 1 - создаем глобальную переменную g со значением 1

g = nil - удаляем глобальную переменную g

MsgBox(tostring (g)) --> nil

Все глобальные переменные являются полями обычной таблицы, называемой глобальным окружением. Эта таблица доступна через глобальную переменную _G. Поскольку полями глобального окружения являются все глобальные переменные (включая саму _G), то _G._G == _G.

Локальные переменные Lua

Любые локальные переменные должны быть объявлены явно с использованием ключевого слова local. Объявить локальную переменную можно в любом месте скрипта. Объявление может включать в себя присваивание переменной начального значения. Если значение не присвоено, переменная содержит nil.

local a - объявляем локальную переменную a

local b = 1 - объявляем локальную переменную b, присваиваем ей значение 1

local c, d = 2, 3 - объявляем локальные переменные c и d, присваиваем им значения 2 и 3

Область видимости локальной переменной начинается после объявления и продолжается до конца блока.

Примечание

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

Под блоком понимается:

тело управляющей конструкции (if-then, else, for, while, repeat);

тело функции;

фрагмент кода, заключённый в ключевые слова do...end.

Если локальная переменная определена вне какого-либо блока, её область видимости распространяется до конца скрипта.

local i = 1 - переменная i локальна в пределах скрипта

while i <= a do - цикл от 1 до 5

local a = i^2 - переменная а локальна внутри цикла while

MsgBox(a) --> 1, 4, 9, 16, 25

MsgBox(a) -->

if i > 5 then

local a - переменная а локальна внутри then

MsgBox(a) --> 10

MsgBox(a) --> 5 (здесь обращение к глобальной a)

local a = 20 - переменная а локальна внутри do-end

MsgBox(a) --> 20

MsgBox(a) --> 5 (здесь обращение к глобальной a)

Обратите внимание

Когда возможно, рекомендуется использовать локальные переменные вместо глобальных. Это позволит избежать «засорения» глобального пространства имён и обеспечит лучшую производительность (поскольку доступ к локальным переменным в Lua выполняется несколько быстрее, чем к глобальным).

Типы данных Lua

Какие типы данных поддерживает язык Lua?

Lua поддерживает следующие типы данных:

1. Nil (ничего). Соответствует отсутствию у переменной значения. Этот тип представлен единственным значением - nil.

2. Boolean (логический). К данному типу относятся значения false (ложь) и true (истина).

При выполнении логических операций значение nil рассматривается как false. Все остальные значения, включая число 0 и пустую строку, рассматриваются как true.

3. Number (числовой). Служит для представления числовых значений.

В числовых константах можно указывать необязательную дробную часть и необязательный десятичный порядок, задаваемый символами «e» или «E». Целочисленные числовые константы можно задавать в шестнадцатеричной системе, используя префикс 0x.

Примеры допустимых числовых констант: 3, 3.0, 3.1415926, 314.16e-2, 0xff.

4. String (строковый). Служит для представления строк.

Строковые значения задаются в виде последовательности символов, заключённой в одинарные или двойные кавычки:

a = «это строка»

b = "это вторая строка"

Строки, заключённые в двойные кавычки, могут интерпретировать C-подобные управляющие последовательности (escape-последовательности), начинающиеся с символа «\» (обратный слэш):

\b (пробел),

\n (перевод строки),

\r (возврат каретки);

\t (горизонтальная табуляция),

\\ (обратный слеш);

\"" (двойная кавычка);

\" (одинарная кавычка).

Обратите внимание

Символ в строке также может быть представлен своим кодом с помощью escape-последовательности:

где ddd - последовательность из не более чем трёх цифр.

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

Определение строки с помощью двойных квадратных скобок позволяет игнорировать все escape-последовательности, т. е. строка создаётся полностью так, как описана:

local a = [] в Lua]=]

Будет срока: «определение строки [] в Lua»

5. Function (функция). Функции в Lua могут быть записаны в переменные, переданы как параметры в другие функции ивозвращены как результат выполнения функций.

6. Table (таблица). Таблица представляет собой набор пар «ключ» - «значение», которые называют полями илиэлементами таблицы. Как ключи, так и значения полей таблицы могут иметь любой тип, за исключением nil. Таблицы не имеют фиксированного размера: в любой момент времени в них можно добавить произвольное число элементов.

Подробнее - в статье «Создание таблиц в Lua»

7. Userdata (пользовательские данные). Является особым типом данных. Значения этого типа не могут быть созданы или изменены непосредственно в Lua-скрипте.

Userdata используется для представления новых типов, созданных в вызывающей скрипт программе или в библиотеках, написанных на языке С. Например, библиотеки расширений Lua для «CronosPRO» используют этот тип для представления таких объектов, как:

банки данных (класс Bank);

базы данных (класс Base);

записи (класс Record) и т. п.

8. Thread (поток). Соответствует потоку выполнения. Эти потоки никаким образом не связаны с операционной системой и поддерживаются исключительно средствами самого Lua.

Как в Lua задать тип переменной?

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

a = 123 - переменная a имеет тип number

a = «123» - теперь переменная a имеет тип string

a = true - теперь переменная a имеет тип boolean

a = {} - теперь переменная a имеет тип table

Обратите внимание

Переменные типа table, function, thread и userdata не содержат самих данных, а хранят ссылки на соответствующие объекты. При присваивании, передачи в функцию в качестве аргумента и возвращении из функции в качестве результата копирования объектов не происходит, копируются только ссылки на них.

a = {} - создаем таблицу. В переменную a помещается ссылка на таблицу

b = a - переменная b ссылается на ту же таблицу, что и a

a = 10 - элементу таблицы с индексом 1 присвоено значение 10

MsgBox(b) --> "10"

MsgBox(a) --> "20"

Остальные данные являются непосредственными значениями.

MsgBox(a) --> "20"

MsgBox(b) --> "10"

Как в Lua получить тип переменной?

Тип значения, сохранённого в переменной, можно выяснить при помощи стандартной функции type. Эта функция возвращает строку, содержащую название типа («nil», «number», «string», «boolean», «table», «function», «thread», «userdata»).

t = type («это строка») - t равно «string»

t = type (123) - t равно «number»

t = type (type) - t равно «function»

t = type (true) - t равно «boolean»

t = type (nil) - t равно «nil»

t = type (CroApp.GetBank()) - t равно «userdata»

Как в Lua преобразовать тип переменной?

Lua при необходимости автоматически преобразует числа в строки и наоборот. Например, если строковое значение является операндом в арифметической операции, оно преобразуется в число. Аналогично числовое значение, встретившееся в том месте, где ожидается строковое, будет преобразовано в строку.

a = «10» + 2 - a равно 12

a = «10» + 2 - a равно «10 + 2»

a = "-5.3e-10"*«2» - a равно -1.06e-09

a = «строка» + 2 - Ошибка! Невозможно преобразовать «строка» в число

Значение любого типа можно явным образом преобразовать в строку с помощью стандартной функции tostring.

a = tostring (10) - a равно «10»

a = tostring (true) - a равно «true»

a = tostring (nil) - a равно «nil»

a = tostring ({ = «это поле 1»}) - a равно «table: 06DB1058»

Из предыдущего примера видно, что содержимое таблиц функцией tostring не преобразуется. Выполнить такое преобразование можно с помощью функции render.

a = render (10) - a равно «10»

a = render (true) - a равно «true»

a = render (nil) - a равно «nil»

a = render ({ = «это поле 1»}) - a равно "{ = «это поле 1»}"

Для явного преобразования значения в число можно использовать стандартную функцию tonumber. Если значение является строкой, которую можно преобразовать в число (или уже является числом), функция возвращает результат преобразования, в противном случае возвращает nil.

a = tonumber («10») - a равно «10»

a = tonumber («10»..".5") - a равно 10.5

a = tonumber (true) - a равно «nil»

a = tonumber (nil) - a равно «nil»

Расстановка комментариев в Lua

Комментарий в Lua начинается двумя знаками «минус» (--) и продолжается до конца строки.

local a = 1 - однострочный комментарий

Если непосредственно после символов «--» идут две открывающие квадратные скобки ([[), комментарий являетсямногострочным и продолжается до двух закрывающих квадратных скобок (]]).

local a = 1 - [[ многострочный

комментарий ]]

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

local a = [[Компания «Кронос»]] - [=[

local a = [[Компания «Кронос»]]

Количество символов «=» определяет вложенность:

local a = [=[определение некоторой строки [] в языке Lua]=] --[==[

local a = [=[определение некоторой строки [] в языке Lua]=]

Операции, применяемые в Lua

В выражениях, написанных на Lua, могут применяться следующие виды операций:

1. Арифметические операции.

Lua поддерживает следующие арифметические операции:

+ (сложение);

- (вычитание);

* (умножение);

/ (деление);

^ (возведение в степень);

% (остаток от деления).

Обратите внимание

Арифметические операции применимы как к числам, так и к строкам, которые в этом случае преобразуются в числа.

2. Операции сравнения.

В Lua допустимы следующие операции сравнения величин:

== (равно);

~= (не равно);

< (меньше);

> (больше);

<= (меньше или равно);

>= (больше или равно).

Обратите внимание

Операции сравнения всегда возвращают логическое значение true или false.

Правила преобразования чисел в строки (и наоборот) при сравнениях не работают, т. е. выражение «0» == 0 даёт в результате false.

3. Логические операции.

К логическим операциям относятся:

and (логическое И).

Операция and возвращает свой первый операнд, если он имеет значение false или nil. В противном случае, операция возвращает второй операнд (причём этот операнд может быть произвольного типа).

a = (nil and 5) - a равно nil

a == (false and 5) - a равно false

a == (4 and 5) - a равно 5

or (логическое ИЛИ).

Операция or возвращает первый операнд, если он не false и не nil, иначе он возвращает второй операнд.

a == (4 or 5) - a равно 4

a == (false or 5) - a равно 5

Обратите внимание

Логические операции and и or могут возвращать значения любых типов.

Логические операции and и or вычисляют значение второго операнда только в том случае, если его нужно вернуть. Если этого не требуется, второй операнд не вычисляется. Например:

a == (4 or f()) - вызова функции f() не произойдет

not (логическое НЕ).

Операция not всегда возвращает true или false.

4. Операция конкатенации.

Для конкатенации (объединения) строк служит операция… (две точки).

a = «Кронос».."-"..«Информ» - переменная a получит значение «Кронос-Информ»

Обратите внимание

Если один или оба операнда являются числами, выполняется их преобразование в строки.

a = 0..1 - переменная a получит значение «01»

5. Операция получения длины.

В Lua определена операция длины #, которую можно использовать для получения длины строки.

a = «строка»

len = #a - len равно 6

len = #«ещё строка» - len равно 10

Обратите внимание

С помощью операции # можно также узнать максимальный индекс (или размер) массива. Подробнее - в статье «Работа с массивами в Lua» .

Приоритет операций в Lua

В языке Lua выполнение операций осуществляется в соответствии со следующим приоритетом (в порядке убывания):

2. not # - (унарный)

6. < > <= >= ~= ==

Вызов скриптов из форм

С каждой формой (включая вложенные формы) связан отдельный скрипт, который обычно содержит функции, выполняющие обработку событий формы и её элементов.

Когда форма запускается, её скрипт загружается в глобальное окружение. При возникновении события формы или её элемента система вызывает сопоставленную этому событию функцию-обработчик.

Необходимо отметить, что скрипт формы, хотя и не содержит вызова функции module, фактически является модулем. Это означает, что переменные, объявленные в скрипте формы без ключевого слова local, не выносятся в глобальное окружение и доступны только внутри этого скрипта. Если необходимо сделать какое-либо значение доступным для скриптов других форм, его следует явным образом определить в глобальной таблице _G:

local a = _G.var

Блоки операторов (инструкций)

К основным операторам Lua относятся:

присваивание;

условный оператор;

операторы для организации циклов.

Группа операторов может быть объединена в блок (составной оператор) при помощи конструкции do… end.

do - начало блока

<оператор1> - тело блока

<оператор2>

<операторN>

end - конец блока

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

a = 5 - глобальная переменная a

local a = 20 - внутри do-end определяется локальная переменная а

MsgBox(a) --> 20

MsgBox(a) --> 5 (здесь обращение уже к глобальной a)

Оператор присваивания в Lua

Присваивание изменяет значение переменной или поля таблицы. В простейшем виде присваивание может выглядеть так:

a = 1 - переменной a присвоено значение 1

a = b + c - переменной a присвоена сумма значений переменных b и с

a = f(x) - переменной a присвоено значение, возвращённое функцией f(x)

В Lua допускается так называемое множественное присваивание, когда несколько переменных, находящихся слева от оператора присваивания, получают значения нескольких выражений, записанных справа от оператора присваивания:

a, b = 1, 5*c - a равно 1; b равно 5*c

Если переменных больше чем значений, «лишним» переменным присваивается nil.

a, b, c = 1, 2 - a равно 1; b равно 2; c равно nil

Если значений больше чем переменных, «лишние» значения игнорируются.

a, b = 1, 2, 3 - a равно 1; b равно 2; значение 3 не использовано

Множественное присваивание можно использовать для обмена значениями между переменными:

a = 10; b = 20 - a равно 10, b равно 20

a, b = b, a - теперь a равно 20, b равно 10

Условный оператор (if) в Lua

Оператор if проверяет истинность заданного условия. Если условие является истинным, выполняется часть кода, следующая за ключевым словом then (секция then). В противном случае, выполняется код, следующий за ключевым словом else (секция else).

if a > b then

return a - если a больше b, вернуть a

return b - в противном случае - вернуть b

Секция else является необязательной.

if a < 0 then

a = 0 - если a меньше 0, присвоить a значение 0

Вместо вложенных операторов if можно использовать конструкцию elseif. Например, приведенный код:

будет проще для восприятия, если заменить его следующим:

return «Иван» - если a равно 1

elseif a == 2 then

return «Петр» - если a равно 2

elseif a == 3 then

return «Сергей» - если a равно 3

return «Нет такого игрока» - если a - ни одно из перечисленных

Цикл с предусловием (while) в Lua

Оператор while предназначен для организации циклов с предусловием и имеет следующий вид:

while do

… - тело цикла

Перед каждой итерацией цикла проверяется условие :

если условие ложно, цикл завершается и управление передаётся первому оператору, следующему за оператором while;

если условие истинно, выполняется тело цикла, после чего все действия повторяются.

while i > 0 do - цикл от 10 до 1

t[i] = «поле »..i

a = {3, 5, 8, -6, 5}

while i > 0 do - ищем в массиве отрицательное значение

if a[i] < 0 then break end - если найдено, прерываем цикл

i = i - 1 - иначе переходим к следующему элементу

if i > 0 then

MsgBox («Индекс отрицательного значения: »..i)

MsgBox («Массив не содержит отрицательных значений»)

Примечание

Цикл с постусловием (repeat) в Lua

Оператор repeat предназначен для организации циклов с постусловием и имеет следующий вид:

… - тело цикла

until

Тело цикла выполняется до тех пор, пока условие не станет истинным. Проверка условия осуществляется после выполнения тела цикла, поэтому в любом случае тело цикла выполнится хотя бы один раз.

Суммируем значения массива a, пока сумма не превысит 10

a = {3, 2, 5, 7, 9}

sum = sum + a[i]

until sum > 10

MsgBox («Сложено »..i.." элементов. Сумма равна "..sum)

Для выхода из цикла до его завершения можно использовать оператор break.

Примечание

Подробнее об особенностях использования оператора break - в статье «Операторы break и return»

Циклы с оператором for в Lua

Оператор for предназначен для организации циклов и допускает две формы записи:

простую (числовой for);

расширенную (универсальный for).

Простая форма оператора for

Простая форма оператора for имеет следующий вид:

for var = exp1, exp2, exp3 do

… - тело цикла

Тело цикла выполняется для каждого значения переменной цикла (счётчика) var в интервале от exp1 до exp2, с шагом exp3.

Примечание

Шаг может не задаваться. В этом случае он принимается равным 1.

for i = 1, 10 do - цикл от 1 до 10 с шагом 1

MsgBox («i равно »..i)

for i = 10, 1, -1 do - цикл от 10 до 1 с шагом -1

MsgBox («i равно »..i)

Обратите внимание

Выражения exp1, exp2 и exp3 вычисляются всего один раз, перед началом цикла. Так, в примере ниже, функция f(x) будет вызвана для вычисления верхнего предела цикла только один раз:

for i = 1, f(x) do - цикл от 1 до значения, возвращенного функцией f()

MsgBox («i равно »..i)

Переменная цикла является локальной для оператора цикла и по его окончании не определена.

for i = 1, 10 do - цикл от 1 до значения, возвращенного функцией f()

MsgBox («i равно »..i)

MsgBox («После выхода из цикла i равно »..i) - Неверно! i равно nil

Обратите внимание

Значение переменной цикла нельзя изменять внутри цикла: последствия такого изменения непредсказуемы.

Для выхода из цикла до его завершения используется оператор break.

a = {3, 5, 8, -6, 5}

for i = 1,#a do - ищем в массиве отрицательное значение

if a[i] < 0 then - если найдено...

index = i - сохраняем индекс найденного значения...

break - и прерываем цикл

MsgBox («Индекс отрицательного значения: »..index)

Примечание

Подробнее об особенностях использования оператора break - в статье «Операторы break и return»)

Логические операторы

Программирование — это набор определенных правил. Наш код будет следовать строго описанным инструкциям. Логические операции очень важны так как с их помощью код будет выполнять одни либо другие действия.

В языке присутствует несколько логических операций:

and (И) — логическая операция И позволяет выполнять сравнение нескольких значений и возвращает истину только если все значения выполняются.

or (ИЛИ) — логическая операция ИЛИ позволяет выполнять сравнение нескольких значений и возвращает истину если одно из значений выполняется.

not (НЕ) — логическая операция НЕ является операцией отрицания. Этот оператор всегда возвращает либо Истина либо Ложь , первым делом происходит определение истинное значение или ложное поступает на вход, а потом возвращается противоположное значение. Пример может быть таким.

Если условие истинное, тогда вернуть Ложь . Если условие ложное тогда вернуть Истину . Если на входе число, значит это Истина, следовательно вернется Ложь . Если на входе nil , значит это Ложь, следовательно вернется Истина .

Использование логических операций лишь только на первый взгляд может показаться сложным занятием, на самом деле всё намного проще, главное знать основы, что делают эти операции, что я только что я и расписал.

Теперь поговорим как применяются логические операции.

local a = 10; local b = 20; local c = a or b; message("Результат = " .. c);

В первых строках думаю всё понятно, обратите внимание на строку номер 5. Как видите мы объявили переменную с , а результат в неё помещаем либо значение переменной а , либо значение переменной b . В переменную c будет записано одно значение, а какое именно определяется следующим образом.

В переменную c записывается первое значение которое имеется. В данном примере переменная а содержит значение, значит это значение и будет записано в переменную c . Если в переменной а значение будет отсутствовать, значит в переменную c будет записано значение переменной b .

local a = nil; local b = 20; local c = 30; local d = a or b or c; message("Результат = " .. d);

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

А теперь рассмотрим пример с операцией and . Операция and возвращает первый параметр, если значение false или nil , иначе and возвращает второй параметр.

local a = 10; local b = 20; local c = a and b; message("Результат = " .. c);

В этом примере переменная c будет иметь значение 20, так как первый параметр является истиной.

Если переменная а будет равна nil , тогда в переменную c будет записано первое значение, то есть тоже nil . Если переменная а будет содержать значение 10, тогда неважно чему равна переменная b , в переменную c будет записано второе значение (если переменная b равна nil , значит c будет иметь значение nil , если значение переменной b 20, значит значение переменной c тоже будет 20).

local a = 10; local b = 20; local c = 30; local d = a and b and c; message("Результат = " .. d);

И снова немного расширенный пример чтобы было более-менее понятно что происходит в коде.

В этом примере переменная d будет иметь значение 30. Теперь разберемся как так получается.

Вначале написано a and b , так как переменная a имеет значение, значит возвращается вторая часть, далее написано and c , так как переменная b тоже имеет значение, то возвращается следующая часть, а значит в переменную d записывается значение переменной c . Если переменная а будет иметь значение nil , тогда в переменную d также попадает значение nil , в связи с тем что возвращается первая часть. Если же переменная b будет иметь значение nil , тогда в переменную d будет записано значение nil , так как при сравнении b и c возвращается первая часть, то есть значение переменной b .

Если переменная c будет иметь значение nil . Тогда в переменную d будет записано значение nil , это связано с тем что при сравнении b и c , значение b имеется и возвращается вторая часть, то есть значение переменной c , а оно у нас nil .

Из этих примеров можно сделать вывод. Применяя логическую операцию or , можно получить из множества значений первое, которое будет являться значением.

Представьте ситуацию, имеется 10 переменных, некоторые из них могут иметь значение, а некоторые нет. Вы хотите получить первое, которое будет иметь значение, в этом случае можно воспользоваться логической операции or . Неважно какая именно переменная будет иметь значение, главное что вы не получите на выходе nil , если хотя бы одна переменная имеет значение.

Логическая операция and наоборот может вернуть последнее значение, но только в том случае если все переданные переменные имеют значения. Если хотя бы одна переменная не имеет значения, то на выходе вы получите nil . Данную операцию очень удобно использовать если вы хотите получить результат только в том случае, если все данные имеют значения.

local a = 10; local b = 20; local c = 30; local d = a and b or c; message("Результат = " .. d);

Вот еще один пример, чтобы вы лучше усвоили материал. В этом случае переменная d будет иметь значение 20. Происходит это следующим образом. Так как переменная а имеет значение, значит возвращается вторая часть, а так как далее используется операция or , то и возвращается значение 20.

local a = nil; local b = 20; local c = 30; local d = a and b or c; message("Результат = " .. d);

А вот этот пример возвращает значение 30. Если вы сейчас вникните как именно получилось значение 30, значит вы полностью разобрались с темой, смотрите внимательно.

Разбираемся поочередно. В начале описано a and b , ты как переменная а не имеет значение, то возвращается первая часть, то есть возвращается nil . Теперь необходимо сравнить nil or c , так как используется операция or , значит возвращается переменная которая имеет значение, то есть переменная c . В итоге у нас в переменную d помещается значение 30.

Lua предлагает высокоуровневую абстракцию без потери связи с аппаратурой

В то время как интерпретируемые языки программирования, такие как Perl, Python, PHP и Ruby, пользуются все большей популярностью для Web-приложений (и уже давно предпочитаются для автоматизации задач по системному администрированию), компилируемые языки программирования, такие как C и C++, по-прежнему необходимы. Производительность компилируемых языков программирования остается несравнимой (она уступает только производительности ручного ассемблирования), поэтому некоторое программное обеспечение (включая операционные системы и драйверы устройств) может быть реализована эффективно только при использовании компилируемого кода. Действительно, всегда, когда программное и аппаратное обеспечение нужно плавно связать между собой, программисты инстинктивно приходят к компилятору C: C достаточно примитивен для доступа к "голому железу" (то есть, для использования особенностей какой-либо части аппаратного обеспечения) и, в то же время, достаточно выразителен для описания некоторых высокоуровневых программных конструкций, таких как структуры, циклы, именованные переменные и области видимости.

Однако языки сценариев тоже имеют четкие преимущества. Например, после успешного переноса интерпретатора языка на другую платформу подавляющее большинство написанных на этом языке сценариев работает на новой платформе без изменений, не имея зависимостей, таких как системные библиотеки функций (представьте множество DLL-файлов операционной системы Microsoft® Windows® или множество libcs на UNIX® и Linux®). Кроме того, языки сценариев обычно предлагают высокоуровневые программные конструкции и удобные операции, которые программистам нужны для повышения продуктивности и скорости разработки. Более того, программисты, использующие язык сценариев, могут работать быстрее, поскольку этапы компиляции и компоновки не нужны. В сравнении с С и его родственниками цикл "кодирование, компоновки, связывание, запуск" сокращается до ускоренного "написание, запуск".

Новшества в Lua

Как и любой язык сценариев, Lua имеет свои особенности:

  • Типы в Lua . В Lua значения имеют тип, но переменные типизируются динамически. Типы nil , boolean , number и string работают так, как вы могли бы ожидать.
    • Nil - это тип специального значения nil ; используется для представления отсутствия значения.
    • Boolean - это тип констант true и false (Nil тоже представляет значение false , а любое не nil значение представляет true).
    • Все числа в Lua имеют тип doubles (но вы можете легко создать код для реализации других числовых типов).
    • string - это неизменяемый массив для символов (следовательно, для добавления к строке вы должны сделать ее копию).
  • Типы table , function и thread являются ссылками. Каждый такой тип может быть назначен переменной, передаваемой в качестве аргумента, или возвращаемой из функции. Ниже приведен пример сохранения функции:

    Пример анонимной функции, -- возвращаемой как значение -- см. http://www.tecgraf.puc-rio.br/~lhf/ftp/doc/hopl.pdf function add(x) return function (y) return (x + y) end end f = add(2) print(type(f), f(10)) function 12

  • Потоки в Lua . Поток - это сопрограмма, создаваемая вызовом встроенной функции coroutine.create(f) , где f - это функция Lua. Потоки не запускаются при создании; они запускаются позже при помощи функции coroutine.resume(t) , где t - это поток. Каждая сопрограмма может время от времени отдавать процессор другим сопрограммам при помощи функции coroutine.yield() .
  • Выражения присваивания . Lua разрешает множественные присваивания, и выражения сначала вычисляются, а затем присваиваются. Например, результат выражений

    I = 3 a = {1, 3, 5, 7, 9} i, a[i], a, b = i+1, a, a[i] print (i, a, a, b, I)
    равен 4 7 5 nil nil . Если список переменных больше, чем список значений, лишним переменным присваивается значение nil ; поэтому b равно nil . Если значений больше, чем переменных, лишние значения просто игнорируются. В Lua названия переменных зависят от регистра символов, что объясняет, почему переменная I равна nil .

  • Порции (chunks) . Порцией называется любая последовательность Lua-операторов. Порция может быть записана в файл или в строку в Lua-программе. Каждая порция выполняется как тело анонимной функции. Следовательно, порция может определять локальные переменные и возвращать значения.
  • Дополнительные интересные возможности . Lua имеет сборщик мусора "отметь и выкинь". В Lua 5.1 сборщик мусора работает в инкрементном режиме. Lua имеет полное лексическое замыкание (как Scheme, но не как Python). Кроме того, Lua имеет надежную семантику последовательных вызовов (tail call) (опять же, как Scheme, но не как Python).

Большее количество примеров Lua-кода приведено в руководстве "Программирование в Lua " и в wiki Lua-пользователей (ссылки приведены в разделе " ").

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

Беря все лучшее из обоих миров

Что, если бы вы могли взять лучшее из обоих миров: производительность работы с "голым железом" и высокоуровневые, мощные абстракции? Более того, если бы вы могли оптимизировать алгоритмы и функции, зависящие от системы и требующие много процессорного времени, так же как и отдельную логику, не зависящую от системы и очень чувствительную к изменениям требований?

Баланс требований для высокопроизводительного кода и высокоуровневого программирования является сутью Lua, встраиваемого языка программирования. Приложения, включающие Lua, представляют собой комбинацию компилируемого кода и Lua-сценариев. Компилируемый код может при необходимости заняться железом, и, в то же время, может вызывать Lua-сценарии для обработки сложных данных. И поскольку Lua-сценарии отделены от компилируемого кода, вы можете изменять сценарии независимо от него. С Lua цикл разработки более похож на "Кодирование, компоновка, запуск, создание сценариев, создание сценариев, создание сценариев …".

Например, на странице "Uses" Web-сайта Lua (см. раздел " ") перечислены некоторые компьютерные игры для массового рынка, включая World of Warcraft и Defender (версия классической аркады для бытовых консолей), которые интегрируют Lua для запуска всего, начиная с пользовательского интерфейса и заканчивая искусственным интеллектом противника. Другие приложения Lua включают в себя механизмы расширения для популярного инструментального средства обновления Linux-приложений apt-rpm и механизмы управления чемпионатом Robocup 2000 "Сумасшедший Иван". На этой странице есть много хвалебных отзывов о маленьком размере и отличной производительности Lua.

Начало работы с 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 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 приведено в "" (см. раздел " ").

В Lua тип имеют только значения , а переменные типизируются динамически. В Lua есть восемь фундаментальных типов (или значений): nil , boolean , number , string , function , thread , table и userdata . Первые шесть типов говорят сами за себя (исключения приведены в разделе " "); два последних требуют пояснения.

Таблицы в Lua

Таблицы - это универсальная структура данных в Lua. Более того, таблицы - это единственная структура данных в Lua. Вы можете использовать таблицу как массив, словарь (называемый также хеш-таблицей или ассоциативным массивом ), дерево, запись и т.д.

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

Для исследования таблиц запустите Lua-интерпретатор и введите строки, показанные жирным шрифтом в листинге 1.

Листинг 1. Экспериментируя с таблицами Lua
$ lua > -- создать пустую таблицу и добавить несколько элементов > t1 = {} > t1 = "moustache" > t1 = 3 > t1["brothers"] = true > -- создать таблицу и определить элементы (употребляется чаще) > all at once > t2 = { = "groucho", = "chico", = "harpo"} > t3 = { = t2, accent = t2, horn = t2} > t4 = {} > t4 = "the marx brothers" > t5 = {characters = t2, marks = t3} > t6 = {["a night at the opera"] = "classic"} > -- создать ссылку и строку > i = t3 > s = "a night at the opera" > -- индексами могут быть любые Lua-значения > print(t1, t4, 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, t2, 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 с элементами {2, 4, 6, language="Lua", version="5", 8, 10, 12, web="www.lua.org"} и выполните команды table.foreach(t, print) и table.foreachi(t, print) .

Userdata

Поскольку 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({value = string or ""}, 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), заключает ее в таблицу ({value = s or ""}) и назначает мета-таблицу mt этой таблице. Функция mt.__add() является мета-методом, добавляющим строку b к строке, находящейся в a.value b раз. Строка print((s + " There " + " World!").value) активизирует мета-метод дважды.

Index - это еще одно событие. Мета-метод для __index вызывается всегда, когда ключ в таблице не существует. Вот пример, который запоминает ("memoizes") значение функции:

-- код, любезно предоставленный Рики Лэйком (Rici Lake), [email protected] function Memoize(func, t) return setmetatable(t or {}, {__index = function(t, k) local v = func(k); t[k] = v; return v; end }) end COLORS = {"red", "blue", "green", "yellow", "black"} color = Memoize(function(node) return COLORS end)

Поместите этот код в Lua-интерпретатор и введите print(color, color, color) . Вы должны увидеть что-то подобное blue black blue .

Этот код, получающий ключ и узел, ищет цвет узла. Если он не существует, код присваивает узлу новый, выбранный случайно цвет. В противном случае возвращается цвет, назначенный узлу. В первом случае мета-метод __index выполняется один раз для назначения цвета. В последнем случае выполняется простой и быстрый поиск в хеш-таблице.

Язык Lua предлагает много мощных функциональных возможностей, и все они хорошо документированы. Но всегда, когда вы столкнетесь с проблемами или захотите пообщаться с мастером, обратитесь за поддержкой к энтузиастам - IRC-канал Lua Users Chat Room (см. раздел " ").

Встроить и расширить

Кроме простого синтаксиса и мощной структуры таблиц, реальная мощь Lua очевидна при использовании его совместно с базовым языком. Как уже говорилось, Lua-сценарии могут расширить собственные возможности базового языка. Но справедливо также и обратное - базовый язык может одновременно расширять Lua. Например, C-функции могут вызывать Lua-функции и наоборот.

Сердцем симбиотического взаимодействия между Lua и его базовым языком является виртуальный стек . Виртуальный стек (как и реальный) является структурой данных "последний вошел - первый вышел" (last in-first out - LIFO), которая временно сохраняет аргументы функции и ее результаты. Для вызова из Lua базового языка (и наоборот) вызывающая сторона помещает значения в стек и вызывает целевую функцию; принимающая сторона достает аргументы из стека (конечно же, проверяя тип и значение каждого аргумента), обрабатывает данные и помещает в стек результаты. Когда управление возвращается вызывающей стороне, она извлекает значения из стека.

Фактически, все С-интерфейсы прикладного программирования (API) для Lua-операций работают через стек. Стек может хранить любое Lua-значение; однако тип значения должен быть известен как вызывающей стороне, так и вызываемой, а конкретные функции помещают в стек и извлекают из него каждый тип (например, lua_pushnil() и lua_pushnumber()).

В листинге 2 показана простая C-программа (взятая из главы 24 книги "Программирование в Lua ", ссылка на которую приведена в разделе " "), реализующая минимальный, но функциональный Lua-интерпретатор.

Листинг 2. Простой Lua-интерпретатор
1 #include 2 #include 3 #include 4 #include 5 6 int main (void) { 7 char buff; 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.

Lua великолепен

Lua - это чрезвычайно легкий в использовании язык, но его простой синтаксис маскирует его мощь: язык поддерживает объекты (аналогичные объектам Perl), мета-таблицы делают его тип table абсолютно гибким, а C API разрешает отличную интеграцию и расширение сценариев и базового языка. Lua может использоваться совместно с языками C, C++, C#, Java™ и Python.

Перед созданием еще одного формата конфигурационного файла или ресурса (и еще одного синтаксического анализатора для него) попробуйте Lua. Язык Lua (так же как и его сообщество) надежен, изобретателен и готов прийти на помощь.