Obsah
1. Využití jazyka Lua v aplikacích naprogramovaných v Go
2. Programovací jazyk Lua „vestavitelný“ do dalších aplikací
3. Vestavění jazyka Lua do vlastních aplikací naprogramovaných v Go
4. Načtení a spuštění Lua skriptu uloženého v řetězci
5. Víceřádkové řetězce v jazyku Go
6. Načtení a spuštění skriptů uložených v samostatných souborech
7. Inicializace několika virtuálních strojů jazyka Lua
8. Gorutiny a virtuální stroje jazyka Lua
9. Volání funkce vytvořené v jazyku Lua z Go
10. Předání parametrů do funkce naprogramované v jazyku Lua
11. Zpracování návratové hodnoty
12. Konverze návratových hodnot na typy kompatibilní s jazykem Go
13. Datový typ boolean v programovacím jazyce Lua
14. Datový typ nil v programovacím jazyce Lua
15. Zpracování většího množství návratových hodnot ze skriptu naprogramovaného v jazyce Lua
16. Volání funkcí naprogramovaných v Go z Lua skriptu
17. Registrace funkce, která se má z Lua skriptů zavolat
18. Zpracování parametrů a návratových hodnot
19. Repositář s demonstračními příklady
1. Využití jazyka Lua v aplikacích naprogramovaných v Go
Programovací jazyk Lua, jemuž jsme se věnovali v samostatném seriálu, patří do poměrně rozsáhlé a stále častěji používané skupiny vysokoúrovňových skriptovacích jazyků, do níž můžeme zařadit například dnes velmi populární Python, jazyk Perl, Ruby, JavaScript popř. TypeScript či dnes již poněkud méně populární skriptovací jazyk Tcl. Tyto programovací jazyky nabízí vývojářům jednoduchou práci se strukturovanými daty (většinou je použita nějaká forma asociativního pole), dynamicky typované proměnné, automatickou správu paměti (garbage collector) a mnohé další vysokoúrovňové techniky zjednodušující a především do značné míry zrychlující vývoj, ať již prototypů, či výsledných aplikací. Jazyk Lua má navíc velmi jednoduchou – a pro mnoho vývojářů důvěrně známou – syntaxi inspirovanou Modulou a Pascalem, zatímco sémantika jazyka se v mnohém podobá spíše moderním verzím JavaScriptu a Pythonu.
Někteří programátoři si jazyk Lua oblíbili právě kvůli jeho syntaxi, která zbytečně nepřináší žádné nové prvky (snad jen zápis relačního operátoru nerovnosti pomocí ~= je přinejmenším podivný a neobvyklý; někoho zpočátku může mást i indexování prvků od jedničky). Naopak se snaží programátorům ulehčit život, například možností zápisu vícenásobného přiřazení, přístupu k položkám asociativního pole jak pomocí „tečkové“ notace, tak i s využitím hranatých závorek apod. Jednoduše použitelná syntaxe a současně i velká vyjadřovací schopnost jazyka Lua by však pravděpodobně ve velké konkurenci v žádném případě nedostačovala pro jeho masovější rozšíření. Důvodem, proč jsou některé hry, například Escape from Monkey Island, Grim Fandango, Fish Fillets, Neverwinter Nights či MDK2 z menší či větší části naprogramované právě v Lue, spočívá v tom, že kombinace nízkoúrovňového a skriptovacího jazyka umožňuje soustředit se při vývoji na podstatné věci – herní engine vytvořit co nejefektivnější s využitím všech možností nízkoúrovňového jazyka a naopak herní scénář a logiku hry naskriptovat s co největším urychlením cyklu oprava–překlad–spuštění.
V mnoha případech se také využívá další užitečné vlastnosti jazyka Lua – celý překladač i interpret vygenerovaného bajtkódu (popř. pouze interpret – všechny pojmy jsou vysvětleny dále) je možné velmi snadno zabudovat do jiné aplikace, přičemž se výsledná velikost spustitelného souboru této aplikace zvětší o cca 70 kB (popř. lze volat dynamickou knihovnu o řádově stejné velikosti), což není mnoho, když si uvědomíme, že dostáváme k dispozici plnohodnotný vysokoúrovňový programovací jazyk (ostatně Lua se díky své malé velikosti používá i pro „pouhé“ zpracování konfiguračních souborů, které díky tomu mohou obsahovat různé konstanty, výrazy, podmínky atd.). Mnozí programátoři, mezi jinými i John Walker (jeden z vývojářů AutoCADu) se netají tím, že právě zabudování programovacího (skriptovacího) jazyka do jejich aplikací mělo velký význam pro jejich úspěch, protože to umožnilo mnoha dalším vývojářům rozšiřovat funkčnost původní aplikace a tím zvýšit její atraktivitu pro další uživatele.
2. Programovací jazyk Lua „vestavitelný“ do dalších aplikací
Jeden z dalších důvodů relativně velké oblíbenosti programovacího jazyka Lua mezi vývojáři spočívá v tom, že její překladač i interpret je velmi snadno „vestavitelný“ do dalších aplikací, což znamená, že do prakticky libovolného programu je možné zabudovat buď plnohodnotný překladač tohoto jazyka, nebo pouze tu část, která se stará o běh přeloženého bajtkódu (častěji se však setkáme s prvním způsobem integrace). V některých typech aplikací, například počítačových hrách, totiž nemusí být nutné překládat nové zdrojové kódy, ale pouze spouštět bajtkód přeložený přímo výrobcem hry; další aplikace naopak mohou těžit z toho, že jsou uživatelsky skriptovatelné (viz většina moderních „Office“, mnohé programy typu CAD, grafické a textové editory a mnoho dalších programů). Samozřejmě se nejedná o unikátní vlastnost, protože i mnoho interpretů dalších programovacích jazyků lze vestavět do jiných aplikací – v poslední době se stává populární především JavaScript vedle již zavedeného Pythonu (LibreOffice a samozřejmě i OpenOffice.org, GIMP), Scheme (opět GIMP), Lispu (výše zmíněný AutoCAD, Emacs) či Visual Basicu (MS Office a další aplikace podporující VBA neboli Visual Basic for Applications).
Ovšem v případě programovacího jazyka Lua je její vestavění do aplikace skutečně snadné – z pohledu programátora (především pokud programuje v céčku či C++), který ve své aplikaci potřebuje použít nějaký skriptovací jazyk, se jedná o pouhých několik programových řádků s následným slinkováním s objektovým kódem uloženým v archivu liblua.a. Vložením celého překladače a interpretu jazyka Lua včetně jeho podpůrného běhového prostředí (základní funkce, garbage collector aj.) se zvětší velikost výsledného spustitelného souboru o již zmíněných 70 kB, což není nijak závratná hodnota, především při porovnání velikostí interpretů dalších programovacích jazyků. Lua se právě z tohoto důvodu dokonce používá i na mikrořadičích s poměrně malou operační pamětí a pamětí ROM (v jedné z aplikací využívajících Lua byl použit mikrořadič s 64 kB RAM a 256 kB EEPROM). V tomto případě se většinou využívá pouze ta část interpretu, která se stará o běh přeloženého bajtkódu, v některých situacích se také mění základní numerický datový typ na šestnáctibitové či třicetidvoubitové hodnoty namísto hodnot uložených ve formátu plovoucí tečky (viz soubor luaconf.h, především definice LUA_NUMBER).
Vestavěný interpret programovacího jazyka Lua do jisté míry řeší taktéž otázku bezpečnosti skriptů, aby se zabránilo šíření makrovirů, které byly tak „populární“ mezi uživateli jednoho nejmenovaného rozšířeného kancelářského balíku. Problém bezpečnosti je řešen především prakticky úplnou izolací běhového prostředí skriptů od ostatního systému. Pouze přímo programátor aplikace, která má obsahovat překladač a interpret Lua, může (explicitně zapsaným importem příslušné knihovny) skriptům povolit například možnost práce se soubory, spouštění dalších programů přes volání os.execute() apod. Bez importu těchto knihoven je skriptu povoleno se svým okolím komunikovat pouze pomocí volání zaregistrovaných funkcí. Pro předávání parametrů se navíc používá zvláštní zásobník, ne standardní zásobníkové rámce (na klasický zásobníkový rámec se ukládá pouze jeden ukazatel), takže skripty vlastně ani nemají možnost manipulovat se zásobníkem procesu pod kterým běží (tím se eliminují útoky typu stack overflow). Interpret provádí i základní kontrolu korektnosti předaného bajtkódu.
3. Vestavění jazyka Lua do vlastních aplikací naprogramovaných v Go
Vzhledem k tomu, že interpret skriptovacího jazyka Lua je navržen takovým způsobem, aby byl poměrně snadno implementovatelný, asi nebude větším překvapením, že jeho zdrojové kódy byly portovány i do programovacího jazyka Go. To znamená, že se jedná o novou „go-nativní“ implementaci interpretru Luy, která nevyžaduje, aby se z runtime jazyka Go volaly funkce z původní céčkovské knihovny. Touto problematikou jsme se sice prozatím do všech podrobností nezabývali, ovšem ve stručnosti lze říct, že kooperace mezi runtime jazyka Go a nativními céčkovými knihovnami může v praxi způsobovat problémy a není z pohledu programátorů zcela transparentní. Samotné vložení interpretru jazyka Lua do aplikace psané v Go je ovšem velmi snadné – programátor jen musí naimportovat příslušný balíček, inicializovat virtuální stroj jazyka Lua a spustit nějaký skript, jenž může být reprezentován řetězcem nebo může být načten ze souboru. Skript poté může volat předem vybrané funkce naprogramované v jazyku Go a naopak – z Go lze volat libovolnou funkci z Lua skriptu.
Dnes se budeme zabývat projektem nazvaným Gopher-Lua. Ten se instaluje velmi snadno – stejně jako jakýkoli jiný balíček určený pro Go:
$ go get github.com/yuin/gopher-lua
Výsledkem bude běžný balíček, nikoli spustitelný binární soubor – Gopher-Lua předpokládá, že se jazyk Lua skutečně bude vkládat (embed) do jiných aplikací.
4. Načtení a spuštění Lua skriptu uloženého v řetězci
V této kapitole si ukážeme, jakým způsobem se vlastně přímo z aplikace naprogramované v Go inicializuje virtuální stroj skriptovacího jazyka Lua a jak se v něm spustí nějaký skript.
V aplikaci, která chce uživatelům nabídnout možnosti skriptování s využitím jazyka Lua, je nejprve nutné naimportovat balíček s implementací interpretru a VM Luy:
import "github.com/yuin/gopher-lua"
Jednotlivé instance VM (může jich být totiž větší množství) se inicializují zavoláním funkce . Výsledkem je struktura, přes kterou se s vytvořeným virtuálním strojem komunikuje:
luaVM := lua.NewState()
Inicializovaný virtuální stroj by se měl před ukončením aplikace korektně zastavit a uvolnit všechny své prostředky. V Go je možné tento požadavek splnit velmi snadno s využitím konstrukce defer:
defer luaVM.Close()
A nakonec můžeme spustit libovolný skript či jeho část. V tom nejjednodušším případě je skript reprezentován řetězcem a při pokusu o jeho spuštění můžeme zjistit, zda nedošlo k nějaké chybě:
err := luaVM.DoString(`print("Hello from Lua")`) if err != nil { log.Fatal(err) }
Úplný zdrojový kód dnešního prvního demonstračního příkladu, který po svém spuštění provede inicializaci virtuálního stroje jazyka Lua a spustí v něm jednořádkový skript, vypadá následovně:
package main import ( "github.com/yuin/gopher-lua" "log" ) func main() { luaVM := lua.NewState() defer luaVM.Close() err := luaVM.DoString(`print("Hello from Lua")`) if err != nil { log.Fatal(err) } }
Po spuštění tohoto demonstračního příkladu se vypíše jediný řádek textu:
Hello from Lua
Další demonstrační příklady nepatrně vylepšíme tím, že se při inicializaci i deinicializaci virtuálního stroje jazyka Lua budou vypisovat zprávy do logu s využitím standardního balíčku log:
package main import ( "github.com/yuin/gopher-lua" "log" ) func main() { luaVM := lua.NewState() log.Println("Lua VM has been initialized") defer func() { luaVM.Close() log.Println("Lua VM has been closed") }() err := luaVM.DoString(`print("Hello from Lua!")`) if err != nil { log.Fatal(err) } }
Výsledek může vypadat například takto:
2019/05/24 20:50:43 Lua VM has been initialized Hello from Lua! 2019/05/24 20:50:43 Lua VM has been closed
5. Víceřádkové řetězce v jazyku Go
Již v prvních dvou demonstračních příkladech jsme se mohli setkat s tím, že skripty zapsané v jazyku Lua byly reprezentovány řetězcem, ovšem tento řetězec nebyl uzavřen do běžných uvozovek "", ale do zpětných apostrofů ``. Jedná se o takzvaný „raw string literal“, což je konstanta typu řetězec, který je při kompilaci načten přesně v takové formě, jak je zapsán, tj. bez interpretace speciálních znaků začínajících zpětným lomítkem atd. V těchto řetězcích je taktéž možné použít konce řádků, což není u běžných řetězců povoleno (minimálně nikoli v programovacím jazyku Go). Naproti tomu pokud je řetězec uzavřen do běžných uvozovek "", jedná se o takzvaný „interpreted string literal“, v němž jsou speciálně interpretována zpětná lomítka, za nimiž je možné zapsat kód speciálního znaku (\n je konec řádku, \t znak Tab, \xnn ASCII kód znaku, \unnn a \Unnnnnnnn Unicode znak atd.). V těchto řetězcích se nesmí použít konec řádku, což je ovšem dosti nevýhodné pro zápis kódu v jiném programovacím jazyce do řetězce.
Právě z tohoto důvodu jsme v demonstračních příkladech použili řetězce zapsané do zpětných apostrofů. A díky tomu, že v raw řetězcích je možné použít i konce řádků, můžeme do nich bez problémů zapsat i složitější několikařádkové skripty, například:
const Script = ` for i = 1,10 do print("Hello from Lua!", i) end`
Úplný demonstrační příklad, v němž je použit několikařádkový Lua skript, může vypadat následovně:
package main import ( "github.com/yuin/gopher-lua" "log" ) const Script = ` for i = 1,10 do print("Hello from Lua!", i) end` func main() { luaVM := lua.NewState() log.Println("Lua VM has been initialized") defer func() { luaVM.Close() log.Println("Lua VM has been closed") }() log.Println("Starting the following Lua script:") log.Println(Script) err := luaVM.DoString(Script) if err != nil { log.Fatal(err) } }
Po spuštění tohoto příkladu by se na standardním výstupu měly objevit informace o inicializaci virtuálního stroje jazyka Lua a následně i výsledek spuštěného skriptu (celkem deset řádků):
2019/05/24 20:50:54 Lua VM has been initialized 2019/05/24 20:50:54 Starting the following Lua script: 2019/05/24 20:50:54 for i = 1,10 do print("Hello from Lua!", i) end Hello from Lua! 1 Hello from Lua! 2 Hello from Lua! 3 Hello from Lua! 4 Hello from Lua! 5 Hello from Lua! 6 Hello from Lua! 7 Hello from Lua! 8 Hello from Lua! 9 Hello from Lua! 10 2019/05/24 20:50:54 Lua VM has been closed
6. Načtení a spuštění skriptů uložených v samostatných souborech
Spuštění skriptů reprezentovaných řetězci jazyka Go se v praxi pravděpodobně nebude příliš často používat. Mnohem častěji se setkáme s požadavkem, aby se spustily skripty uložené v externích souborech. I tuto možnost nám pochopitelně interpret programovacího jazyka Go nabízí, což si ukážeme na dalším demonstračním příkladu. V tomto příkladu je použita metoda pro načtení a spuštění Lua skriptu přečteného z externího souboru:
package main import ( "github.com/yuin/gopher-lua" "log" ) const LuaSource = "hello.lua" func main() { luaVM := lua.NewState() log.Println("Lua VM has been initialized") defer func() { luaVM.Close() log.Println("Lua VM has been closed") }() err := luaVM.DoFile(LuaSource) if err != nil { log.Fatal(err) } }
Samotný skript hello.lua je až triviálně jednoduchý:
for i = 1,10 do print("Hello #", i) end
Výsledek spuštění předchozího demonstračního příkladu:
2019/05/26 20:31:58 Lua VM has been initialized Hello # 1 Hello # 2 Hello # 3 Hello # 4 Hello # 5 Hello # 6 Hello # 7 Hello # 8 Hello # 9 Hello # 10 2019/05/26 20:31:58 Lua VM has been closed
7. Inicializace několika virtuálních strojů jazyka Lua
Každý virtuální stroj získaný zavoláním funkce lua.NewState() poskytuje skriptům vzájemnou izolaci. To například znamená, že jednotlivé skripty nebudou mít přístup ke svým globálním proměnným (a to proměnným jakéhokoli typu, tedy i k funkcím). Toto chování si pochopitelně můžeme velmi snadno otestovat, a to tak, že vytvoříme dva virtuální stroje a spustíme v nich různé skripty. Nejdříve se podívejme na úplný zdrojový kód demonstračního příkladu
package main import ( "github.com/yuin/gopher-lua" "log" ) const LuaSource1 = "v1.lua" const LuaSource2 = "v2.lua" const LuaSource3 = "v3.lua" func main() { luaVM1 := lua.NewState() log.Println("Lua VM1 has been initialized") luaVM2 := lua.NewState() log.Println("Lua VM2 has been initialized") defer func() { luaVM1.Close() log.Println("Lua VM1 has been closed") }() defer func() { luaVM2.Close() log.Println("Lua VM2 has been closed") }() err := luaVM1.DoFile(LuaSource1) if err != nil { log.Fatal(err) } err = luaVM2.DoFile(LuaSource2) if err != nil { log.Fatal(err) } err = luaVM1.DoFile(LuaSource3) if err != nil { log.Fatal(err) } }
Vidíme, že z prvního virtuálního stroje se volá skript v1.lua a v3.lua, zatímco ze druhého stroje pouze skript v2.lua. Interně jsou tyto skripty prakticky totožné:
print("v1.lua, x=", x) x = 10 print("v1.lua, x=", x)
print("v2.lua, x=", x) x = 10 print("v2.lua, x=", x)
print("v3.lua, x=", x) x = 10 print("v3.lua, x=", x)
Vidíme, že všechny tři skripty se nejdříve pokusí vypsat hodnotu globální proměnné x, následně tuto hodnotu změní a znovu ji vypíšou. Pokud budou skripty zcela izolované, měla by se vždy vypsat speciální hodnota nil a teprve poté hodnota 10. Pokud jsou naopak všechny skripty sdílené v jediné VM, vypíše se hodnota nil pouze jedenkrát. A konečně v případě, že jsou skripty spouštěné v různých VM navzájem izolované, měla by se hodnota nil vypsat dvakrát. Podívejme se tedy na skutečný výsledek běhu tohoto příkladu:
2019/05/26 20:43:31 Lua VM1 has been initialized 2019/05/26 20:43:31 Lua VM2 has been initialized v1.lua, x= nil v1.lua, x= 10 v2.lua, x= nil v2.lua, x= 10 v3.lua, x= 10 v3.lua, x= 10 2019/05/26 20:43:31 Lua VM2 has been closed 2019/05/26 20:43:31 Lua VM1 has been closed
Z výsledků je patrné, že skripty v1.lua a v3.lua sdílí prostor se svými globálními proměnnými. To je v pořádku, neboť běží v rámci jedné VM.
8. Gorutiny a virtuální stroje jazyka Lua
Interní stav VM implementovaný v projektu Gopher-Lua není zcela zabezpečen proti problémům, které vzniknou, pokud by se spustilo více skriptů v několika gorutinách v jediném VM. Nic nám však pochopitelně nebrání vytvořit si několik gorutin, v každé gorutině inicializovat vlastní VM a v něm potom spustit Lua skripty s tím, že případná komunikace bude provedena s využitím kanálů. V dalším demonstračním příkladu je ukázáno, jakým způsobem se paralelně spustí dva Lua skripty, každý ve své vlastní VM:
for i = 1,10 do print("Hello from VM1", i) end
for i = 1,10 do print("Hello from VM2", i) end
Samotné spuštění gorutin se nijak nevymyká informacím, které známe z předchozích částí tohoto seriálu. Jen pochopitelně nesmíme zapomenout počkat na dokončení všech vytvořených gorutin, a to s využitím kanálů – každá gorutina na konci zapíše do příslušného kanálu jednu hodnotu, na kterou čekáme v hlavní gorutině:
package main import ( "github.com/yuin/gopher-lua" "log" ) const LuaSource1 = "l1.lua" const LuaSource2 = "l2.lua" func callLuaVM1(c chan bool) { defer func() { c <- true }() luaVM1 := lua.NewState() log.Println("Lua VM1 has been initialized") defer func() { luaVM1.Close() log.Println("Lua VM1 has been closed") }() err := luaVM1.DoFile(LuaSource1) if err != nil { log.Fatal(err) } } func callLuaVM2(c chan bool) { defer func() { c <- true }() luaVM2 := lua.NewState() log.Println("Lua VM2 has been initialized") defer func() { luaVM2.Close() log.Println("Lua VM2 has been closed") }() err := luaVM2.DoFile(LuaSource2) if err != nil { log.Fatal(err) } } func main() { c1 := make(chan bool) c2 := make(chan bool) go callLuaVM1(c1) go callLuaVM2(c2) <-c1 <-c2 }
Výpis ze skriptů se kvůli nezavolání funkce flush() po každém volání print() navzájem pomíchá:
2019/05/26 20:53:14 Lua VM1 has been initialized 2019/05/26 20:53:14 Lua VM2 has been initialized Hello from VM2 Hello from VM1 1 Hello from VM1 2 Hello from VM1 3 1 Hello from VM1 4 Hello from VM1 5 Hello from VM1 6 Hello from VM1Hello from VM2 7 Hello from VM1 8 Hello from VM1 9 Hello from VM1 10 2019/05/26 20:53:14 Lua VM1 has been closed 2 Hello from VM2 3 Hello from VM2 4 Hello from VM2 5 Hello from VM2 6 Hello from VM2 7 Hello from VM2 8 Hello from VM2 9 Hello from VM2 10 2019/05/26 20:53:14 Lua VM2 has been closed
9. Volání funkce vytvořené v jazyku Lua z Go
Tím hlavním důvodem, proč se do aplikací naprogramovaných v jazyku Go vkládá interpret skriptovacího jazyka Lua, je možnost kooperace mezi oběma částmi aplikace – tj. mezi nativní částí vytvořenou přímo v Go a skripty napsanými v jazyce Lua. Kooperace přitom může být obousměrná:
- Z aplikace napsané v Go je umožněno volat funkce naprogramované (naskriptované) v jazyku Lua. To se samozřejmě týká i možnosti předávání parametrů a zpracování návratové hodnoty či návratových hodnot.
- A naopak, ze skriptu napsaného v jazyce Lua lze volat zvolené (registrované) funkce naprogramované v Go.
Nejdříve si ukážeme tu nejjednodušší variantu, tj. zavolání funkce naskriptované v jazyce Lua. Celý Lua skript, v němž je funkce definována, vypadá takto:
function hello() print("Hello from Lua") end
Samotné zavolání funkce hello je zpočátku komplikované, protože je nutné nastavit jak jméno funkce, tak i počet a typ předávaných parametrů i typ a počet návratových hodnot. V našem případě je funkce volána bez parametrů a nemá žádné návratové hodnoty, takže ji můžeme zavolat takto:
err = luaVM.CallByParam(lua.P{ Fn: luaVM.GetGlobal("hello"), NRet: 0, })
Úplný zdrojový kód příkladu, který po svém spuštění zavolá Lua funkci, naleznete na adrese https://github.com/tisnik/go-root/blob/master/article27/06_call_lua.go:
package main import ( "github.com/yuin/gopher-lua" "log" ) const LuaSource = "function.lua" func main() { luaVM := lua.NewState() log.Println("Lua VM has been initialized") defer func() { luaVM.Close() log.Println("Lua VM has been closed") }() err := luaVM.DoFile(LuaSource) if err != nil { log.Fatal(err) } err = luaVM.CallByParam(lua.P{ Fn: luaVM.GetGlobal("hello"), NRet: 0, }) if err != nil { log.Fatal(err) } }
Spuštění příkladu:
2019/05/26 20:37:26 Lua VM has been initialized Hello from Lua 2019/05/26 20:37:26 Lua VM has been closed
10. Předání parametrů do funkce naprogramované v jazyku Lua
Nyní se podívejme, jak se vlastně předávají parametry z aplikace psané v Go do funkce/funkcí naprogramovaných ve skriptovacím jazyku Lua. Samotný skript naprogramovaný v Lue používá dynamicky typované proměnné:
function hello(a, b) print("Hello from Lua") print("1st parameter", a) print("2nd parameter", b) end
V aplikaci psané v Go je nutné zajistit převod z datového typu Go na datový typ, který je zpracovatelný virtuálním strojem jazyka Lua. Pokud se bude jednat o numerické hodnoty, použijeme toto volání:
err = luaVM.CallByParam(lua.P{ Fn: luaVM.GetGlobal("hello"), NRet: 0, }, lua.LNumber(1), lua.LNumber(2))
Povšimněte si, že jsme dvě celočíselné hodnoty typu int museli převést na objekty typu LNumber stejně pojmenovanou funkcí.
Podívejme se nyní na úplný kód příkladu, který zavolá výše vypsanou funkci hello() a předá ji dvě numerické hodnoty 1 a 2:
package main import ( "github.com/yuin/gopher-lua" "log" ) const LuaSource = "function_params.lua" func main() { luaVM := lua.NewState() log.Println("Lua VM has been initialized") defer func() { luaVM.Close() log.Println("Lua VM has been closed") }() err := luaVM.DoFile(LuaSource) if err != nil { log.Fatal(err) } err = luaVM.CallByParam(lua.P{ Fn: luaVM.GetGlobal("hello"), NRet: 0, }, lua.LNumber(1), lua.LNumber(2)) if err != nil { log.Fatal(err) } }
Výsledek po spuštění:
2019/05/26 21:09:08 Lua VM has been initialized Hello from Lua 1st parameter 1 2nd parameter 2 2019/05/26 21:09:08 Lua VM has been closed
Naprosto stejné funkci hello() je ovšem možné předat například i dva řetězce:
err = luaVM.CallByParam(lua.P{ Fn: luaVM.GetGlobal("hello"), NRet: 0, }, lua.LString("foo"), lua.LString("bar"))
Příklad se jen nepatrně upraví:
package main import ( "github.com/yuin/gopher-lua" "log" ) const LuaSource = "function_params.lua" func main() { luaVM := lua.NewState() log.Println("Lua VM has been initialized") defer func() { luaVM.Close() log.Println("Lua VM has been closed") }() err := luaVM.DoFile(LuaSource) if err != nil { log.Fatal(err) } err = luaVM.CallByParam(lua.P{ Fn: luaVM.GetGlobal("hello"), NRet: 0, }, lua.LString("foo"), lua.LString("bar")) if err != nil { log.Fatal(err) } }
Výsledek po spuštění:
2019/05/26 21:09:25 Lua VM has been initialized Hello from Lua 1st parameter foo 2nd parameter bar 2019/05/26 21:09:25 Lua VM has been closed
11. Zpracování návratové hodnoty
Podívejme se nyní na další velmi jednoduchou funkci naprogramovanou ve skriptovacím jazyce Lua. Tato funkce akceptuje dva parametry, vypíše je na svůj standardní výstup a nakonec vrátí jejich součet:
function add(a, b) print("1st parameter", a) print("2nd parameter", b) return a+b end
Jakým způsobem je ovšem možné přečíst návratovou hodnotu této funkce a nějak ji zpracovat? Pro předávání návratových hodnot (může jich být totiž více – v tomto ohledu není Go prvním jazykem s podobným přístupem) se používá zásobník, z něhož je možné hodnoty načítat metodou Get() a odstraňovat metodou Pop(). Výsledkem je speciální hodnota typu LValue s metodami String() a Type():
ret := luaVM.Get(-1) luaVM.Pop(1) println("Type", ret.Type()) println("Value", ret.String())
Při volání funkce nesmíme zapomenout zadat počet návratových hodnot:
err = luaVM.CallByParam(lua.P{ Fn: luaVM.GetGlobal("add"), NRet: 1, Protect: true, }, lua.LNumber(1), lua.LNumber(2))
Podívejme se nyní na příklad, který zavolá Lua funkci určenou pro součet dvou numerických hodnot:
package main import ( "github.com/yuin/gopher-lua" "log" ) const LuaSource = "add.lua" func main() { luaVM := lua.NewState() log.Println("Lua VM has been initialized") defer func() { luaVM.Close() log.Println("Lua VM has been closed") }() err := luaVM.DoFile(LuaSource) if err != nil { log.Fatal(err) } err = luaVM.CallByParam(lua.P{ Fn: luaVM.GetGlobal("add"), NRet: 1, Protect: true, }, lua.LNumber(1), lua.LNumber(2)) if err != nil { log.Fatal(err) } ret := luaVM.Get(-1) luaVM.Pop(1) println("Type", ret.Type()) println("Value", ret.String()) }
S výsledky:
2019/05/26 21:22:25 Lua VM has been initialized 1st parameter 1 2nd parameter 2 Type 2 Value 3 2019/05/26 21:22:25 Lua VM has been closed
Podobně můžeme zavolat Lua funkci, která spojí dva řetězce:
function concatenate(a, b) print("1st parameter", a) print("2nd parameter", b) return a..b end
Příklad se upraví jen nepatrně:
package main import ( "github.com/yuin/gopher-lua" "log" ) const LuaSource = "concatenate.lua" func main() { luaVM := lua.NewState() log.Println("Lua VM has been initialized") defer func() { luaVM.Close() log.Println("Lua VM has been closed") }() err := luaVM.DoFile(LuaSource) if err != nil { log.Fatal(err) } err = luaVM.CallByParam(lua.P{ Fn: luaVM.GetGlobal("concatenate"), NRet: 1, Protect: true, }, lua.LString("foo"), lua.LString("bar")) if err != nil { log.Fatal(err) } ret := luaVM.Get(-1) luaVM.Pop(1) println("Type", ret.Type()) println("Value", ret.String()) }
A výsledky budou pochopitelně odlišné:
2019/05/26 21:23:09 Lua VM has been initialized 1st parameter foo 2nd parameter bar Type 3 Value foobar 2019/05/26 21:23:09 Lua VM has been closed
12. Konverze návratových hodnot na typy kompatibilní s jazykem Go
Typový systém programovacího jazyka Go se v několika ohledech odlišuje od typového systému skriptovacího jazyka Lua. Proto asi nebude velkým překvapením, že datové typy a hodnoty se musí explicitně konvertovat. Při posílání parametrů funkcím do jazyka Lua se používají konverzní funkce zmíněné v předchozích kapitolách. Ovšem například při čtení návratové hodnoty (návratových hodnot) z Lua funkce se musí konverze provést složitějším způsobem, a to pochopitelně z toho důvodu, že Go je staticky a silně typovaný jazyk, tj. v tomto ohledu stojí na opačné straně než dynamicky typovaný jazyk Lua. Podívejme se nyní, jak je možné konverzi provést, zde konkrétně pro funkci vracející číslo (v Lua typu double, v Go pochopitelně float64):
if number, ok := ret.(lua.LNumber); ok { println("Value", float64(number)) }
Konverzi si vyzkoušíme na funkci, která sečte své parametry a vrátí výsledek tohoto součtu:
function add(a, b) print("1st parameter", a) print("2nd parameter", b) return a+b end
Zavolání této Lua funkce se zpracováním výsledků může vypadat následovně:
package main import ( "github.com/yuin/gopher-lua" "log" ) const LuaSource = "add.lua" func main() { luaVM := lua.NewState() log.Println("Lua VM has been initialized") defer func() { luaVM.Close() log.Println("Lua VM has been closed") }() err := luaVM.DoFile(LuaSource) if err != nil { log.Fatal(err) } err = luaVM.CallByParam(lua.P{ Fn: luaVM.GetGlobal("add"), NRet: 1, Protect: true, }, lua.LNumber(1), lua.LNumber(2)) if err != nil { log.Fatal(err) } ret := luaVM.Get(-1) luaVM.Pop(1) println("Type", ret.Type()) if number, ok := ret.(lua.LNumber); ok { println("Value", float64(number)) } }
Výsledek po spuštění:
2019/05/27 19:50:30 Lua VM has been initialized 1st parameter 1 2nd parameter 2 Type 2 Value +3.000000e+000 2019/05/27 19:50:30 Lua VM has been closed
13. Datový typ boolean v programovacím jazyce Lua
Pro Lua funkce, které vrací pravdivostní hodnoty true a false se návratové hodnoty konvertují tímto postupem:
println("Result", ret == lua.LTrue) println("Result", ret == lua.LFalse)
Ukažme si výše popsaný postup na praktickém příkladu, konkrétně na následující Lua funkci sloužící pro porovnání hodnot, kterou budeme volat s různými parametry:
function compare(a, b) print("1st parameter", a) print("2nd parameter", b) return a==b end
V programovacím jazyku Go by se volání této funkce a zpracování její návratové hodnoty mohlo realizovat například následujícím způsobem:
package main import ( "github.com/yuin/gopher-lua" "log" ) const LuaSource = "compare.lua" func compareNumbers(a int, b int) { luaVM := lua.NewState() log.Println("Lua VM has been initialized") defer func() { luaVM.Close() log.Println("Lua VM has been closed") }() err := luaVM.DoFile(LuaSource) if err != nil { log.Fatal(err) } err = luaVM.CallByParam(lua.P{ Fn: luaVM.GetGlobal("compare"), NRet: 1, Protect: true, }, lua.LNumber(a), lua.LNumber(b)) if err != nil { log.Fatal(err) } ret := luaVM.Get(-1) luaVM.Pop(1) println("Type", ret.Type()) println("Value", ret.String()) println("Result", ret == lua.LTrue) } func main() { compareNumbers(1, 2) compareNumbers(1, 1) }
Výsledky, které získáme po spuštění tohoto demonstračního příkladu:
2019/05/27 19:52:08 Lua VM has been initialized 1st parameter 1 2nd parameter 2 Type 1 Value false Result false 2019/05/27 19:52:08 Lua VM has been closed 2019/05/27 19:52:08 Lua VM has been initialized 1st parameter 1 2nd parameter 1 Type 1 Value true Result true 2019/05/27 19:52:08 Lua VM has been closed
14. Datový typ nil v programovacím jazyce Lua
Podobně je nutné správně pracovat s hodnotou a současně i datovým typem nil. Konverze a test, zda Lua funkce vrátila tuto hodnotu, může vypadat takto:
println("is nil?", ret == lua.LNil)
Opět si chování otestujme, tentokrát na velmi primitivní Lua funkci, která za všech okolností vrátí hodnotu nil:
function return_nil() return nil end
Samozřejmě si opět ukážeme úplný zdrojový kód příkladu, který tuto funkci zavolá a zobrazí její návratovou hodnotu:
package main import ( "github.com/yuin/gopher-lua" "log" ) const LuaSource = "return_nil.lua" func main() { luaVM := lua.NewState() log.Println("Lua VM has been initialized") defer func() { luaVM.Close() log.Println("Lua VM has been closed") }() err := luaVM.DoFile(LuaSource) if err != nil { log.Fatal(err) } err = luaVM.CallByParam(lua.P{ Fn: luaVM.GetGlobal("return_nil"), NRet: 1, Protect: true, }) if err != nil { log.Fatal(err) } ret := luaVM.Get(-1) luaVM.Pop(1) println("Type", ret.Type()) println("Value", ret.String()) println("is nil?", ret == lua.LNil) }
Výsledek:
2019/05/27 19:53:50 Lua VM has been initialized Type 0 Value nil is nil? true 2019/05/27 19:53:50 Lua VM has been closed
15. Zpracování většího množství návratových hodnot ze skriptu naprogramovaného v jazyce Lua
V programovacích jazycích Go a Lua je možné vracet větší množství návratových hodnot. Ukažme si nyní, jak se tyto hodnoty zpracují ve chvíli, kdy budeme mít Lua funkci vracející své parametry, ovšem v opačném pořadí:
function swap(a, b) print("1st parameter", a) print("2nd parameter", b) return b, a end
Návratové hodnoty můžeme získat ze zásobníku, pochopitelně v opačném pořadí (jde o zásobník). Taktéž nesmíme zapomenout na odstranění těchto hodnot ze zásobníku, aby došlo k jeho vyvážení:
ret1 := luaVM.Get(-2) ret2 := luaVM.Get(-1) luaVM.Pop(2)
Následuje výpis zdrojového kódu demonstračního příkladu, který výše uvedenou funkci swap() zavolá a zpracuje její výsledky:
package main import ( "github.com/yuin/gopher-lua" "log" ) const LuaSource = "swap.lua" func main() { luaVM := lua.NewState() log.Println("Lua VM has been initialized") defer func() { luaVM.Close() log.Println("Lua VM has been closed") }() err := luaVM.DoFile(LuaSource) if err != nil { log.Fatal(err) } err = luaVM.CallByParam(lua.P{ Fn: luaVM.GetGlobal("swap"), NRet: 2, Protect: true, }, lua.LNumber(1), lua.LNumber(2)) if err != nil { log.Fatal(err) } ret1 := luaVM.Get(-2) ret2 := luaVM.Get(-1) luaVM.Pop(2) println("Type", ret1.Type()) println("Type", ret2.Type()) if number, ok := ret1.(lua.LNumber); ok { println("Value #1", float64(number)) } if number, ok := ret2.(lua.LNumber); ok { println("Value #2", float64(number)) } }
16. Volání funkcí naprogramovaných v Go z Lua skriptu
V předchozím textu jsme se dozvěděli, jakým způsobem je možné z programovacího jazyka Go zavolat libovolnou funkci naprogramovanou ve skriptovacím jazyce Lua, jak se volané funkci předávají hodnoty a pochopitelně i to, jak se zpracovává případná návratová hodnota (hodnoty). Ovšem pro úplnou integraci Go+Lua je nutné zajistit i opačný způsob volání, tj. volání Go funkce z Lua skriptu. Můžeme si nejprve vyzkoušet, co se stane, pokud se pokusíme takovou funkci přímo zavolat bez toho, abychom ji jakýmkoli způsobem registrovali.
Samotný Lua skript, který zavolá (resp. pokusí se zavolat) funkci naprogramovanou v jazyce Go, vypadá takto:
function call_go() print("Hello from Lua!") hello() end
Funkci vytvořenou v jazyku Lua se můžeme pokusit zavolat v této aplikaci:
package main import ( "github.com/yuin/gopher-lua" "log" ) const LuaSource = "go_from_lua.lua" func hello(L *lua.LState) int { log.Println("Hello from Go!") return 0 } func main() { luaVM := lua.NewState() log.Println("Lua VM has been initialized") defer func() { luaVM.Close() log.Println("Lua VM has been closed") }() err := luaVM.DoFile(LuaSource) if err != nil { log.Fatal(err) } err = luaVM.CallByParam(lua.P{ Fn: luaVM.GetGlobal("call_go"), NRet: 0, Protect: true, }) if err != nil { log.Fatal(err) } }
Při pokusu o spuštění tohoto příkladu se zobrazí následující chybové hlášení:
2019/05/27 19:59:41 Lua VM has been initialized Hello from Lua! 2019/05/27 19:59:41 go_from_lua.lua:3: attempt to call a non-function object stack traceback: go_from_lua.lua:3: in main chunk [G]: ? exit status 1
Je tomu tak z toho prostého důvodu, že virtuální stroj skriptovacího jazyka Lua nemá žádnou informaci o tom, jak funkci zavolat – vůbec ji ve svém kontextu nezná.
17. Registrace funkce, která se má z Lua skriptů zavolat
Aby bylo možné Go funkci zavolat ze skriptu vytvořeného v jazyce Lua, je nutné takovou funkci nejprve korektně napsat. Tato funkce musí mít hlavičku:
func foobar(L *lua.LState) int { }
Dále je nutné tuto funkci zaregistrovat, a to pod jménem, jakým bude volána z Lua skriptu:
luaVM.SetGlobal("hello", luaVM.NewFunction(Hello))
Následně je již možné realizovat následující sekvenci volání:
- Funkce main naprogramovaná v Go volá funkci s Lua skriptu.
- Z Lua skriptu se zavolá funkce naprogramovaná v jazyku Go.
Vše je ukázáno v dalším (již předposledním) demonstračním příkladu:
package main import ( "github.com/yuin/gopher-lua" "log" ) const LuaSource = "go_from_lua.lua" func Hello(L *lua.LState) int { log.Println("Hello from Go!") return 0 } func main() { luaVM := lua.NewState() log.Println("Lua VM has been initialized") defer func() { luaVM.Close() log.Println("Lua VM has been closed") }() err := luaVM.DoFile(LuaSource) if err != nil { log.Fatal(err) } luaVM.SetGlobal("hello", luaVM.NewFunction(Hello)) err = luaVM.CallByParam(lua.P{ Fn: luaVM.GetGlobal("call_go"), NRet: 0, Protect: true, }) if err != nil { log.Fatal(err) } }
S výsledky:
2019/05/27 20:02:51 Lua VM has been initialized Hello from Lua! 2019/05/27 20:02:51 Hello from Go! 2019/05/27 20:02:51 Lua VM has been closed
18. Zpracování parametrů a návratových hodnot
V dnešním posledním demonstračním příkladu si ukážeme, jak se ve funkci vytvořené v jazyce Go zpracují parametry předané z Lua skriptu. Připomeňme si, že funkce v Go volaná z Lua musí mít tuto hlavičku:
func foobar(L *lua.LState) int { }
Všechny parametry předané z Lua skriptu jsou přenášeny přes zásobník, z něhož je musíme explicitně načíst, a to například následujícím způsobem:
a := L.ToInt(1) b := L.ToInt(2)
Výsledek se ukládá zpět na zásobník metodou Push():
L.Push(lua.LNumber(a + b))
Následuje výpis úplného zdrojového kódu příkladu, který toto dvojí volání realizuje:
package main import ( "github.com/yuin/gopher-lua" "log" ) const LuaSource = "go_from_lua_add.lua" func compute(L *lua.LState) int { log.Println("called from Lua") a := L.ToInt(1) b := L.ToInt(2) log.Printf("1st parameter %d\n", a) log.Printf("2nd parameter %d\n", b) L.Push(lua.LNumber(a + b)) return 1 } func main() { luaVM := lua.NewState() log.Println("Lua VM has been initialized") defer func() { luaVM.Close() log.Println("Lua VM has been closed") }() err := luaVM.DoFile(LuaSource) if err != nil { log.Fatal(err) } luaVM.SetGlobal("compute", luaVM.NewFunction(compute)) err = luaVM.CallByParam(lua.P{ Fn: luaVM.GetGlobal("add"), NRet: 1, Protect: true, }, lua.LNumber(1), lua.LNumber(2)) if err != nil { log.Fatal(err) } ret := luaVM.Get(-1) luaVM.Pop(1) println("Type", ret.Type()) println("Value", ret.String()) }
Po spuštění tohoto příkladu by se měly vypsat následující informace o zavolání Lua funkce (a o jejích parametrech) i o zavolání funkce compute naprogramované v jazyku Go:
2019/05/27 19:55:13 Lua VM has been initialized 1st parameter 1 2nd parameter 2 2019/05/27 19:55:13 called from Lua 2019/05/27 19:55:13 1st parameter 1 2019/05/27 19:55:13 2nd parameter 2 Type 2 Value 3 2019/05/27 19:55:13 Lua VM has been closed
19. Repositář s demonstračními příklady
Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně dva megabajty), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
Skripty naprogramované v jazyku Lua, které jsou demonstračními příklady používány:
20. Odkazy na Internetu
- Delve: a debugger for the Go programming language.
https://github.com/go-delve/delve - Příkazy debuggeru Delve
https://github.com/go-delve/delve/tree/master/Documentation/cli - Debuggery a jejich nadstavby v Linuxu
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu/ - Debuggery a jejich nadstavby v Linuxu (2. část)
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-2-cast/ - Debuggery a jejich nadstavby v Linuxu (3): Nemiver
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-3-nemiver/ - Debuggery a jejich nadstavby v Linuxu (4): KDbg
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-4-kdbg/ - Debuggery a jejich nadstavby v Linuxu (5): ladění aplikací v editorech Emacs a Vim
http://mojefedora.cz/debuggery-a-jejich-nadstavby-v-linuxu-5-ladeni-aplikaci-v-editorech-emacs-a-vim/ - Debugging Go Code with GDB
https://golang.org/doc/gdb - Debugging Go (golang) programs with gdb
https://thornydev.blogspot.com/2014/01/debugging-go-golang-programs-with-gdb.html - GDB – Dokumentace
http://sourceware.org/gdb/current/onlinedocs/gdb/ - GDB – Supported Languages
http://sourceware.org/gdb/current/onlinedocs/gdb/Supported-Languages.html#Supported-Languages - GNU Debugger (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Debugger - The LLDB Debugger
http://lldb.llvm.org/ - Debugger (Wikipedia)
https://en.wikipedia.org/wiki/Debugger - 13 Linux Debuggers for C++ Reviewed
http://www.drdobbs.com/testing/13-linux-debuggers-for-c-reviewed/240156817 - Go is on a Trajectory to Become the Next Enterprise Programming Language
https://hackernoon.com/go-is-on-a-trajectory-to-become-the-next-enterprise-programming-language-3b75d70544e - Go Proverbs: Simple, Poetic, Pithy
https://go-proverbs.github.io/ - Handling Sparse Files on Linux
https://www.systutorials.com/136652/handling-sparse-files-on-linux/ - Gzip (Wikipedia)
https://en.wikipedia.org/wiki/Gzip - Deflate
https://en.wikipedia.org/wiki/DEFLATE - 10 tools written in Go that every developer needs to know
https://gustavohenrique.net/en/2019/01/10-tools-written-in-go-that-every-dev-needs-to-know/ - Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním
https://www.root.cz/clanky/hexadecimalni-prohlizece-a-editory-s-textovym-uzivatelskym-rozhranim/ - Hex dump
https://en.wikipedia.org/wiki/Hex_dump - Rozhraní io.ByteReader
https://golang.org/pkg/io/#ByteReader - Rozhraní io.RuneReader
https://golang.org/pkg/io/#RuneReader - Rozhraní io.ByteScanner
https://golang.org/pkg/io/#ByteScanner - Rozhraní io.RuneScanner
https://golang.org/pkg/io/#RuneScanner - Rozhraní io.Closer
https://golang.org/pkg/io/#Closer - Rozhraní io.Reader
https://golang.org/pkg/io/#Reader - Rozhraní io.Writer
https://golang.org/pkg/io/#Writer - Typ Strings.Reader
https://golang.org/pkg/strings/#Reader - VACUUM (SQL)
https://www.sqlite.org/lang_vacuum.html - VACUUM (Postgres)
https://www.postgresql.org/docs/8.4/sql-vacuum.html - go-cron
https://github.com/rk/go-cron - gocron
https://github.com/jasonlvhit/gocron - clockwork
https://github.com/whiteShtef/clockwork - clockwerk
https://github.com/onatm/clockwerk - JobRunner
https://github.com/bamzi/jobrunner - Rethinking Cron
https://adam.herokuapp.com/past/2010/4/13/rethinking_cron/ - In the Beginning was the Command Line
https://web.archive.org/web/20180218045352/http://www.cryptonomicon.com/beginning.html - repl.it (REPL pro různé jazyky)
https://repl.it/languages - GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
https://github.com/jroimartin/gocui - Read–eval–print loop
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop - go-prompt
https://github.com/c-bata/go-prompt - readline
https://github.com/chzyer/readline - A pure golang implementation for GNU-Readline kind library
https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/ - go-readline
https://github.com/fiorix/go-readline - 4 Python libraries for building great command-line user interfaces
https://opensource.com/article/17/5/4-practical-python-libraries - prompt_toolkit 2.0.3 na PyPi
https://pypi.org/project/prompt_toolkit/ - python-prompt-toolkit na GitHubu
https://github.com/jonathanslenders/python-prompt-toolkit - The GNU Readline Library
https://tiswww.case.edu/php/chet/readline/rltop.html - GNU Readline (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Readline - readline — GNU readline interface (Python 3.x)
https://docs.python.org/3/library/readline.html - readline — GNU readline interface (Python 2.x)
https://docs.python.org/2/library/readline.html - GNU Readline Library – command line editing
https://tiswww.cwru.edu/php/chet/readline/readline.html - gnureadline 6.3.8 na PyPi
https://pypi.org/project/gnureadline/ - Editline Library (libedit)
http://thrysoee.dk/editline/ - Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/ - libedit or editline
http://www.cs.utah.edu/~bigler/code/libedit.html - WinEditLine
http://mingweditline.sourceforge.net/ - rlcompleter — Completion function for GNU readline
https://docs.python.org/3/library/rlcompleter.html - rlwrap na GitHubu
https://github.com/hanslub42/rlwrap - rlwrap(1) – Linux man page
https://linux.die.net/man/1/rlwrap - readline(3) – Linux man page
https://linux.die.net/man/3/readline - history(3) – Linux man page
https://linux.die.net/man/3/history - Dokumentace k balíčku oglematchers
https://godoc.org/github.com/jacobsa/oglematchers - Balíček oglematchers
https://github.com/jacobsa/oglematchers - Dokumentace k balíčku ogletest
https://godoc.org/github.com/jacobsa/ogletest - Balíček ogletest
https://github.com/jacobsa/ogletest - Dokumentace k balíčku assert
https://godoc.org/github.com/stretchr/testify/assert - Testify – Thou Shalt Write Tests
https://github.com/stretchr/testify/ - package testing
https://golang.org/pkg/testing/ - Golang basics – writing unit tests
https://blog.alexellis.io/golang-writing-unit-tests/ - An Introduction to Programming in Go / Testing
https://www.golang-book.com/books/intro/12 - An Introduction to Testing in Go
https://tutorialedge.net/golang/intro-testing-in-go/ - Advanced Go Testing Tutorial
https://tutorialedge.net/golang/advanced-go-testing-tutorial/ - GoConvey
http://goconvey.co/ - Testing Techniques
https://talks.golang.org/2014/testing.slide - 5 simple tips and tricks for writing unit tests in #golang
https://medium.com/@matryer/5-simple-tips-and-tricks-for-writing-unit-tests-in-golang-619653f90742 - Afinní transformace
https://cs.wikibooks.org/wiki/Geometrie/Afinn%C3%AD_transformace_sou%C5%99adnic - package gg
https://godoc.org/github.com/fogleman/gg - Generate an animated GIF with Golang
http://tech.nitoyon.com/en/blog/2016/01/07/go-animated-gif-gen/ - Generate an image programmatically with Golang
http://tech.nitoyon.com/en/blog/2015/12/31/go-image-gen/ - The Go image package
https://blog.golang.org/go-image-package - Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
https://github.com/llgcode/draw2d - Draw a rectangle in Golang?
https://stackoverflow.com/questions/28992396/draw-a-rectangle-in-golang - YAML
https://yaml.org/ - edn
https://github.com/edn-format/edn - Smile
https://github.com/FasterXML/smile-format-specification - Protocol-Buffers
https://developers.google.com/protocol-buffers/ - Marshalling (computer science)
https://en.wikipedia.org/wiki/Marshalling_(computer_science) - Unmarshalling
https://en.wikipedia.org/wiki/Unmarshalling - Introducing JSON
http://json.org/ - Package json
https://golang.org/pkg/encoding/json/ - The Go Blog: JSON and Go
https://blog.golang.org/json-and-go - Go by Example: JSON
https://gobyexample.com/json - Writing Web Applications
https://golang.org/doc/articles/wiki/ - Golang Web Apps
https://www.reinbach.com/blog/golang-webapps-1/ - Build web application with Golang
https://legacy.gitbook.com/book/astaxie/build-web-application-with-golang/details - Golang Templates – Golang Web Pages
https://www.youtube.com/watch?v=TkNIETmF-RU - Simple Golang HTTPS/TLS Examples
https://github.com/denji/golang-tls - Playing with images in HTTP response in golang
https://www.sanarias.com/blog/1214PlayingwithimagesinHTTPresponseingolang - MIME Types List
https://www.freeformatter.com/mime-types-list.html - Go Mutex Tutorial
https://tutorialedge.net/golang/go-mutex-tutorial/ - Creating A Simple Web Server With Golang
https://tutorialedge.net/golang/creating-simple-web-server-with-golang/ - Building a Web Server in Go
https://thenewstack.io/building-a-web-server-in-go/ - How big is the pipe buffer?
https://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer - How to turn off buffering of stdout in C
https://stackoverflow.com/questions/7876660/how-to-turn-off-buffering-of-stdout-in-c - setbuf(3) – Linux man page
https://linux.die.net/man/3/setbuf - setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
https://linux.die.net/man/3/setvbuf - Select waits on a group of channels
https://yourbasic.org/golang/select-explained/ - Rob Pike: Simplicity is Complicated (video)
http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893 - Algorithms to Go
https://yourbasic.org/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů: vlastní filtry a lexery
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu-vlastni-filtry-a-lexery/ - Go Defer Simplified with Practical Visuals
https://blog.learngoprogramming.com/golang-defer-simplified-77d3b2b817ff - 5 More Gotchas of Defer in Go — Part II
https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa - The Go Blog: Defer, Panic, and Recover
https://blog.golang.org/defer-panic-and-recover - The defer keyword in Swift 2: try/finally done right
https://www.hackingwithswift.com/new-syntax-swift-2-defer - Swift Defer Statement
https://andybargh.com/swift-defer-statement/ - Modulo operation (Wikipedia)
https://en.wikipedia.org/wiki/Modulo_operation - Node.js vs Golang: Battle of the Next-Gen Languages
https://www.hostingadvice.com/blog/nodejs-vs-golang/ - The Go Programming Language (home page)
https://golang.org/ - GoDoc
https://godoc.org/ - Go (programming language), Wikipedia
https://en.wikipedia.org/wiki/Go_(programming_language) - Go Books (kniha o jazyku Go)
https://github.com/dariubs/GoBooks - The Go Programming Language Specification
https://golang.org/ref/spec - Go: the Good, the Bad and the Ugly
https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/ - Package builtin
https://golang.org/pkg/builtin/ - Package fmt
https://golang.org/pkg/fmt/ - The Little Go Book (další kniha)
https://github.com/dariubs/GoBooks - The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
https://www.safaribooksonline.com/library/view/the-go-programming/9780134190570/ebook_split010.html - Learning Go
https://www.miek.nl/go/ - Go Bootcamp
http://www.golangbootcamp.com/ - Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
http://www.informit.com/store/programming-in-go-creating-applications-for-the-21st-9780321774637 - Introducing Go (Build Reliable, Scalable Programs)
http://shop.oreilly.com/product/0636920046516.do - Learning Go Programming
https://www.packtpub.com/application-development/learning-go-programming - The Go Blog
https://blog.golang.org/ - Getting to Go: The Journey of Go's Garbage Collector
https://blog.golang.org/ismmkeynote - Go (programovací jazyk, Wikipedia)
https://cs.wikipedia.org/wiki/Go_(programovac%C3%AD_jazyk) - Rychle, rychleji až úplně nejrychleji s jazykem Go
https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/ - Installing Go on the Raspberry Pi
https://dave.cheney.net/2012/09/25/installing-go-on-the-raspberry-pi - How the Go runtime implements maps efficiently (without generics)
https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics - Niečo málo o Go – Golang (slovensky)
http://golangsk.logdown.com/ - How Many Go Developers Are There?
https://research.swtch.com/gophercount - Most Popular Technologies (Stack Overflow Survery 2018)
https://insights.stackoverflow.com/survey/2018/#most-popular-technologies - Most Popular Technologies (Stack Overflow Survery 2017)
https://insights.stackoverflow.com/survey/2017#technology - JavaScript vs. Golang for IoT: Is Gopher Winning?
https://www.iotforall.com/javascript-vs-golang-iot/ - The Go Programming Language: Release History
https://golang.org/doc/devel/release.html - Go 1.11 Release Notes
https://golang.org/doc/go1.11 - Go 1.10 Release Notes
https://golang.org/doc/go1.10 - Go 1.9 Release Notes (tato verze je stále používána)
https://golang.org/doc/go1.9 - Go 1.8 Release Notes (i tato verze je stále používána)
https://golang.org/doc/go1.8 - Go on Fedora
https://developer.fedoraproject.org/tech/languages/go/go-installation.html - Writing Go programs
https://developer.fedoraproject.org/tech/languages/go/go-programs.html - The GOPATH environment variable
https://tip.golang.org/doc/code.html#GOPATH - Command gofmt
https://tip.golang.org/cmd/gofmt/ - The Go Blog: go fmt your code
https://blog.golang.org/go-fmt-your-code - C? Go? Cgo!
https://blog.golang.org/c-go-cgo - Spaces vs. Tabs: A 20-Year Debate Reignited by Google’s Golang
https://thenewstack.io/spaces-vs-tabs-a-20-year-debate-and-now-this-what-the-hell-is-wrong-with-go/ - 400,000 GitHub repositories, 1 billion files, 14 terabytes of code: Spaces or Tabs?
https://medium.com/@hoffa/400–000-github-repositories-1-billion-files-14-terabytes-of-code-spaces-or-tabs-7cfe0b5dd7fd - Gofmt No Longer Allows Spaces. Tabs Only
https://news.ycombinator.com/item?id=7914523 - Why does Go „go fmt“ uses tabs instead of whitespaces?
https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces - Interactive: The Top Programming Languages 2018
https://spectrum.ieee.org/static/interactive-the-top-programming-languages-2018 - Go vs. Python
https://www.peterbe.com/plog/govspy - PackageManagementTools
https://github.com/golang/go/wiki/PackageManagementTools - A Tour of Go: Type inference
https://tour.golang.org/basics/14 - Go Slices: usage and internals
https://blog.golang.org/go-slices-usage-and-internals - Go by Example: Slices
https://gobyexample.com/slices - What is the point of slice type in Go?
https://stackoverflow.com/questions/2098874/what-is-the-point-of-slice-type-in-go - The curious case of Golang array and slices
https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335 - Introduction to Slices in Golang
https://www.callicoder.com/golang-slices/ - Golang: Understanding ‚null‘ and nil
https://newfivefour.com/golang-null-nil.html - What does nil mean in golang?
https://stackoverflow.com/questions/35983118/what-does-nil-mean-in-golang - nils In Go
https://go101.org/article/nil.html - Go slices are not dynamic arrays
https://appliedgo.net/slices/ - Go-is-no-good (nelze brát doslova)
https://github.com/ksimka/go-is-not-good - Rust vs. Go
https://news.ycombinator.com/item?id=13430108 - Seriál Programovací jazyk Rust
https://www.root.cz/serialy/programovaci-jazyk-rust/ - Modern garbage collection: A look at the Go GC strategy
https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e - Go GC: Prioritizing low latency and simplicity
https://blog.golang.org/go15gc - Is Golang a good language for embedded systems?
https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems - Running GoLang on an STM32 MCU. A quick tutorial.
https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial - Go, Robot, Go! Golang Powered Robotics
https://gobot.io/ - Emgo: Bare metal Go (language for programming embedded systems)
https://github.com/ziutek/emgo - UTF-8 history
https://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt - Less is exponentially more
https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html - Should I Rust, or Should I Go
https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9 - Setting up and using gccgo
https://golang.org/doc/install/gccgo - Elastic Tabstops
http://nickgravgaard.com/elastic-tabstops/ - Strings, bytes, runes and characters in Go
https://blog.golang.org/strings - Datový typ
https://cs.wikipedia.org/wiki/Datov%C3%BD_typ - Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
https://www.root.cz/clanky/programovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09 - Seriál o programovacím jazyku Rust: Vytvoření „řezu“ z pole
https://www.root.cz/clanky/prace-s-poli-v-programovacim-jazyku-rust/#k06 - Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05 - Printf Format Strings
https://www.cprogramming.com/tutorial/printf-format-strings.html - Java: String.format
https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#format-java.lang.String-java.lang.Object…- - Java: format string syntax
https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax - Selectors
https://golang.org/ref/spec#Selectors - Calling Go code from Python code
http://savorywatt.com/2015/09/18/calling-go-code-from-python-code/ - Go Data Structures: Interfaces
https://research.swtch.com/interfaces - How to use interfaces in Go
http://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go - Interfaces in Go (part I)
https://medium.com/golangspec/interfaces-in-go-part-i-4ae53a97479c - Part 21: Goroutines
https://golangbot.com/goroutines/ - Part 22: Channels
https://golangbot.com/channels/ - [Go] Lightweight eventbus with async compatibility for Go
https://github.com/asaskevich/EventBus - What about Trait support in Golang?
https://www.reddit.com/r/golang/comments/8mfykl/what_about_trait_support_in_golang/ - Don't Get Bitten by Pointer vs Non-Pointer Method Receivers in Golang
https://nathanleclaire.com/blog/2014/08/09/dont-get-bitten-by-pointer-vs-non-pointer-method-receivers-in-golang/ - Control Flow
https://en.wikipedia.org/wiki/Control_flow - Structured programming
https://en.wikipedia.org/wiki/Structured_programming - Control Structures
https://www.golang-book.com/books/intro/5 - Control structures – Go if else statement
http://golangtutorials.blogspot.com/2011/06/control-structures-if-else-statement.html - Control structures – Go switch case statement
http://golangtutorials.blogspot.com/2011/06/control-structures-go-switch-case.html - Control structures – Go for loop, break, continue, range
http://golangtutorials.blogspot.com/2011/06/control-structures-go-for-loop-break.html - Different ways to pass channels as arguments in function in go (golang)
https://stackoverflow.com/questions/24868859/different-ways-to-pass-channels-as-arguments-in-function-in-go-golang - justforfunc #22: using the Go execution tracer
https://www.youtube.com/watch?v=ySy3sR1LFCQ - Single Function Exit Point
http://wiki.c2.com/?SingleFunctionExitPoint - Entry point
https://en.wikipedia.org/wiki/Entry_point - Why does Go have a GOTO statement?!
https://www.reddit.com/r/golang/comments/kag5q/why_does_go_have_a_goto_statement/ - Effective Go
https://golang.org/doc/effective_go.html - GoClipse: an Eclipse IDE for the Go programming language
http://goclipse.github.io/ - GoClipse Installation
https://github.com/GoClipse/goclipse/blob/latest/documentation/Installation.md#installation - The zero value of a slice is not nil
https://stackoverflow.com/questions/30806931/the-zero-value-of-a-slice-is-not-nil - Go-tcha: When nil != nil
https://dev.to/pauljlucas/go-tcha-when-nil–nil-hic - Nils in Go
https://www.doxsey.net/blog/nils-in-go