728 x 90

Lua, mały język którego pełno w każdej niszy

Lua, mały język którego pełno w każdej niszy

Lua to bardzo mały język programowania,  który wydaje się być obecny w najdziwniejszych niszach. Znajdziemy go we frameworkach aplikacji webowych, IDE, programach graficznych, kernelach NetBSD i Linux , silnikach gier (także mobilnych), bazach danych, oprogramowaniu do składu tekstu, edytorach tekstu czy wirusach… Dlatego warto się z nim zapoznać a nawet zaprzyjaźnić.

Czym jest Lua?

Język Lua jest dynamicznym językiem programowania, ze składnią podobną bardziej do Pascala i Basica niż do języków takich jak C, C++, Java, C#, Rust.

Lua zainspirowała lub pomogła powstać również innym językom np. Squirrel (język znany głownie z bycia użytym w kilku grach na silnku Source od firmy Valve) i Terra (język o zastosowaniach podobnych do C/C++ ale metaprogramowany w Lua).
Lua posiada też kilka języków które kompilują się do niego takich jak MoonScript (określający się jako będący dla Lua tym czym CoffeScript dla JavaScriptu), TypedLua (opcjonalne typowanie, bardzo rzadko używane) i Haxe (statyczny język posiadający możliwość kompilacji do aplikacji Flashowej i wielu innych języków).

Składnia

W dokumentacji Lua znajduje się składnia całego języka w notacji BNF. Tych, którzy się tego przestraszą, zapraszam mimo wszystko do czytania artykułu dalej.

To składnia Lua 5.1:

chunk ::= {stat [`;´]} [laststat [`;´]]

block ::= chunk

stat ::= varlist `=´ explist | 
 functioncall | do block end | 
 while exp do block end | 
 repeat block until exp | 
 if exp then block {elseif exp then block} [else block] end | 
 for Name `=´ exp `,´ exp [`,´ exp] do block end | 
 for namelist in explist do block end | 
 function funcname funcbody | 
 local function Name funcbody | 
 local namelist [`=´ explist]

laststat ::= return [explist] | break

funcname ::= Name {`.´ Name} [`:´ Name]

varlist ::= var {`,´ var}

var ::= Name | prefixexp `[´ exp `]´ | prefixexp `.´ Name

namelist ::= Name {`,´ Name}

explist ::= {exp `,´} exp

exp ::= nil | false | true | Number | String | `...´ | function | 
 prefixexp | tableconstructor | exp binop exp | unop exp

prefixexp ::= var | functioncall | `(´ exp `)´

functioncall ::= prefixexp args | prefixexp `:´ Name args

args ::= `(´ [explist] `)´ | tableconstructor | String

function ::= function funcbody

funcbody ::= `(´ [parlist] `)´ block end

parlist ::= namelist [`,´ `...´] | `...´

tableconstructor ::= `{´ [fieldlist] `}´

fieldlist ::= field {fieldsep field} [fieldsep]

field ::= `[´ exp `]´ `=´ exp | Name `=´ exp | exp

fieldsep ::= `,´ | `;´

binop ::= `+´ | `-´ | `*´ | `/´ | `^´ | `%´ | `..´ | 
 `<´ | `<=´ | `>´ | `>=´ | `==´ | `~=´ | 
 and | or

unop ::= `-´ | not | `#´chunk ::= {stat [`;´]} [laststat [`;´]]

block ::= chunk

stat ::= varlist `=´ explist | 
 functioncall | do block end | 
 while exp do block end | 
 repeat block until exp | 
 if exp then block {elseif exp then block} [else block] end | 
 for Name `=´ exp `,´ exp [`,´ exp] do block end | 
 for namelist in explist do block end | 
 function funcname funcbody | 
 local function Name funcbody | 
 local namelist [`=´ explist]

laststat ::= return [explist] | break

funcname ::= Name {`.´ Name} [`:´ Name]

varlist ::= var {`,´ var}

var ::= Name | prefixexp `[´ exp `]´ | prefixexp `.´ Name

namelist ::= Name {`,´ Name}

explist ::= {exp `,´} exp

exp ::= nil | false | true | Number | String | `...´ | function | 
 prefixexp | tableconstructor | exp binop exp | unop exp

prefixexp ::= var | functioncall | `(´ exp `)´

functioncall ::= prefixexp args | prefixexp `:´ Name args

args ::= `(´ [explist] `)´ | tableconstructor | String

function ::= function funcbody

funcbody ::= `(´ [parlist] `)´ block end

parlist ::= namelist [`,´ `...´] | `...´

tableconstructor ::= `{´ [fieldlist] `}´

fieldlist ::= field {fieldsep field} [fieldsep]

field ::= `[´ exp `]´ `=´ exp | Name `=´ exp | exp

fieldsep ::= `,´ | `;´

binop ::= `+´ | `-´ | `*´ | `/´ | `^´ | `%´ | `..´ | 
 `<´ | `<=´ | `>´ | `>=´ | `==´ | `~=´ | 
 and | or

unop ::= `-´ | not | `#´

Może się ona wydawać długa w takiej postaci, ale jeśli porównać ją do składni C++ albo nawet Pythona albo C# to jest ona krótka.

Gdzie jest używany

Jak już wspomniałem na początku, można powiedzieć, że Lua jest używany praktycznie wszędzie, zarówno dziś jak i w przeszłości (wersje 2, 3 i 4, obecnie Lua jest w wersji 5.3).

Szukając miejsc gdzie używane jest Lua możemny natknąć się na: frameworki aplikacji webowych (OpenResty będący połączeniem NGINX i LuaJIT oraz Lapis używający MoonScripu i stworzony na potrzeby strony sprzedaży niezależnych gier itch.io), IDE (ZeroBrane, IDE w LuaJIT przeznaczone dla wszelkich wersji i zastosowań Lua 5), programy graficzne (Adobe LightRoom), kernele NetBSD i Linux (w przypadku tego drugiego jako oddzielny opcjonalny moduł), silniki gier (MOAI, Cocos, CryEngine, Love2D i wiele innych), baza Redis, oprogramowanie do składu tekstu (LuaTeX), edytory tekstu (vim – opcjonalnie przy kompilacji, SciTE – poprzez dodatek), wirusy (Flamer, znany też jako Flame i SkyWiper).

Lua ma również bardzo silną pozycję w programowaniu gier.
Gry takie jak Don’t Starve, seria Shank, seria S.T.A.L.K.E.R., seria Payday, seria Saints Row, World of Warcraft, Garry’s Mod, Wiedźmin 1 i wiele innych w różnym stopniu używają Lua w różnych wersjach i w różnym stopniu. Od całego kodu gry (Grim Fandango, Don’t Starve) po skromne elementy (UI w World of Warcraft).

Dlaczego jest używany

W wielu przypadkach Lua używane jest w celu umożliwienia programowania osobom które nie są profesjonalnymi programistami, nie znają C i C++, lub których głównym zadaniem jest coś innego, np. firmy tworzące gry pozwalają osobom których stanowisko to “game designer” programować w Lua, podczas gdy pełnoprawni programiści używają dużo trudniejszych i mniej wygodnych języków czyli C i C++. W taki sposób możliwa jest również szybka iteracja, bez potrzeby kompilacji natywnego kodu, często nawet bez potrzeby wyłączenia gry.

Kolejnym częstym powodem użycia Lua są drobe zmiany i konfiguracja, np. GUI w grach Payday i World of Warcraft. W takich przypadkach często wymagana jest odrobina kodu więc czysto deklaratywny format taki jak ini, json albo XML nie wystarczył do takiego rodzaju “konfiguracji”, a nawet w tym ostatnim przypadku można użyć Lua jako czysto deklaratywnego języka.

To wszystko dodatkowo jest wspomagane przez prostotę języka. Lua czasem jest też nieco zmieniane, np. by zmienić nieco format bajtkodu i nie pozwolić na jego łatwe wydobycie. Język Lua, jego API w C i jego implementacja są tak małe, że jeden lub dwóch programistów zaznajomionych z C i Lua może dodać Lua do programu w C lub C++ w kilka dni a przeczytać i być gotowym do modyfikacji języka i jego interpretera w kilka tygodni. Próba zrobienia tego ostatniego dla Mono lub Pythona skończyłaby się szybką porażką lub latami nauki i pracy z powodu poziomu skomplikowania tych rozwiązań w porównaniu do Lua.

Zalety

  • Bardzo szybki interpreter jak na interpretowany język dynamiczny
  • Bardzo szybki JIT za pomocą LuaJIT (oddzielnego niezależnego projektu)
  • Mała i prosta składnia
  • Bardzo proste API w C
  • Prosta implementacja w C która może służyć do nauki o programowaniu interpreterów, maszyn wirtualnych, itp.
  • Spora ilość bindingów i niezależnych bibliotek w C, C++ i Lua
  • Możliwości i kontrola którą daje programiście, zwłaszcza od strony C

Wady

  • Brak standardowego menadżera pakietów (ale istnieją poboczne projekty, np. Lua Rocks)
  • Brak operatorów binarnych przed Lua 5.3
  • Indeksowanie tablic i napisów od 1 a nie od 0 jak w większości języków
  • Projekt LuaJIT to Lua 5.1 z elementami 5.2, funkcjonalności z 5.3 w nim nie ma
  • Brak zgodności API i ABI między wersjami (jedynie LuaJIT jest zgodne z 5.1)
  • Bardzo mała ilość wbudowanych funkcjonalności
  • Mała ilość niezależnych biblitek w porównaniu do Node.js, Pythona
  • Skromna składnia
  • Brak wbudowanego systemu obiektowego

Porównanie do Pythona i JavaScriptu

Najczęściej Lua porównywane jest do dwóch innych popularnych dynamicznych języków programowania: Pythona i JavaScriptu, do tego pierwszego ze względu na jego popularność i moc, a do tego drugiego ze względu na ogólne podobieństwo i lekkość.

Lua może też być dobrym wstępem do języków typowanych dynamicznie dla kogoś kto używał wcześniej tylko języków typowanych statycznie. Znając Lua nauka JavaScriptu i Pythona będzie dużo szybsza i łatwiejsza, z możliwością skupienia się na bibliotekach i nowej składni a nie na próbach zrozumienia dynamicznego typowania.

Jak zacząć?

Na systemach Linuxowych i podobnych można pobrać jedną lub więcej z Lua 5.1, 5.2, 5.3 i JIT.
Na systemach Windowsowych najlepszym sposobem jest pobranie interpretera i bibliotek z http://luabinaries.sourceforge.net/
Dodatkowo, na stronie języka, można go wypróbować online: http://www.lua.org/demo.html.
Kompilacja jest łatwa ponieważ Lua nie wymaga żadnych zewnętrznych ani dodatkowych bibliotek i używa tylko tego ze standardów C i C++ co jest dostępne w większości kompilatorów od lat.

Jeśli chodzi o edytory to większość popularnych edytorów wspiera kolorowanie składni Lua. Np. Emacs, vim, Geany, VS Code, Notepad++ i wiele innych. Istnieje również darmowe pełnoprawne IDE napisane w Lua przy pomocy biblioteki wxLua: https://studio.zerobrane.com/.

Za idealne źródła informacji o języku, składni i paradygmatach posłuży książka Programming in Lua (dla wersji 5.0 dostępna za darmo online, jednak zmiany w wersjach kolejnych są na tyle małe i na tyle dobrze udokumentowane, że może służyć do nauki dowolnej wersji Lua 5.x przy odrobinie ostrożności) i oficjalna dokumentacja.

Aby uruchomić program napisany w Lua należy użyć programu o nazwie lua i przekazać nazwę pliku jako pierwszy argument, by skompilować kod do bajtkodu a potem go wylistować można użyć luac z odpowiednimi opcjami. Program lua może też być uruchomiony interaktywnie, podobnie do Pythona, Basha, itp.

Przykłady

Przykłady są w Lua 5.1 ale większość z nich działa też w wersjach 5.2 i 5.3 bez zmian lub z małymi zmianami. Oczywiście kod z 5.1 zawsze działa też bez żadnych zmian w LuaJIT z powodu jego pełnej zgodności z 5.1.

Lua posiada 8 typów danych: nil, liczba, napis, boolean, tablica, “userdata”, wątek i funkcja.

Typ “userdata” dodatkowo dzieli się na “pełne” i “lekkie”, a od wersji 5.3 typ liczba ma podtyp zmiennoprzecinkowy i całkowity, w sposób podobny do Pythona, podczas gdy wcześniej wszystkie zmienne były jednego typu (domyślnie double w C, ale można to zmienić przy samodzielnej kompilacji Lua).

Userdata reprezentują dane z C, a tak zwane “wątki” to nie wątki systemu operacyjnego a mechanizm w Lua pozwalający na zawieszenie wykonywania i wrócenie do niego potem.

-- typy danych w Lua
 local a = nil --ta zmienna jest lokalna, pozostałe są globalne
 b = 10
 c = 'text'
 d = true
 e = {a = 10, b = 20}
 f = io.open('a.txt', 'w')
 g = coroutine.create(function()end)
 h = function(x) return x * 10 end

Funkcje i ich wywoływanie na różne sposoby, w tym poprzez domyślny argument self, podobnie do tego jak ma to miejsce w Pythonie, co jest jednym z nielicznych wbudowanych w Lua mechanizmów związanych z obiektowością.

--tworzenie funkcji globalnych i lokalnych
function a(x) return x * 10 end

--powyzszy zapis ma takie same znaczenie jak ponizszy i skutkuje takim samym bajtkodem
b = function(x) return x * 10 end

local function c(x) return x * 10 end

--podobnie jak wyzej, znaczenie jest to samo, ale --zadeklarowanie zmiennej lokalnej ma znaczenie jesli chcemy uzyc funkcji rekurencyjnie
local d
d = function(x) return x * 10 end

--wywolywanie funkcji
print(a(10), b(10), c(10), d(10))

--funkcje z argumentem self
local a = {}
function a:b() --zapis rownowazny function a.b(self)
print(self)
end
a:b() --to samo co a.b(a) ale a jest wyszukane tylko raz w obecnym srodowisku

Funkcje w Lua mogą przyjmować dowolną ilość argumentów i zwracać dowolną ilość wartości, podobnie do Pythona, ale w przypadku argumentów nie ma aż tak rygorystycznej kontroli nad ich ilością, a w przypadku zwracania nie jest to krotka otoczona specjalną syntaktyką a mechanizm wbudowany w samą maszynę wirtualną.

local function a(x, y, z)
return x * y * z end

local function b(a)
return type(a)
end

print(a(1, 2, 3, 4)) --liczba 6, nadmiarowy argument jest ignorowany
print(b()) --napis 'nil', brakujacy argument zostal wypelniony nilem

--funkcja przyjmujaca dowolnie wiele argumentow
--aby uzyc poszczegolnych nalezy uzyc tablicy lub wykorzystac wbudowana funkcje select
--aby przekazac je dalej mozna uzyc ..., tak jak do wywolania select
local function c(...)
return select(1, ...), {...}
end

print(c('a', 'b', 'c')) --napis 'a' i tablica

--x to 1, y to nil, niedomiarowe wartosci w multi-przypisaniu zastepuje nil
local x, y = 1

--q, w i e do 1, 2 i 3, nadmiarowe wartosci w multi-przypisaniu sa ignorowane
local q, w, e = 1, 2, 3, 4, 5

--zamiana zmiennych poprzez multi-przypisanie
a, b = b, a

Lua posiada system ładowania bibliotek ale zaimplementowany jest on jako funkcje i struktury w Lua i C, a nie jako część języka (jak robi to np. Python gdzie import to słowo kluczowe wbudowane w język mimo wszelkich możliwości wpływania na jego działanie). Teoretycznie można go całkowicie usunąć i ominąć, ale duża ilość i np. LuaRocks respektuje ten system i instaluje biblioteki tak, by mogły one być przez niego użyte.

--wbudowany modul, domyslnie dostepny tez pod nazwa 'math' globalnie
--require to funkcja a wywolanie funkcji w Lua jest mozliwe bez ()
--gdy chcemy jej przekazac jeden argument ktory jest literalem, napisem lub tablica
local m = require 'math'

--require moze zwrocic dowolna wartosc, nie tylko tablice,
--ale powszechna praktyka jest tworzenie modulow jako tablice
print(m.pi)

Poza tym Lua prezentuje podstawowe operatory z C i innych języków, dodawanie, odejmowanie, negowanie, itd. Jedyną ciekawostką jest # dla pobrania długości tablicy lub napisu i ^ jako operator potęgowania.

Co dalej?

Niedługo na łamach FRIWEB napiszemy więcej o Lua, pokażemy też przykłady jak z jego wykorzystaniem tworzyć gry mobilne.

Źródło obrazu z nagłówka: opracowanie własne na podstawie obrazów z freeimages.com

Leave a Comment

Your email address will not be published. Required fields are marked with *

Cancel reply

Inne artykuły