Сайт о телевидении

Сайт о телевидении

» » Создание встраиваемых сценариев на языке Lua. Беря все лучшее из обоих миров. Вызов скриптов из форм

Создание встраиваемых сценариев на языке Lua. Беря все лучшее из обоих миров. Вызов скриптов из форм

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 (так же как и его сообщество) надежен, изобретателен и готов прийти на помощь.

=== Урок №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".

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

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

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

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.