Язык lua руководство

Маленькая книга о Lua

image

Карлу Сегуину — с благодарностью за «маленькую библиотеку».

Введение

Есть прекрасная книга «Программирование на Lua», написанная автором языка. Это настоящий учебник, освещающий все тонкости и особенности языка. Там даже упражнения есть. Будете ли вы её читать просто для ознакомления с еще одним языком программирования? Я думаю, что нет.

Другая крайность — это «Lua за 60 минут». Я видел подобные статьи и не могу однозначно сказать, приносят ли они пользу или причиняют вред. Для меня знакомство с Lua началось именно с этой статьи, но я понял из нее только то, что нуждаюсь в более глубоких источниках, чем прокомментированные сниппеты.

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

Читайте подряд. Последовательность изложения отличается от канонической, зато вам ни разу не встретится раздражающее «вы поймете это потом». Это быстрое чтиво, простое и легкое (надеюсь!), которое поможет понять, стоит ли переходить к более глубокому изучению или уже пора остановиться и больше не тратить время.

Применимость

Применимость — это ответ на вопрос «зачем». Существует совершенно безумное количество языков программирования и у каждого есть свой ответ на этот вопрос. Lua появился в недрах университетской кафедры компьютерной графики как встраиваемый язык для приложений на С.

Описывая Lua, автор называет его расширяемым (то есть умеющим вызывать функции приложения) и расширяющим (то есть умеющим предоставлять свои функции приложению). Будучи сам написан на C, он легко встраивается в C-приложения и взаимодействует с ним. Это совсем не означает, что единственная аудитория языка это программисты на C, но… да, им он полезней всего.

При этом Lua предлагает и самостоятельный интерпретатор, который может использовать внешние динамические библиотеки — как написанные специально для Lua, так и любые другие — с помощью механизма FFI. Во втором случае, правда, дополнительно потребуется библиотека expat или интерпретатор LuaJIT (написанный другим автором, но полностью совместимый с оригинальным Lua), в котором не только реализован FFI, но есть еще и JIT-компиляция, многократно ускоряющий и без того быстрый Lua.

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

Lua компактен — настолько, что его используют в маршрутизаторах Miktotik, телефонных станциях Asterisk и даже «зашивают» в микросхемы.

И он прост. Его включают в сетевые сканеры nmap и wireshark, он работает внутри баз данных Redis и Tarantool, на нем пишут плагины для медиаплеера Rhythmbox… даже биржевых ботов на платформе Quik. Это немного похоже на «бойцовский клуб» — многие используют Lua, ничего о нем не зная, просто как часть платформы.

Кроме того, на Lua неплохо получаются веб-приложения — благодаря реализованной в проекте OpenResty интеграции c Nginx получаются весьма выносливые к нагрузкам системы. Такие как AliExpress, например. Или CloudFlare.

С легкостью создаются настольные приложения с графическим интерфейсом — с помощью IUP, QT, wxWidgets, FLTK или чего-нибудь еще. Не только под Linux, но и под Windows, MacOS или вообще без графической среды, в «сыром» framebuffer.

Графика, «то, ради чего» Lua писался изначально, открывает дорогу в игровую индустрию. Love2D, специально написанный игровой движок, работает не только в настольных операционных системах, но и на мобильных устройствах. Unity, трехмерный игровой движок, лежит в основе довольно серьезных игровых проектов. Для игр класса AAA, правда, потребуется написать платформу на более «машинном» языке, но внутри MTA, World of Warcraft и S.T.A.L.K.E.R. используется все тот же Lua.

А вот задачи реального времени на Lua делать нельзя, даже если использовать микросхемы NodeMCU. Это интерпретатор для виртуальной машины и доступа к реальному оборудованию у него нет. Не поможет даже написать «подложку» с прямым доступом и управлять ею (в играх так и делается), это будет лишь приближением к желаемому — виртуальная машина асинхронно выполняет свои задачи и вы никогда не сможете выполнить что-то «прямо сейчас». Как бы ни была мала эта задержка, «взрослые» задачи реального времени не для Lua. Оставим это Erlang-у.

Установка

Можно собрать Lua из исходников с сайта lua.org. Это самый суровый путь, но если вы линуксоид, то вам не привыкать. При помощи MinGW, CygWin или Windows Platform SDK вы точно так же можете собрать его и для Windows. Но это необязательно — во многих дистрибутивах Linux бинарные сборки доступны из менеджера пакетов. Для Windows их тоже можно найти и скачать из Интернета. Ну, или установить при помощи Chocolatey — тамошнего аналога пакетного менеджера.

Запуск программ

Если просто запустить интерпретатор — он запустится в интерактивном режиме, где весьма неплох в качестве калькулятора.

$ lua
Lua 5.3.5  Copyright (C) 1994-2018 Lua.org, PUC-Rio
> 2 + 2 * 2
6

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

$ lua hello.lua
Hello, World!

Содержимое файла hello.lua — каноничная первая программа. Те, кто считает, что первая программа должна быть более осмысленной, упускают самое главное её предназначение — проверить работоспособность установленной среды.

print ("Hello, world!") -- print это функция вывода из стандартной библиотеки

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

#!/usr/bin/env lua
print "Hello, world!" -- для вывода одной строки можно обойтись без скобок

Сохраните эту программу в файл hello.lua, выполните команду chmod +x hello.lua, и вы сможете запускать её прямо из консоли:

$ ./hello.lua
Hello, world!

Комментарии

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

-- это короткий комментарий
--[[ длинные комментарии нужны не только для долгих "лирических отступлений",
     но и позволяют временно "отключить" часть кода, не удаляя его ]]

Переменные

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

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

Имена переменных могут содержать латинские буквы, цифры (но не могут с них начинаться) и знак подчеркивания. Нет никаких специальных правил — СamelCase, isHungarianNotation, underscore_delimited_names или любой другой способ могут быть использованы без ограничений и в любом сочетании.

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

Единственное пожелание автора — не использовать переменные, которые начинаются со знака подчеркивания, за которым следуют заглавные буквы. Внутренние переменные могут совпасть с вашими и программа может повести себя, скажем так, странно.

Выражения

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

4 = a -- это не работает; слева от знака операции только имена переменных

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

a = 4 -- теперь у нас есть переменная "a" и в ней содержится число 2
a, b = 4, 8 -- так тоже можно; это называется множественным присваиванием
a = 2 + 2 * 2-- значение можно записать как результат математической операции
a = b / 2 -- чтение переменных происходит при использовании их в выражениях
a, b = b, a -- простой и элегантный способ поменять значения переменных местами
i = i + 1 -- просто увеличение значения переменной на 1
x = a  = b -- теперь и X, и A равны 4

Когда в переменной пропадает нужда — она уничтожается сборщиком мусора. В общем, все как везде, где произносятся слова «автоматическая сборка мусора»

Пустое значение

Оно всего одно и записывается латинским словом nil, что переводится как «ничего». Это не нуль, не пустая строка и не нулевой указатель. Это ничего. Его не существует.

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

Логические значения

Их два — true («истина») и false («ложь»).

a, b = true, false -- их можно напрямую присваивать

-- они могут быть результатом логических операций
x = not b -- true; логическое НЕ
x = a and b -- false; логическое И
x = a or b -- true; логическое ИЛИ

--[[ можно выполнять эти операции и с другими типами данных,
     логика в этом случае несколько своеобразная, но она есть ]]

x = not nil -- true; nil аналогичен false
x = not 0 -- false; все остальные значения ведут себя как true, даже 0
x = 4 and 5 -- 5; and возвращает первый аргумент, если он ложный, иначе второй
x = 4 or 5 -- 4; or возвращает первый аргумент, если он НЕ ложный, иначе второй
x = 3 and 4 or 5 -- 4; это аналог тернарной операции "a?b:c" в Си

Числа

Для хранения чисел Lua использует 64-битные блоки памяти. Это аналогично типу double в С. В версии 5.3 появились целые числа, но это мало что меняет. Единственная разница — вы можете явно создать число с плавающей точкой, если используете десятичную точку, после которой может быть и нуль.

n = 42 -- ответ на Главный вопрос жизни, вселенной и всего такого
n = 42.0 -- это значение типа double, а не int
n = 0x2A -- он же в шестнадцатиричной системе счисления
n = 420e-1 -- в экспоненциальной форме
n = 0x2A0p-1 -- или даже в экспоненциальной форме шестнадцатиричного счисления
x = 3.1415926 -- у вещественных чисел дробная часть отделяется точкой
y = .5 -- нуль перед десятичным разделителем необязателен
z = -500 -- как и в случае с отрицательными значениями; фактически это "z = 0 - 500"

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

a = 2 + 2 -- 4; сложение
a = 2 - 2 -- 0; вычитание
a = 2 * 2 -- 4; умножение
a = 2 ^ 2 -- 4; возведение в степень
a = 5 / 2 -- 2.5; деление
a = 5 //2 -- 2; целочисленной деление (без дробной части)
a = 5 % 2 -- 1; остаток от целочисленного деления
a = 2 + 2 * 2 -- 6; приоритеты операций, как в школе
a =(2 + 2)* 2 -- 8; но его так же можно менять скобками

А также операции сравнения:

a, b = 3, 4

x = a  > b -- false; больше
x = a  < b -- true; меньше
x = a >= b -- false; больше или равно
x = a <= b -- true; меньше или равно
x = a == b -- false; равно
x = a ~= b -- true; не равно

Строки

Строка — это массив байт. Строка может содержать нулевые символы и это не станет признаком конца строки, символы могут быть в любой кодировке или вообще непечатными. В строках можно хранить любые двоичные данные. Между строками в 1 байт и строками в 10 мегабайт нет разницы. И нет ограничений, кроме размера оперативной памяти.

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

s = "Для записи строк используются кавычки"
s = 'одиночные кавычки тоже допустимы'
s = "можно комбинировать 'одиночные' и \"двойные\" кавычки в любом сочетании"
s = [[А если в тексте много строк,
никто не мешает хранить его в строке целиком.
Да, при помощи двойных квадратных скобок, как в многострочным комментарии.]]

с = #'Hello' -- 5; операция # позволяет узнать длину строки (количество байт, не букв!)
s = "строки"..'можно'..[[объединять]] -- это называется "конкатенация"

w = [[ 
Экранирование специальных символов делается как в Си:
\a - звонок (beep)
\b - возврат на одну позицию (backspace)
\r - перевод страницы
\n - перевод строки (newline)
\r - возврат каретки (carriage return)
\t - горизонтальная табуляция
\v - вертикальная табуляция
\\ - обратная косая черта (backslash)
\" - одиночная кавычка
\' - двойная кавычка
]]

-- строки можно сравнивать
x = s == w -- false, строки НЕ равны
y = s ~= w -- true,  строка НЕ равны

Привидение типов

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

a = 100..500 -- 100500
a = "10" + 7 -- 17
a = 12 + "a" -- ошибка

Это спорная идея. Лучше использовать явные преобразования и не рисковать.

a = tostring( 10 ) -- "10"; строка
b = tonumber("10") --  10 ; число
c = tonumber("XY") --  nil

Таблицы

Используйте таблицы, если вам нужны массивы, словари, структуры, объекты. Таблицы — это всё. Хотя, на самом деле, таблицы это просто способ хранить множество пар «ключ-значение», а все остальное — это то, какие вы выбираете ключи и какие значения.

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

a = {42,616,999}
a[#a + 1] = "Земля-616" -- функция # возвращает количество элементов
print (a[4]) --> Земля-616

А еще «натуральные» означает, что нумерация индексов начинается с единицы, а не с нуля. Программистам на C нужно всегда об этом помнить.

Массивы не надо объявлять и под них не выделяется пространство. И индексы у них необязательно последовательные. Можно использовать большие числа и не бояться, что закончится память — в таблице будет только те ключи, которые вы внесли. Если ключи в таблице не являются натуральной последовательностью, то это разряженный массив.

a = {}
a[13] = 666
a[100500] = "Alpha Centauri"
a[42] = "The Ultimate Question of Life, the Universe, and Everything answer"
print(#a) --> 0, для разряженных массивов операция # неприменима

Значения элементов таблицы сами могут быть таблицами. Ограничений на вложенность никаких, таблицы могут быть многомерными матрицами.

a = {
    {101,102,103},
    {201,202,203},
    {301,302,303}, -- эта запятая лишняя, но это допустимо
}
print (a[2][3]) --> 203

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

a = {
    ["one"] = 1,["two"] = 2,["three"] = 3, four = 4, five = "пять"
}
print (a.one) --> 1
print (a["four"]) --> 4

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

Фрагменты

У английского «chunk» есть масса смыслов. Если вы во имя точности предпочитаете англицизмы, используйте «чанк» — вполне сложившийся термин (в описании, например, HTTP-протокола). Если нет, то «фрагмент» ничем не хуже. Суть в том, что это просто какое-то количество инструкций языка без начала, конца и какого-то обрамления.

a = 1
print (a) --> 1
a = a + 1
b = a / 2
print (b) --> 1

В программе на Lua не нужно как-то специально оформлять «точку входа», как это делается в C функцией main(). Что касается точки выхода, то её тоже нет — выполнив все инструкции фрагмента, интерпретатор останавливается.

Вы, конечно, можете написать что-то вроде этого:

function main ()
    a = 1
    print (a) --> 1
    a = a + 1
    b = a / 2
    print (b) --> 1
end
main ()

…но особого смысла в этом нет. Да, сначала объявится функция, но её потом все равно надо будет явным образом вызвать после объявления. И интерпретатору совершенно без разницы, какое у нее имя и надо ли её вообще выполнять.

Полученный код выполняется «как есть», все остальное — на ваше усмотрение.

Блоки и области видимости

Несколько инструкций (тот самый «фрагмент») можно разместить между словами do и end. Это то же самое, что фигурные скобки в C/C++ или begin .. end в Pascal. Блок, группирующий инструкции, является отдельной исполнимой сущностью и применим везде, где применима единичная инструкция.

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

a = 1
do
    local a = 10
    print(a) --> 10
    b = 2
end
print(a) --> 1
print(b) --> 2

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

Управление потоком

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

А команды управляющие этим маршрутом называются инструкциями управления потоком. Что позволяет вложить в программу множество маршрутов, которые будут зависеть от разных факторов.

Безусловный переход

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

do
    a = 0
    ::loop:: -- имя метки подчиняется тем же правилам, что и имена переменных
        a = a + 1 -- это не блок, отступы просто для читаемости
        if a % 2 == 0 then goto continue end
        if a > 100 then goto exit end -- прыгать внутрь области видимости переменной нельзя
        print (a)
        ::continue::
    goto loop
    local b = -a
    ::exit:: 
    -- но если после метки до конца области видимости ничего нет, то можно
end

Условия

Программа не всегда должна делать одно и то же.

В зависимости от условий поток выполнения может разделяться на ветки, поэтому условное выполнение называется ветвлением. Каждая ветка может разделяться ещё на ветки и так далее.

if a > 0 then
    print ("'a' is positive") -- если выполняется уловие
end

if b > 0 then 
    print ("'b' is positive")
else
    print ("'b' is NOT positive") -- есть не выполняется
end

if c > 0 then
    print ("'c' is positive")
elseif c < 0 then -- это вместо switch-case, elseif-ов может быть много
    print ("'c' is negative")
else
    print ("'c' is zero")
end    

Циклы

Циклы нужны для многократного повторения одинаковых действий.

Цикл с предусловием проверяют условие повторения и выполняют блок, пока оно истинно. Циклы с постусловием сначала выполняют блок, а потом проверяют, достигнуто ли условие завершения.

while false do 
    print ('Hello?') 
end -- не выполнится ни разу

repeat 
    print ('yes, hello!') 
until true -- выполнится один раз

Цикл с выходом из середины не имеют граничных условий, поэтому в блоке необходимо явным образом предусмотреть выход. При помощи break можно «выпрыгнуть» из цикла, функции и вообще из любого блока. Нет continue, но его можно реализовать с помощью goto.

a = 0
while true do
    ::continue::
    a = a + 2
    if a == 13 then -- правильно выбирайте условие выхода из цикла!
        break 
    end
    if a % 10 == 0 then goto continue end -- пропускаем всё, кратное 10
    print (a)
end

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

for i = 0,9,1 do -- если шаг равен 1, третий параметр можно пропустить
    print(i)
end
print(i) --> nil; счетчик является локальной переменной и снаружи не доступен

Совместный цикл задает выполнение некоторой операции для объектов из множества.

x = {4, 8, 15, 16, 23, 42}
for k,v in ipairs(x)
    print ('code #'..k..' is '..v) 
end
print(k,v) --> nil    nil

Функции

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

Работает она примерно так:

x = {4, 8, 15, 16, 23, 42}
function values(t)
    local i = 0
    return function() 
        i = i + 1 
        return t[i] -- мы могли бы возвращать еще и i, но ipairs у нас уже есть
    end
end
for v in values(x) do -- обратите внимание, что здесь мы тоже обходимся без ключа
    print (v)
end

Этот коротенький пример сразу дает нам массу информации.

Во-первых, функции являются значениями первого класса. То есть их можно присваивать переменным, передавать параметром в другие функции и использовать в качестве возвращаемого значения.

sum = function (a,b)
    return a + b
end
print (sum (2,2)) --> 4

function mul (a, b) -- более привычный способ записи всего лишь "семантический сахар"
    return a*b
end
print (mul (2,2)) --> 4

Во-вторых, значение не знает имя переменной, в котором оно содержится. Таким образом все функции анонимны. Но не всегда…

function fact(n)
    if n == 0 then return 1 else return n*fact(n-1) end
end

В-третьих, возвращаемых значений может быть несколько. Не один, не два, а столько, сколько захочет вернуть функция. Если при этом мы используем множественное присваивание, то можем получить все значения. Если имен переменных меньше — то «лишние» значения будут отброшены. А если больше — то они получат nil.

Ну, и четвертых, функция является блоком со своей областью видимости для локальных переменных, имея доступ к переменным глобальным. А, будучи объявлена внутри другого блока, локальные переменные внешнего блока воспринимает как глобальные. Это и позволяет нам создавать замыкания — то есть функции, сохраняющие контекст. Весь контекст, без уточнений. Будьте благоразумны и не создавайте лишние сущности.

Кстати, кроме ipairsв стандартной библиотеке Lua есть еще pairs — более общий итератор, который позволяет работать с любыми таблицами, не только с массивами. Но в случае с массивами мы получим значения не по возрастанию ключей, а произвольно.

Сопрограммы

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

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

Не так важно, выполняются ли потоки разными физическими процессорами или разными потоками одного, переключаются ли они операционной системой, «главным» потоком или явно передают управление друг другу — главное, что каждый поток (строго говоря «нить», от английского «thread» — для англоманов «тред») имеет свое состояние.

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

c = coroutine.create(
  function (t)
    local i = coroutine.yield("initialized")
    repeat 
      i = coroutine.yield(t[i])
    until not t[i]
    return "finished"
  end
)
print (c,coroutine.status(c)) --> thread: 0x416e52a8    suspended
s = {2,3,5,7,11,13,17,19}
print (coroutine.resume(c,s)) --> true    initialized
print (coroutine.resume(c,1)) --> true    2
print (coroutine.resume(c,3)) --> true    5
print (coroutine.resume(c,9)) --> true    finished
print (c,coroutine.status(c)) --> thread: 0x416e52a8    dead
print (coroutine.resume(c,5)) --> false   cannot resume dead coroutine

В этом примере описан весь функционал сопрограмм. Давайте разберемся с ним, шаг за шагом.

Вызов corutine.create получает функцию и возвращает спящую («suspended») сопрограмму. Её можно «разбудить» вызовом coroutine.resume и тогда она начинает выполняться.

Выполняется она до тех пор, пока не встретиться вызов coroutine.yield. В этот момент управление возвращается к вызывающему потоку. Следующий вызов coroutine.resume восстановит выполнение сопрограммы, передавая управление ровно в то место, где она была приостановлена.

Сопрограмма может быть вечной — если она время от времени уступает выполнение, то в какой-то момент её можно просто не возобновить. Но она может и завершится и перейти в состояние «dead», возобновить из которого её будет уже невозможно.

Вызовы .resume и .yield могут не только передавать управление, но и обмениваться данными. В примере первый вызов .resume передает сопрограмме таблицу простых чисел, как если бы она передавалась функции в параметре, два других вызова передают данные внутрь сопрограммы, а она, в свою очередь, передает при помощи .yield обратно запрашиваемые значение из таблицы. При попытке получить значение за пределами таблицы сопрограмма заканчивается и возвращает сообщение при помощи return, как самая заурядная функция.

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

ООП, «которого нет»

Структура с методами это простейший вариант объекта. Метод это функция, сохраненная в элементе таблицы.

a = {
    name = "Nick",
    sayHello = function (t)
        print ("Hello, "..t.name)
    end
}
a.sayHello(a) --> Hello, Nick
a:sayHello() --> Hello, Nick

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

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

Есть несколько способов определять методы.

a = {
    name = "Nick",
    whoami = function (t) -- метод можно определить сразу в таблице
        return t.name
    end
}
-- а можно и отдельно от неё
function a.hello(t,name) -- здесь таблица передается в явном виде
    local me = name or t.whoami()
    print("Hello, "..me)
end

function a:bye(name)
    local me = name or self.name -- а здесь появляется "магическая" переменная self
    print("Goodbye, "..me)
end

-- способ вызова метода не зависит от того, как он был определен
a:hello() --> Hello, Nick
a.bye(a) --> Goodbye, Nick
a.hello(a,"John") --> Hello, John
a:bye("John") --> Goodbye, John
print(a:whoami()) --> Nick

Все это тоже семантический сахар.

Метатаблицы

Латинское «meta» буквально означает «после» в том смысле, в каком слово «метафизика» означает «не только физика». Метатаблица способна менять обычное поведение других таблиц.

a,b = {},{}
setmetatable (a,b) -- назначаем одну таблицу метатаблицей для другой
print (getmetatable (a) == b) --> true

b.x = 2

print (a.x) --> 1
print (getmetatable(a).x) --> 2

Что же нам это дает? В описанном примере — практические ничего. Вся сила метатаблиц в метаметодах.

Метаметоды

Это «магические» методы, которые позволяют менять существующее поведение таблицы. «Магические» они потому, что их логика зависит от того, как они называются.

t = {
    a = 42
}
print (t.a) --> 42
print (t.b) --> nil; стандартное поведение таблицы
-- создадим метатаблицу с измененной логикой
mt = {
    -- этот метод вызывается при чтении несуществующей переменной
    __index = function (t,k) 
        return k.." : ключ отсуствует"
    end
}
setmetatable (t,mt) -- задаем метатаблицу для нашей таблицы и повторяем те же действия
-- теперь таблица ведет себя иначе
print (t.a) --> 42
print (t.b) --> b: ключ отсуствует"

Если вы попытаетесь прочесть из таблицы значение по несуществующему ключу, вы получите nil. Но если у таблицы есть метатаблица, а в ней — метод __index, то будет вызван он.

t, mt = {}, {}
t.a.b = 42 -- ошибка: t.a равно nil, а не пустая таблица
-- определяем новую логику
function mt:__index(k)
    self[k] = {}
    setmetatable (self[k], mt)
    return self[k]
end
setmetatable (t,mt) -- и применяем её к нашей таблице
t.a.b.l.e = 42 -- больше никаких проблем с таблицами любой вложенности
print (t.a.b.l.e) --> 42

Наследование:

n = {name = "Nick"}
j = {name = "John"}
m = {
    __index = m, -- если присвоить этому методу таблицу, поиск ключа будет вестить в ней
    hello = function (t)
        print ("Hello, "..t.name)
    end
}
setmetatable(n,m)
setmetatable(j,m)
n:hello() --> ошибка: вы пытаетесь вызвать метод 'hello' (а он равен nil)

Внимание! Это красивый, но неправильный пример.

И дело тут не в том, что __index вместо функции является таблицей (для этого «магического» метода это допустимо). И не в том, что элемент таблицы ссылается на саму таблицу — это тоже нормально. Просто до завершения объявления таблицы она не существует и метод __index не к чему «привязывать».

А вот правильный вариант:

n = {name = "Nick"}
j = {name = "John"}
m = { -- сначала создаем метатаблицу
    hello = function (t)
        print ("Hello, "..t.name)
    end
}
m.__index = m -- потом назначаем её
setmetatable(n,m)
setmetatable(j,m)
n:hello() --> Hello, Nick
j:hello() --> Hello, John

Здесь мы затрагиваем одну интересную особенность. Пока элемент не определен — он недоступен. Возможно, стоило бы разрешить «ранее связывание», чтобы первый вариант тоже был рабочим. Возможно, это даже будет сделано в будущем, но это уж как решит профессор Иерусалемски — автор и «пожизненный великодушный диктор» Lua.

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

--[[ специально определяем функцию как переменную, 
     чтобы быть увереным, что тут нет никакого "скрытого сахара" ]]
fact = function (n)
    if n == 1 then
        return 1
    else
        return n * fact(n-1) -- формально определение функции еще не завершено
    end
end
print (fact(5)) --> 120; тем не менее, все работает (но только начиная с версии 5.3)

Не индексом единым…

m = {
    __index = function (t,k)
        print ("Чтение "..k)
        return t[k]
    end,
    __nexindex = function (t,k,v)
        print ("Запись "..k)
        t[k] = v
    end
}
a = {x = 12}
setmetatable (a,m)
print (a.x) --> 12
a.y = 1 --[[ операция уходит в бесконечный цикл и завершается, 
             когда интерпретатор это понимает]]

(простите, но это был еще один неправильный пример)

Вопреки ожиданиям, программа ведет себя не так, как ожидалось. Это потому, что при обращении к таблице сначала выполняется обращение к элементам, а потом (если их нет) идет поиск «альтернативной логики» в метатаблице (опять же, если она есть).

Если элементы существуют, интерпретатору нет необходимости обращаться к метаметодам. Но мы можем «вынудить» его это делать.

m = {
    __index = function (t,k)
        print ("Чтение "..k)
        return t.data[k]
    end,
    __newindex = function (t,k,v)
        print ("Запись "..k)
        t.data[k] = v
    end
}
a = {data = {}}

setmetatable (a,m)
a.x = 12    --> Запись x
print (a.x) --> Чтение x
            --> 12
a.y = 1     --> Запись y
print (a.z) --> Чтение z
            --> nil

Мы переносим значения таблицы в таблицу внутри таблицы. Этот прием позволяет перехватывать все операции чтения и записи в таблицу и менять их.

Или не позволять получать элементы, который мы посчитаем закрытыми, как в «классическом» ООП. Хотя на этот счет профессор высказался предельно ясно: «если вам нужны приватные свойства, просто не обращайтесь к ним«.

Арифметические метатметоды

  • __add: сложение (+)
  • __sub: вычитание (-)
  • __mul: умножение (*)
  • __div: деление (/)
  • __mod: остаток от деления (%)
  • __pow: возведение в степень (^)
  • __unm: унарный (одноместный) минус (-)
  • __idiv: целочисленное деление (//)

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

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

Логические метаметоды

  • __band: битовое И (&)
  • __bor: битовое ИЛИ (|)
  • __bxor: битовое ИЛИ-НЕ (~)
  • __bnot: битовое одноместное НЕ (~)
  • __shl: битовый сдвиг влево (<<)
  • __shr: битовый сдвиг вправо (>>)

Логика схожая.

Строковые метаметоды

  • __concat: конкатенация (..)
  • __len: длина (#)

Разница в том, что метаметоды будут использоваться, если аргументы не строки и не числа (которые можно привести к строкам). Если в таблице отсутствует реализация __len , то будет вызвана стандартная функция #, а она, как мы помним, не везде применима.

Метаметоды сравнения

  • __eq: равенство (==)
  • __lt: меньше (<)
  • __le: меньше или равно (<=)

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

Метаметоды доступа

  • __index: чтение из таблицы
  • __newindex: запись в таблицу нового значения
  • __call: обращение к таблице как к функции
  • __gc: выполняется перед уничтожением таблицы сборщиком мусора

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

Третий метод очень удобен в качестве «конструктора» — метода по умолчанию. Он получает в параметрах таблицу, за которой следуют все оригинальные аргументы вызова. Ну, а последний метод можно использовать в качестве «деструктора».

Перегрузка операций

Если при выполнении арифметической операции один из операндов является таблицей, то произойдет ошибка. Но с целой «колодой» метаметодов в рукаве мы можем это изменить.

Если один из операндов сложения является таблицей, интерпретатор будет искать метод __sum в его метатаблице. Если оба операнда отвечают этим требованиям — будет использован метаметод первого (левого) операнда.

odd  = {1,3,5,7,9} -- таблица нечетных чисел
even = {2,4,6,8} -- таблица четных чисел
set  = {
    __add = function (a,b)  -- передаются операдны операции сложения
        if type(b) ~= "table" then -- операнд может не быть таблицей
            a[#a + 1] = b -- тогда просто добавляем его ко множеству
        else -- в противном случае
            for _,v in pairs (b) do
                a[#a + 1] = v -- добавляем по одному все элементы этой таблицы
               end
           end
        return a
    end
}
setmetatable(odd,set) -- превращаем таблицы во "множества"
setmetatable(even,set)
even = even + 10 -- будьте осторожны, ситуацию "even = 10 + even" мы не предусмотрели
for _,v in pairs(odd + even) do
    print(v) -- сумма множеств представляем собой множество всех элементов подмножеств
end

Исходя их постулата «таблица это объект» можно реализовать перегрузку всех арифметических и логических операций, чтения и записи, вызова функции… но все это работает только для таблиц — мета-таблицы нельзя назначать никаким другим типам данных. Это сделано специально, чтобы не давать слишком много возможностей «выстрелить себе в ногу».

Стандартная библиотека

В Lua граница между языком и библиотеками очень четкая. Все, что не касается непосредственно языка, вынесено в стандартную библиотеку. Можно собрать Lua без нее и тогда ваша программа не сможет даже вывести на экран строку «привет, мир». Что является огромным преимуществом при использовании Lua внутри другого приложения в качестве встроенного языка, который должен уметь только то, что ему позволено и не оставит скрипту ни малейшего шанса «выбраться из песочницы».

Стандартный библиотек немного и функционал их не впечатляет. Причина в том, что во имя переносимости в стандартных библиотеках есть только то, что может быть реализовано на всех платформах, где есть ANSI C. Все остальные библиотеки так или иначе являются сторонними.

Модули

Принцип повторного использования кода подразумевает, что написанный функционал может быть использован повторно. Примем необязательно автором. Отчуждаемый код — это модули, пакеты, библиотеки, все то, благодаря чему нам не нужно каждый раз писать все самим. Девиз «все уже украдено написано до нас» особенно близок адептам Python-а, но не чужд и нам.

local lunajson = require 'lunajson' -- теперь мы умеет работать с JSON

local jsonstr = '{"Hello":["lunajson",1.5]}'
local t = lunajson.decode(jsonstr)
print(t.Hello[2]) --> 1.5
print(lunajson.encode(t)) --> {"Hello":["lunajson",1.5]}

Разумеется, не все так просто. Для того, чтобы использовать пакет, он должен присутствовать в одном из мест, где его будет искать require.

В переменной package.path содержится строка с вариантами шаблона путей для поиска. Вы можете увидеть из при помощи команды print.

> print (package.path)
/usr/share/lua/5.3/?.lua;/usr/share/lua/5.3/?/init.lua;/usr/lib/lua/5.3/?.lua;/usr/lib/lua/5.3/?/init.lua;./?.lua;./?/init.lua

Подставьте вместо вопросительного знака имя модуля — и у вас будет список всех возможных файлов, который будет пытаться открыть команда require.

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

Проще всего разместить подключаемый модуль в той же директории, что и ваша программа. Создайте файл с именем module.lua и впишите в него следующее:

local complex = {
    __add = function (x, y)
        return {a = x.a + y.a, b = x.b + y.b}
    end,
    __tostring = function (z) 
        -- этот метаметод вызывается, когда с таблицей пытаются обращаться, как со строкой
        return z.a..'+'..z.b..'i'
    end
}
return function (a, b)
    z = {a = a, b = b}
    setmetatable (z, {
        __index = complex
    })
    return z
end

Теперь в своей программе вы можете написать так:

local complex = require "complex"

x = complex(3,2)
y = complex(4,5)
print (x + y) -- вот здесь перед выводом выполняется метатметод __tostring

Пакеты

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

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

А если вы снова внимательно посмотрите на значение переменной package.path, то увидите там такую подстроку:

Это позволяет загружать пакет, в модуле init.lua которого будет выполняться загрузка его компонентов или другие действия инициализации.

Заключение

Теперь вы знаете, что такое Lua. Теперь переходите к «Программирование на Lua», той самой книге, о которой мы говорили в самом начале. Если вы решили включить Lua в свой инструментальный набор, эта книга станет для вас настольной. В том числе и потому, что в ней автор рассказывает, что и почему в Lua реализовано именно так, а не иначе.

А еще — там есть такой громадный пласт материала, как встраивание в приложение на C и взаимодействие к кодом, написанным на C. Я посчитал, что для «маленькой книги» это слишком глубокий материал.

Мы также обошли стороной luarocks — это пакетный менеджер, который Hisham Muhammad написал и поддерживает с 2006 года. Это не единственный менеджер пакетов, но для Lua он стал стандартом «де-факто». Строго говоря, он не является частью языка, поэтому я не стал его описывать. Так же, как ZeroBrane Studio, которая является великолепной IDE и удачным примером приложения, полностью написанного на Lua.

Засим откланиваюсь.
Спасибо, что дочитали.

Definitive Guide to Lua for Beginners

Lately, several international companies are offering a vacancy on Stack Overflow, remote or in person, for C++ programmers. br/cpp) that have knowledge of the Lua programming language, in addition to various software and Games are adopting Lua very quickly. So, no let those opportunities slip away!

Here’s everything for you to start in this world of the Lua programming language!

Come on!

Note: This tutorial is suitable for anyone who basically knows any programming language.


Introduction

Lua is a high-level, dynamically typed, multiparadigm, reflective, lightweight, interpreted programming language designed by Tecgraf at PUC-Rio in 1993 to expand general applications in an extensible manner (which joins parts of a program made in more than one language), for prototyping and to be embedded in complex software such as games. It resembles Python, Ruby and Icon, among others.

Lua was created by a team of Tecgraf developers from PUC-Rio, at first, to be used in a Petrobras project. Due to its efficiency, clarity and ease of learning, it started to be used in several branches of programming, such as in game development (Blizzard Entertainment, for example, used the language in the World of Warcraft game), robot control, computer processing. text, etc. It is also often used as a general purpose language.

Lua combines procedural programming with powerful data description constructs based on associative tables and extensible semantics. It is dynamically typed, interpreted from bytecodes, and has automatic memory management with garbage collection. These characteristics make Lua an ideal language for configuration, automation (scripting) and rapid prototyping.

Who uses Lua?

  • In 2013, the Wikimedia Foundation started using the language in templates.
  • Neovim — It has native support for Lua. One of the reasons for doing this is that VimScript is a slow interpreter language, with almost no optimizations. Much of the time spent starting vim and plug-in actions that can block the main loop in the editor is parsing and running vimscript.

And also:

  • Adobe Photoshop Lightroom
  • Celestia
  • Cheat Engine
  • ClanLib
  • CryEngine 3
  • Corona SDK
  • Damn Small Linux
  • Ginga
  • Kepler (software)
  • lighttpd
  • Liquid Feedback
  • MinGW
  • Monotone
  • Nmap
  • PlayStation Home

Games that also use Lua:

Examples of companies that developed games using the Lua language: LucasArts, Croteam, BioWare, Microsoft, Relic Entertainment, Absolute Studios, Monkeystone Games, Blizzard, SNKPlaymore, Facepunch Studios, KOG.

  • Angry Birds
  • Baldur’s Gate
  • The Battle for Wesnoth
  • Civilization V
  • Counter-Strike Online / Counter-Strike Nexon: Zombies
  • Escape from Monkey Island
  • Fable II
  • Far cry
  • FlyFF
  • Freeciv
  • Freelancer
  • Garry’s Mod
  • Grand Chase
  • Grim Fandango
  • Impossible Creatures
  • Lego Universe
  • MapleStory
  • MDK2
  • Monopoly Tycoon
  • Multi Theft Auto

Story

Lua was created in 1993 by Roberto Ierusalimschy, Luiz Henrique de Figueiredo and Waldemar Celes, members of the Computer Graphics Technology Group at PUC-Rio, the Pontifical Catholic University of Rio de Janeiro, in Brazil.[5] Versions of Lua before version 5.0 were released under a license similar to the BSD license. As of version 5.0, Lua has been licensed under the MIT license.

Some of its closest relatives are Icon, for its design, and Python, for its ease of use by non-programmers. In an article published in Dr. Dobb’s Journal, the creators of Lua also claim that Lisp and Scheme were a big influence in the decision to develop the table as Lua’s main data structure. Lua has been used in various applications, both commercial and non-commercial.

The first project using the language in games was in 1997 when LucasArts used it as a scripting language in the game Grim Fandango.[3]

In 2008 one of Lua’s most famous engines was born (Corona SDK)

In 2009 Love2D was born, a 2D game engine.

Characteristics

Lua is usually described as a multi-paradigm language, offering a small set of general features that can be extended to fit different types of problems, rather than providing a more complex and rigid specification to match a single paradigm. Lua, for example, does not contain explicit support for inheritance, but it does allow it to be performed relatively easily with metatables. Likewise, Lua allows programmers, when implementing names, classes, and other functions, to employ powerful functional programming techniques and complete lexical scopes.

Lua is a language that only supports a small number of structures, such as atomic data, boolean values, numbers (double-precision floating point by default), and strings. Common data structures such as arrays, sets, tables, lists, and records can be represented using Lua. Lua was not built with support for object-oriented programming.

Via: https://wikipedia.org/wiki/Lua_(programming_language)


Preparing the Environment

To install Lua just use your distribution’s package manager, for example:

  • In Gentoo, Funtoo and derivatives: emerge lua
  • On Debian, Ubuntu, Mint and derivatives: sudo apt install lua or if it doesn’t work, enter the version, example: sudo apt install lua51

You can write in Lua in any IDE, however, the most recommended one is Neovim which includes we’ll see how to create a plugin for Neovim using Lua that has native integration.

After installing run: lua --help the output will be:

lua --help
usage: lua [options] [script [args]].
Available options are:
  -e stat execute string 'stat'
  -l name require library 'name'
  -i enter interactive mode after executing 'script'
  -v show version information
  -- stop handling options
  - run stdin and stop handling options

Examples:

Running only lua command enters a subshell, run os.exit() inside the subshell to exit.

Parameter Usage
-e lua -e 'print("Hello, Lua!")'
-i lua -i script.lua
-v lua -v
Version
Lua 5.1.5 Copyright (C) 1994-2012 Lua.org, PUC-Rio

Hello World!

The most basic program.

nvim hello-world.lua

print("Hello Lua!")

And run: lua hello-world.lua or:

#!/usr/bin/env lua
print("Hello Lua!")

chmod +x hello-world.lua and run: ./hello-world.lua . You could still replace the header with which lua and add, for example: #!/usr/bin/lua

Other ways to print:

No parentheses

print "Hello, no parentheses, Lua!"

Multiple lines

print [[
it can be seen
in several lines
 including the initial spaces
]]

Or also with parentheses: print([[ ]])


Single-line comments use two dashes (–) can be:

-- up here
print "Luaaaaa" -- here on the side
-- here below

Or in multiple lines

print "Starting the code"
--[[ Comment
in
multiple
lines
]]
print "Finishing the code"

Detail: the -- must be pasted from the [[ at the beginning.


Variables

There are three types of variables in Lua: global variables, local variables and table fields. Every variable is assumed to be a global variable unless it is explicitly declared as a local variable.

name = "Marcos Oliveira"
print(name)
print("The name value is:", name) -- output spaced with TAB

Creating a local variable:

version location = _VERSION
print(version)

Table fields:

my_table = {
  x = 10,
  y = 20
}

print( my_table.x )
print( my_table.y )

Creating an empty table and adding later:

table = {}
table[1] = "Lua"
print(table[1])

Concatenation

To concatenate strings in Lua you need to use the dot twice: .. , example:

hello = "hello"
world = "world"
print( hello .. ", " .. world .. "!" )

Output: Hello World! .

We can declare multiple variables and print it like this:

first name, last name = "Marcos","Olivera"
print(first name .. " " .. last name)

Constants

Lua before version 5.4 didn’t support constants automatically, but you can add a table and make the table read-only using metatable (we’ll see this later). But from 5.4 on, use it like this:

location my_var <const> = 42
print( my_var )

Run: lua5.4 constants.lua

If trying to reset, example:

my_var = 24

You will get the error: lua5.4: sandbox.lua:3: attempt to assign to const variable 'my_var'


Arrays

Arrays are ordered arrays of objects, which can be a one-dimensional array containing a collection of rows or a multidimensional array containing several rows and columns.

In Lua, arrays are implemented using index tables with integers. The size of an array is not fixed and can grow based on our requirements, subject to memory constraints.

In Lua the array/matrix starts from 1, if you try to print 0(zero) the output will be nil

One-dimensional array

A one-dimensional array can be represented using a simple table structure and can be initialized as shown below.

my_array = {"Lua", "C++", "JavaScript", "C", "Bash", "Rust", "PHP"}
print( my_array[1]) -- Lua

Data Types

Data typing in Lua is dynamic, but if you want to get the data type, just, for example:

print( type("What is the type of this data?") ) -- string
print( type(936) ) -- number
print( type( true ) ) -- boolean
print( type( print ) ) -- function
print( type( os ) ) -- table
print( type( nil ) ) -- nil

name = "Marcos Oliveira"
print( type( type ) ) -- function
print( type( 9.36 ) ) -- num
print( type( name ) ) -- string

Besides that there are also types: userdata and thread .


Operators

Lua uses almost all the operators that other programming languages ​​use, examples:

operator operator operator operator
+ - * /
% ^ == ~=
> < >= <=
and or not #

In addition to the .. we’ve already seen that serves for concatenation.

Examples:

-- sum
print("2 + 2:")
print(2 + 2) -- 4

-- subtraction
print("2 - 2:")
print(2 - 2) -- 0

-- multiplication
print("2 * 2:")
print(2 * 2) -- 4

-- division
print("2 / 2:")
print(2 / 2) -- 1

-- rest of division (module)
print("2 % 2:")
print(2 % 2) -- 0

The others will be seen later!


Conditions

Like other programming languages, Lua also has conditions, of course (the most used resource in any programming language).

Note the use of the words: then and end

To use the if, example:

number = 10

if( number == 10 ) then
  print("The number is equal to TEN")
end

To use else

number = 8

if( number == 10 ) then
  print("The number is equal to TEN")
else
  print("The number is different from TEN")
end

To use elseif

number = 8

if( number == 10 ) then
  print("The number is equal to TEN")
elseif( number == 8 ) then
  print("The number is EIGHT")
else
  print("The number is DIFFERENT from TEN and EIGHT")
end

Loops

To perform an action at certain times we use loops, in Lua there are 3 types of loops: while, for and repeat until, let’s see how to use them:

Note the use of reserved words: do and end

while loop

number = 1

while( number < 4 ) do
  print("Printing " .. number .. "th time!")
  number = number + 1
end

for loop

Miscellaneous examples.

-- increasing value
for i = 1,4 of the
 print("Printing "..i.."th time!")
end
-- decreasing value
for i = 4,1,-1 of the
 print("Printing "..i.."th time!")
end

repeat until loop

number = 4 -- starts from number 4
repeat
   print("The value of 'number' is: "..number)
   number = number + 1
until( number > 8 ) -- until number 8

Using break

number = 4 -- starts from number 4
repeat
  print("The value of 'number' is: "..number)
  if( number == 6 ) then -- if it is equal to 6 stop the loop
    break
  end
  number = number + 1
until( number > 8 ) -- until number 8

Functions

Functions are important for code reuse, let’s see some ways to use them in Lua.

Basic function:

function hello_world()
  print("Hello Lua via function!")
end

hello_world() -- call the function

Passing parameters:

function sum(x,y)
  print(x.." + "..y.." = "..x + y)
end

sum(3,9)

Using signature:

output = function(flag)
  print("The result is: "..flag)
end

function sum(x, y, callback)
  result = x + y
  callback(result)
end

sum(3, 9, output)

Function with variable argument:

Note the use of the native ipairs function which is an iterator (we’ll see more later)

function qtd_params(...)
  location qtd = 0;
  parameters = {...}
  for i,v in ipairs(parameters) do
    qtd = qtd + 1
  end
  return qtd
end

print([[The number of parameters passed
for the function qtd_params(...) is: ]] .. qtd_params(8,4,11,2,17))

In this case we pass 5 parameters to the function.

Printing all parameters passed with variable argument:

function list_params(...)
  location qtd = 0;
  parameters = {...}
  for i,v in ipairs(parameters) do
    qtd = qtd + 1
  end

  for j = 1.qtd of
    print("The "..j.."th parameter is: "..parameters[j])
  end
end

list_params("Mark", "Olive", "Terminal", "Root")

Working with strings

  • Another way of printing:
str = [[Lua is really cool!]]
print(str)
  • Printing colors:
str = "Lua is really cool!"
yellow = "\027[33m"
off = "\027[m"
print(yellow..str..off)
  • Printing all colors:
str = "Lua is really cool!"

-- from 30 to 37 (8 colors)
-- but there's more: try 0 to 106
for i = 30.37 of the
  if( i == 30) then
    -- if your terminal is the background color
    -- the background will not appear
    print("\027[90m"..str.."\027[m")
  else
    print("\027["..i.."m"..str.."\027[m")
  end
end
  • Converting to UPPER or lower case

    For UPPERCASE

str = "Lua is really cool!"
print( string.upper( str ) )

Note that accented words do not change. We’ll see how to change this when we talk about including libraries.

To lowercase

str = "Lua is really cool!"
print( string.lower( str ) )
  • Replacing parts of a string
str = "Perl is really cool!"
str = string.gsub(str,"Perl","Lua")
print(str)
  • Formatting string:
    • in the middle of the sentence
str = "❤"
print( string.format("I %s Lua!",str))
  • Formatting date
day, month, year = 26.08.2021
print( string.format("%d/%02d/%d",day,month,year))

For month with zero: %02d.

  • Print float with 4 significant digits:
print( string.format("%.4f",3.1415))
  • Finding a string:
str = "The Lua programming language is really cool"

if( string.find(str,"a lot") ) then
  print("Found the word 'very'")
else
  print("The word 'much' was not found")
end

if( string.find(str,"bye") ) then
  print("Found the word 'bye'")
else
  print("NO word found 'bye'")
end
  • Reversing a string:
str = "Wow! The Lua!"
print(string.reverse(str))
  • Getting the length of a string:
str = "Lua"
print(string.len(str))
  • Subdividing a string

    string.sub(STRING, POS_INI, POS_FINAL)

    • Prints only: Lua
str = "Lua Programming Language"
print( string.sub(str,0,3) )
  • Prints only: Programming
str = "Lua Programming Language"
print( string.sub(str, string.len(str) - 12, string.len(str) ) )

Or

str = "Lua Programming Language"
print( string.sub(str, 18, 30 ) )
  • Repeat string n times

    For this example: 4 times

str = "Lua"
print(string.rep(str,4))
  • Printing a letter in byte format (ASCII table number):
    • Getting number by letter
print( string.byte("Lua")) -- first character
print( string.byte("Lua",2)) -- second character
print( string.byte("Lua",-1)) -- last character
print( string.byte("Lua",-2)) -- penultimate character
  • Getting the letter by number
print( string.char(97)) -- first character

Working with Tables

Tables are the only data structure available in Lua that help us create different types, such as arrays and dictionaries. Lua uses associative arrays and can be indexed not only with numbers but also with strings, except nil. Tables have no fixed size and can grow as per our need.

Lua uses tables in all representations, including the package representation. When we access a string.format method, it means that we are accessing the formatting function available in the string package.

Tables are called objects and are not values or variables. Lua uses a constructor expression {} to create an empty table. It should be noted that there is no fixed relationship between a variable that contains the table reference and the table itself.

Read the comments in the code for greater understanding:

table = {"Lua", "C++", "JavaScript", "C"}

-- prints everything together
print("Printed the table: " .. table.concat(table))

-- prints with spacing
print("Printed the table: " .. table.concat(table," "))

-- prints from second to third element
print("Printed the table: " .. table.concat(table," ",2,3))

-- removing the last index from the table:
table.remove(table)
print( table.concat( table,", " ) )

-- removing the 3rd index (JavaScript) from the table:
table.remove(table, 3)
print( table.concat( table,", " ) )

-- inserts an index(Rust) in the last position
table.insert(table, "Rust" )
print( table.concat( table," " ) )

-- inserts an index (PHP) in the 1st position
table.insert(table, 1, "PHP" )
print( table.concat( table," " ) )

-- informs the number of indexes in the table
print("The 'table' has: "..table.maxn(table) .. " indexes")

There are also the functions: sort, foreach, foreachi and getn. Find out more here.


Modules

The module is like a library that can be loaded using require and has a unique global name containing the code to be used.

Example: Using a function that is in the my_module.lua file of the program.lua program:

my_module.lua

function my_function( param )
  print("You passed the parameter: " .. param)
end

lua program

require "my_module"

my_function("Hello module!")

Another way to use and instantiate the module:

my_module.lua

location mf = {}

function mf.my_function( param )
  print("You passed the parameter: " .. param)
end

return mp

lua program

mf = require ("my_module")
mf.minha_funcao("Lua is awesome!")

Working with Shell

Let’s use the os native library

  • Getting environment variables
home = os.getenv("HOME")
pwd = os.getenv("PWD")
editor = os.getenv("EDITOR")

print("Your home directory is: " .. home)
print("You are in the directory: " ..pwd)
print("Your default editor is: " .. editor)
  • Executing commands
os.execute("echo 'Hello Shell via Lua!'")
os.execute("uptime")
os.execute("touch file.txt")
os.execute("ls")
  • Removing files:
os.remove("file.txt")

Learn more at: https://www.lua.org/pil/22.1.html


Passing Parameters via the command line

To do this, use the reserved word: arg. See the examples below:

print( "The filename is: " .. arg[0])

print([[Number of arguments
via the command line: ]] .. #arg)

if( #arg > 0 ) then
  for i = 1,#arg do
    print("Argument "..i.." is "..arg[i])
  end
end

print("The command is: " .. arg[-1])

A metatable is a table that helps modify the behavior of a table to which it is attached with the help of a set of related meta keys and methods. These metamethods are powerful Lua features that enable features like —

  • Change/add functionalities to operators at tables.
  • Searching meta-tables when key is not available in table using __index in meta-table.

There are two important methods that are used in handling metatables, which include —

  • setmetatable(table, metatable) — This method is used to set the metatable to a table.
  • getmetatable (table) — This method is used to get the metatable of a table.

Let’s first look at how to define one table as a metatable of another. It is shown below.

my_table = {}
my_meta_table = {}
setmetatable( my_table , my_meta_table )

Or just:

my_table = setmetatable({}, {})

Using __index

A simple example of a metatable to search the metatable when it is not available in the table is shown below.

my_table = setmetatable({key1 = "value1"}, {
   __index = function(my_table, key)

      if key == "key2" then
         return "Metatable value"
      else
         return my_table[key]
      end
   end
})

print(my_table.key1,my_table.key2)

Output: value1 MetatableValue

Summarizing the code above:

my_table = setmetatable({key1 = "value1"},
   { __index = { key2 = "META TABLE VALUE" } })
print(my_table.key1,my_table.key2)

If an index doesn’t exist and you want to add it when it’s called and it doesn’t exist you can use __newindex and create a method/function for that.

More information here.


LuaJIT

LuaJIT is a Just-In-Time (JIT) Compiler for the Lua programming language. Its official website is: https://luajit.org/ .

JIT is a technology that compiles a program at runtime.

The LuaJIT implementation seems to outperform all other dynamic JIT languages ​​(V8, Tracemonkey, PLT Scheme, Erlang HIPE) by an order of magnitude.

To install LuaJIT use your system’s package manager, examples:

sudo emerge luajit # Gentoo, Funtoo and similar
sudo apt install luajit # Debian, Ubuntu and similar
sudo pacman -S luajit # Arch, Manjaro and similar
sudo dnf install luajit # Fedora, Red Hat and similar
# ...

To use it, instead of using the lua command, use luajit, example:

luajit myprogram.lua

LuaJIT provides superior performance to the Lua interpreter.

As with any high-performance system, the answer in the end comes down to two things: algorithms and engineering. LuaJIT uses advanced compilation techniques and also has a very precise engineering implementation. For example, when sophisticated compilation techniques cannot handle a piece of code, LuaJIT resorts to a very fast interpreter written in x86 assembly.

LuaJIT earns double points in the engineering aspect, because not only is LuaJIT itself well designed, but the Lua language itself has a simpler and more coherent design than Python and JavaScript. This makes it (marginally) easier for an implementation to deliver consistently good performance.


LuaRocks

LuaRocks is a package manager for the Lua programming language, similar to package managers for other languages ​​such as: npm(JavaScript), composer(PHP), gem(Ruby), pip(Python) and among others.

Its official website is: https://luarocks.org/

Provides a standard format for distributing Lua modules (in a standalone format called “rock”), a tool designed to easily manage the installation of rocks, and a server to distribute them. Although not included in the Lua distribution, it is called a “de facto package manager for community-contributed Lua modules”.

To install it also use your system’s package manager, examples:

sudo emerge luarocks # Gentoo, Funtoo and similar
sudo apt install luarocks # Debian, Ubuntu and similar
sudo pacman -S luarocks # Arch, Manjaro and similar
sudo dnf install luarocks # Fedora, Red Hat and similar
# ...

To use it use the command luarocks --help .



lua

programming



This help page is meant to provide some basic orientation for those new to Lua/Scribunto.

Overview[edit]

Lua is a programming language implemented on Wikipedia with some substantial restrictions via Scribunto. Its purpose is to allow you to process the data which is available on Wikipedia content pages to allow various sorts of customized display of information.

The most important help file is the MediaWiki Scribunto Lua reference manual, which provides a concise summary of the language and standard library calls as implemented on MediaWiki.

The general, non-MediaWiki Lua reference manual can—while being very well written, comprehensive, and informative—be problematic for beginners, because certain features don’t work in Wikipedia—beginning with print(), which appears in standard Lua «hello world» programs.

Issues with the current implementation[edit]

Besides the lack of print(), there are other features missing – see Differences from standard Lua for a complete list.

At the present time, it is advisable to use mw.ustring functions instead of string, because the latter sometimes fails with Unicode characters.

Input[edit]

The programs are run only when the page is «parsed» (when it or a page it incorporates is changed or previewed), not every time you view the output. Therefore there can be no convenient Lua module that allows you to type in a Fahrenheit temperature in an input box and get back the corresponding Celsius temperature when you press a button, or allows you to click on a segment of a Mandelbrot set visualization on a page to expand it as often as you like. There has to be an actual Wiki page (or at least a page you have submitted for preview) containing the input data.

However, it is possible to use library functions like mw.title.new to import content from any text content page on the Wiki. You cannot, however, import data from files, not even .svg files which contain XML text data.

Calling a Lua module[edit]

Lua calls look much like templates, and consist of a small block of text like

{{#invoke:ConvertNumeric|decToHex|73}}, giving 49

This text calls the Lua script itself, which is housed in the Module: namespace. The effect of this call is to send the information within the #invoke block to the Lua module, and to replace everything within the brackets with a piece of text that it sends back in return. (Literally, in the «return» statement)

Note that the first «parameter», in this case decToHex, is actually a function called within the Lua module. This field must always be included in any #invoke. To those unfamiliar with modules, especially Wikipedia template coders who expect anything after | to be a parameter, the need for this extra field is surprising, especially if all uses of the module depend on its presence. When documenting your work for them it is useful to include an explicit usage instruction like {{mlx|ConvertNumeric|decToHex|73}} so that they understand not to omit it.

For many existing modules, an example #invoke of the script (and little else) is provided on the Module talk: page. It is convenient for authors to be able to flip quickly to the talk tab to look at the effects of their changes, but you should never transclude the talk page as a template — people might actually talk on it! Alternatively, the module page can show documentation from a separate /doc-page (as Module:WikidataIB does).

Another example: Using LuaCall to perform a single Lua instruction[edit]

As a beginner, or in casual talk page conversation, you might only have one little calculation you want to use Lua for but don’t want to write a full module. You might find Module:LuaCall convenient for this. For example, you can test how a greedy Lua pattern works:

  • {{#invoke:LuaCall|main|a=bbbbbbbbbba|b=bb(.*)b(.+)bba|string.match(a,b)}}bbbb

or count up the length of a Did you know hook or the text portion of a Did you know candidate:

  • {{#invoke:LuaCall|main|a=... that you can count the length of your DYK hook with a Lua module?|string.len(a)}}69

In these specific examples, however, Module:String could do both of these tasks.

The script at Module:LuaCall has been written to accept any set of named parameters somename=value, for each one storing the string value in the variable with the name somename, and then allowing you to use these variables as parameters for any function available in Lua. The script then returns only the first value returned by the function (Lua functions can return multiple values, but in this case, only the first is returned from the module).

Errors[edit]

Lua errors appear as red «Script error» messages. If Javascript is enabled, the red script error message is a link which usually allows you to follow it back to the line in the module where the error occurred. There are some exceptions, for example «Module not found», if the name of the module itself is mistyped, or «The function you specified did not exist» if the function name given is invalid.

Lua program structure: Output[edit]

The most fundamental part of a Wikipedia Lua program is a return statement which carries its output back to the page that had the #invoke. You can have a Lua function that runs without error even though it doesn’t contain a return statement, but on Wikipedia it is pointless, as Lua programs cannot generally have side effects on Wikipedia.

The module itself must return a Lua table of values. A Lua table is expressed as a list of values separated by commas, within curly braces. When the module is called by #invoke, the function it names (the first argument after |) is looked for in that table. That function, in turn, is expected to return something that can be represented as a string.

Therefore, return { mw.ustring.gmatch( "Hello world", "(.*)" ) } is actually a complete Lua module (though a very strange one) — it returns the function returned by mw.ustring.gmatch (an iterator function listed in the Lua reference cited above) as the one and only element in an array (represented within {})—which when executed using {{#invoke:ModuleName|1}} yields the string «Hello world». However, things are not usually done this way. Typically we use the overall form:

local p = {} -- Defines a variable p as an empty table, but *not* nil.

function p.main( frame ) -- This block defines the table element p["main"] as a function.
    return "Hello world" -- The string result of the function.
end -- Ends the block defining the function object p["main"] (or p.main).

return p -- This returns the table p, which under the key "main" contains the 
    -- function above (p.main), which when called returns string "Hello world".

Note that function p.main(frame) ... end is equivalent to p.main = function(frame) ... end or p["main"] = function(frame) ... end. The function is just another type of value, retrieved with the key "main" from table p. If you want to allow users to invoke the same module with {{#invoke:module-name|hello}} instead of {{#invoke:module-name|main}}, you can write p.hello = p.main to copy the reference to this function to a new key in the table. You can even write p[""] = p.main, which causes {{#invoke:module-name|}} to produce the same output as {{#invoke:module-name|main}}. Learning to think of functions as a data type becomes very important later on for working with library functions like mw.ustring.gsub, and constructing iterator functions.

Lua program structure: Input[edit]

The frame parameter above (which is pretty much always given this name in Wikipedia Lua modules) receives another table, which is passed from the page that makes the call to the Lua module. It contains a surprising amount of stuff, of which just a few things concern the novice.

Arguments[edit]

frame.args contains another table, namely, all the content sent by the user within the #invoke brackets except the first argument which states the name of the function to be executed. So in {{#invoke:ConvertNumeric|decToHex|3377}}, the string "3377" is the content of frame.args[1] (which is the same as frame["args"][1] but not the same as frame.args["1"] or frame["args"]["1"]). Unnamed parameters come out with numbers as keys (frame.args[1], frame.args[2], etc.), while named parameters come out with the parameter names (strings) as keys (frame.args["count"], frame.args["style"], etc.).

Example:

{{#invoke:modulename|functionname|3377|4|count=3|style=bold}}

results in

frame.args[1]=3377, frame.args[2]=4, frame.args["count"]=3, frame.args["style"]="bold".

Parent frame[edit]

Within frame there is a parent frame, referring to the page that called the page that gives the script, and you can pull out arguments from that also. Just write

parent=frame.getParent(frame)

and parent.args will contain those arguments.

It is popular in Lua to use the synonymous statement parent=frame:getParent(), cancelling the need to write frame twice. Note the colon (:) instead of the dot (.). parent=frame:getParent() means exactly the same as parent=frame.getParent(frame). For novices this can be confusing, and it is important to be aware of this idiom. If you use it in the wrong way, though, the script errors are pretty good at pointing out that this was the mistake.

Basic debugging[edit]

Debugging can start as soon as you write programs, and can be done simply with string concatenation.
Just set up a variable with some recognizable name like «debuglog» in your main function (p.main) with a statement like local debuglog="". This initial «» definition helps because otherwise it will be nil and concatenating a string to nil gets you an error. Now whenever you have a variable you’d like to test, say x, just write debuglog = debuglog .. "x=" .. tostring(x), and have at the end of your program return output .. debuglog The «tostring» is a function to ensure x is interpreted as a string, so that if it is an table, nil, etc. it will display as «table», «nil», etc. rather than as Script error.

Format[edit]

The WP:Lua style guide gives some basic formatting suggestions expected by the JavaScript module editor, such as using four-space indentations and keeping if, then, else, end at the same level of indentation.

Comments to the end of a line are marked by — . Use them. Many modules for Wikipedia have a straightforward, linear design, but that doesn’t mean it won’t help to have your sections clearly labelled when you go back to the code for the hundredth time. The Lua style guide gives additional recommendations for using functions to keep your work more organized.

Exasperating bugs[edit]

Some bugs you might want to keep in mind:

  • Attempt to call a string value. It means you forgot the .. between a string and a variable somewhere in a mess of stuff you’re concatenating.
  • A variable ignores all your efforts to assign stuff to it. You may have inadvertently written two local statements — the one sets the value of the variable within a limited region, and when the program leaves that region, you’re back to the old value.
  • A numbered table entry ignores all your efforts to assign to it. This is because a[«50»] is not a[50]. Typically you have processed a parameter (which you may have received from the invoke as a string) with string functions in one place, but performed numeric operations in another, leaving you with two different types of variable to use for an index.
  • Some graphics you’re trying to display are heading off to the hills. (actually a HTML error) You didn’t close your </div>s, so all the top: and left: styles keep adding up.
  • … nil … There are all sorts of things you can’t do to a nil variable, like assign x.somefield or x[n] if x is nil, concatenate a .. b if a or b is nil, or evaluate a[x] if x is nil. Initialize such variables with (local) x={}, a=»», etc. Often «global» is mentioned in these errors because you didn’t have a local statement for the nil variable.
  • string expected, got function. Some important things like mw.ustring.gmatch actually return functions, not strings — see Functions below.
  • no such module. You #invoked a module that didn’t exist — or wrote #invoke:Module:x instead of #invoke:x.
  • the function specified did not exist. You #invoked a module, but the field after the name of the module is wrong. Often this field expects a standard name like «main», and you’ve forgotten it and gone straight to the first data parameter. If you’re unsure of the function name, check the module documentation, or look for what function(s) in the code accept a «frame» parameter.

Understanding tables and related concepts[edit]

  • An expression list is a set of values separated by commas. The values can be strings, numbers, tables, functions, etc.
  • A sequence is a set of entries with indices from 1 to N, where N is a positive integer. They can be created by placing brackets around an expression list. For example, if a={1, "quotation", mw.ustring.gmatch("abca","a"), {2,3,4}} then a[1]=1, a[2]=»quotation», a[3] is the function returned by gmatch(), and a[4] is the table {2,3,4}. An expression list can also be recovered from a table using unpack(): b, c, d = unpack(a) will set b=1, c=»quotation», and d as the function returned by gmatch(); {2,3,4} will be discarded in this case.
  • A table is a sequence, optionally supplemented by named keys: digit["two"]="2". Several table functions like table.concat will only work with the numbered values and ignore named keys.
  • The metatable offers a large, optional set of methods for altering table behavior. For example, you can define a table to be callable like a function.

Initializing a table[edit]

It is often useful to create a whole table at once in a statement. There are many equivalent ways to do this, but the shortcuts don’t work for every kind of value. To begin with, the most general way is to assign each key and value explicitly:

a = {[0]='zero', [1]='one', ['1']='string for one'}

If sequence keys (positive integers) are given in order, only the values need to be given, so the following will assign 'one' to a[1]:

a = {[0]='zero', 'one', ['1']='string for one'}

If a key has only letters, digits, and underscores, and begins with a non-digit, the brackets and quotation marks can be omitted:

a = {a='one', b='two'}

This is identical to a = {["a"]='one', ["b"]='two'}.

However, this will fail for keys that begin with a digit: hex = {7f = 127} will produce an error; use hex = {['7f'] = 127} instead.

Note that when given within brackets, or to the right of the equal sign, quotation marks are needed, or else string values will be taken as variables:

a = {[b] = c}

assigns the value of variable c to the key contained in variable b.

Functions[edit]

  • Functions can return any kind of value — including a function. This is a powerful feature that can readily confuse the beginner. If you set a=mw.ustring.gmatch(text, "(.)"), the result assigned to a will be a function, not a string character! However, assigning b=a() by calling the function stored in a will return the first match (a string). Every time you set b=a() after that you’ll get another match (string) into b, until you run out of matches and get nil. Many iterator functions act this way.
  • You can keep separate counts for iterator functions by using different variables. For example, if you set q=mw.ustring.gmatch(text, "(x.)") in the same module, you can pull characters from the same piece of text (text) by evaluating d=q() without losing your place in a().
  • Tail calls offer substantial benefits in performance for those who master the language.
  • Function names are often of the form «p.myFunctionName», where p is the table from the «return p» at the bottom of your program. The reason for this is that you can only access functions that are entries in this table from the original #invoke statement. Functions for local use within the program can have any name.

Understanding patterns[edit]

Note: Lua patterns are not regular expressions in the traditional POSIX sense, and they are not even a subset of regular expressions. But they share many constructs with regular expressions (more below).

Lua patterns are used to define, find and handle a pattern in a string. It can do the common search and replace action in a text, but it has more options that doing plain text only. For example, in one go it can change the errors ‘New yorker’, ‘New-Yorker’, and ‘NewYorker’ into ‘New Yorker’.

  • To begin with, a pattern works like a plain string so long as it doesn’t contain the special characters ^ $ () % . [] * + — ?
  • Square brackets [ ] are used to match one single character in the string from a list of choices. [abc] matches the letters a, b, or c. With ^ right after [ they indicate «anything but»: [^abc] = not a, b, or c. Inside brackets and when not the first character, a minus indicates a range: [a-z] matches one single character from a, b, c, …, z.
  • Period . matches any character.
  • Percent % indicates a large set (class) of possible character matches when it is followed by a letter. See [1] for a full list. When followed by punctuation (whether a special character above or not) the % is removed and the punctuation is taken as a literal character; %% = literal %. Special classes include a balanced class %bxy and %f[set]; see the link above for more.
  • Parentheses ( ) indicate captures. The captures can be accessed later in the search string or in the string.gsub replacement string as %1 to %9, and are returned by string.match as an expression list of results.
  • The qualifiers ? — * + specify repetitions of a single character (not a longer string).
  • ? means 0 or 1 repetitions: «a?» matches «a» or «».
  • — means 0 or more repetitions, choosing as few as possible to achieve a match («non-greedy»). For example string.match(«bbbb», «(.-)») yields «», which is less than useful because there is nothing to root the ends of the expression and prevent it from matching zero characters.
  • * means 0 or more repetitions, choosing as many as possible («greedy»). For example string.match(«bbbb», «.*») yields bbbb.
  • + means 1 or more repetitions, choosing as many as possible («greedy»).

Note that the greediness of the leftmost qualifier rules over all others when there is a choice: (.*)b(.*) when matched on «bbb» will return «bb», «», while a(.-)b(.-)a when matched on «abbba» will return «», «bb».

  • ^ and $ indicate the beginning and end of the string if they occur in the appropriate place in the pattern. Otherwise they are literal characters. ^ is not used in the string.gmatch function.

The reference manual for Lua patterns is at mediawiki.org.

Note on Lua patterns versus regular expressions[edit]

Lua patterns are loosely based on regular expressions (sometimes shortened to regex or regexp). Lua patterns deliberately lack the most complex regular expression constructs (to avoid bloating the Lua code base), where many other computer languages or libraries use a more complete set. Lua patterns are not even a subset of regular expressions, as there are also discrepancies, like Lua using the escape character % instead of \,, and additions, like Lua providing - as a non-greedy version of *.

Here is a list of some of the things that Lua patterns lack compared to regular expressions:

  • You cannot search for alternations between anything else than single characters (you cannot say (his|her) to choose between his and her, you can only say [abc] to choose between single characters a, b, or c).
  • You cannot look for multiples of multi-letter constructs such as (choo-)*choo to match choo, choo-choo or choo-choo-choo. There is no way to do this with Lua patterns.
  • You cannot specify the minimum and maximum number of repetitions like [0-9]{3,5} (to match 3 to 5 digits); in Lua you would say %d%d%d%d?%d? instead in this case.

There are Lua libraries that offer more powerful options,[2] including regular expressions, but the support on Wikipedia is pretty basic.

Wikipedia help for regular expressions (which Lua, as mentioned, does not support) is at Wikipedia:AutoWikiBrowser/Regular expression.

Понравилась статья? Поделить с друзьями:
  • Руководство руднянского района
  • Стиль руководства при принятии управленческих решений
  • Крб 126 руководство по эксплуатации
  • Как научить хомяка играть в шахматы шуточная инструкция
  • Как подать заявление на самозанятость через госуслуги пошаговая инструкция