Role skriptovacích jazyků zabudovaných do aplikací
Co se dozvíte v článku
- Role skriptovacích jazyků zabudovaných do aplikací
- Obecné skriptovací jazyky versus doménově specifické jazyky (DSL)
- Skriptovací jazyky pro aplikace psané v Go
- Jazyk Go a skriptování v Pythonu
- Smyčka se uzavírá: Go v roli skriptovacího jazyka
- Skriptovací jazyk expr
- Instalace balíčku expr
- Kostra programu v Go, který přeloží a vyhodnotí jednoduchý výraz
- Vyhodnocování výrazů, volání funkcí dostupných ze skriptu
- Prostředí skriptu (environment), použití předaných parametrů ve skriptech
- Jednořádkové i víceřádkové výrazy s podmínkou
- Lokální proměnné definované ve skriptu
- Skripty volající nativní funkce jazyka Go dostupné přes prostředí (environment)
- Přetížení operátorů
- Zřetězení operací s využitím operátoru |
- Práce s chybějícími hodnotami: operátory ?? a ?.
- Překlad skriptů do AST
- Zobrazení výsledného AST
- Repositář s demonstračními příklady
- Odkazy na Internetu
Jak již bylo napsáno v perexu dnešního článku, existuje poměrně rozsáhlá skupina aplikací, která je vybavená nějakou podporou pro skriptování nebo alespoň více či méně sofistikovaným systémem zápisu příkazů a výrazů. To znamená, že je možné na uživatelské úrovni psát různá makra či dokonce celé nadstavby. Takové aplikace jsou rozšiřitelné a tím pádem i (většinou) dobře uzpůsobitelné potřebám uživatelů. To s sebou nese výhody pro všechny strany: uživatel si může aplikaci rozšířit či si toto rozšíření objednat, prodejce aplikace získá „zadarmo“ další oblasti, ve kterých se jeho aplikace bude moci použít (a tím pádem i větší uživatelskou resp. zákaznickou základnu) a otevírá se zde i prostor pro další vývojáře, kteří pracují právě na rozšiřujících makrech či dokonce rozsáhlejších modulech.
V současnosti není podpora skriptování v rozsáhlejších aplikacích ničím zvláštní a už vůbec ne revoluční. Příkladem mohou být například různé balíčky typu „office“ vybavené interpretrem jazyka (Visual) Basic, Python atd., či plně skriptovatelné webové prohlížeče (Firefox). Už v dobách kralování DOSu a (většinou) uzavřených CAD systémů umožnil AutoCAD, aby se na rozšiřování jeho funkcionality podíleli i zručnější uživatelé či další firmy, které tak vlastně firmě AutoDesk (tj. výrobci AutoCADu) zvyšovaly obrat, protože se AutoCAD mohl používat i v těch oblastech, pro které původně nebyl primárně určený.
Příkladem open source nástroje, který byl vybaven podporou pro skriptování už od svých raných verzí, je GIMP. Původně se v GIMPu mohly psát skripty pouze v programovacím jazyce Scheme (pro tyto skripty, resp. pro celou technologii, se používá název Script-Fu), ovšem v současnosti existuje i podpora pro populární programovací jazyk Python, který mimochodem některé své vlastnosti převzal právě ze Scheme a LISPu (což je ideový předchůdce Scheme). Skripty napsané ve Scheme či Pythonu umožňují vytvářet nové obrázky a provádět nad nimi základní grafické operace – kreslení na úrovni jednotlivých pixelů a taktéž kreslení s využitím již existujících kreslicích nástrojů, zejména tužky (pen), štětce (brush) či rozprašovače (airbrush).
Dalším příkladem open source aplikace, kterou si ani nelze představit bez podpory skriptování, je Emacs. V základní podobě se vlastně jedná o „engine“ textového editoru vybavená interpretrem jazyka Emacs Lisp (Elisp). A nad těmito technologiemi jsou postavené módy Emacsu, Spacemacs, slavný org-mode atd.
Obecné skriptovací jazyky versus doménově specifické jazyky (DSL)
V současnosti se mnohdy setkáme s podporou skriptování založenou na jazyku Lua (ten se pro podobné účely skutečně hodí), JavaScriptu či Python. Ovšem může se jednat i o specializované jazyky. Mnohé z těchto doménově specifických jazyků nejsou Turingovsky kompletní (úplné), což však nelze považovat za nedostatek, ale mnohdy je to naopak požadovaná vlastnost. Příkladem mohou být DSL, v nichž není možné zapsat programové smyčky ani rekurzi – tudíž je většinou výpočet resp. vyhodnocení výrazu časově dosti přesně určené. Další DSL neumožňují explicitní alokaci paměti, což může být v dané oblasti použití taktéž výhodné. Nasazení DSL oproti plnohodnotnému jazyku tedy může být výhodné, protože cíleně omezené možnosti takového jazyka můžeme chápat jako formu „sémantického sandboxingu“ (ostatně právě proto jsou regulární výrazy regulární). Jazyk expr, který bude popsán v praktické části dnešního článku, patří do skupiny jazyků, které mají naschvál omezenou sémantiku – chybí smyčky, rekurze atd.
Mezi obecné skriptovací jazyky patří například:
| Skriptovací jazyk |
|---|
| AppleScript |
| AWK |
| BeanShell |
| Bash |
| Ch |
| ActionScript |
| JavaScript |
| Game Maker Language |
| Julia |
| Groovy |
| Korn shell |
| Lua |
| Perl |
| PHP |
| PowerShell |
| Python |
| R |
| Rebol |
| Rexx |
| S-lang |
| Tcl |
| VBScript |
Skriptovací jazyky pro aplikace psané v Go
V seriálu o programovacím jazyce Go jsme se již několikrát zmínili o možnosti zkombinování tohoto staticky typovaného a překládaného (kompilovaného) programovacího jazyka s nějakým interpretrem, typicky s interpretrem vhodného vyššího programovacího jazyka. Díky této kombinaci je například možné „skriptovat“ aplikaci naprogramovanou v Go, rozšiřovat tuto aplikaci o další logiku, upravovat či definovat nová „business“ pravidla atd. (viz úvodní kapitolu s příklady podobně koncipovaných aplikací).
Prozatím jsme se v praktické části seriálu o Go věnovali popisu způsobů vestavění interpretru programovacího jazyka Lua do aplikace naprogramované v Go. Kombinace Lua+Go, resp. obecněji řečeno Lua+staticky typovaný překládaný jazyk, je velmi běžná a vlastně i logická, protože právě jazyk Lua je navržen s ohledem na jeho relativně snadnou „vložitelnost“ (embed) do větších aplikací. 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í (viz též odkazy na konci dnešního článku popř. přímo článek Skriptovací jazyk Lua v aplikacích naprogramovaných v Go).
Jazyk Go a skriptování v Pythonu
V současnosti se v roli vestavěného skriptovacího jazyka využívá i Python, což je poněkud zvláštní, protože Python nebyl pro tyto účely ani navržen ani optimalizován. Pro Go vzniklo hned několik balíčků a nástrojů, které se snaží o integraci těchto dvou jazyků. Příkladem jsou projekty Gopy, Grumpy či GPython (ovšem některé z těchto nástrojů již nejsou vyvíjeny). Každý z těchto nástrojů pojímá integraci odlišným způsobem. Gopy překládá balíčky v Pythonu tak, aby je bylo možné volat z Go, Grumpy je transpřekladačem z Pythonu do Go (ovšem pro starší verzi Pythonu) a gpython představuje pokus o reimplementaci Pythonu v Go. Žádný z těchto nástrojů však není (alespoň podle mého názoru) vhodný pro produkční nasazení.
Poněkud nízkoúrovňovou možností je volání API interpretru Pythonu přímo z jazyka Go. Zde se pochopitelně musí využívat cgo se všemi nevýhodami, které toto řešení přináší:
// #include <Python.h>
import "C"
func main() {
C.Py_Initialize()
defer C.Py_Finalize()
pCode := C.CString("import sys; print('Hello from Python')")
C.PyRun_SimpleString(pCode)
C.free(unsafe.Pointer(pCode))
}
Alternativou je integrace opačným směrem, tedy volání funkcí a metod napsaných v Go z Pythonu. Tímto tématem jsme se již zabývali v článcích:
- Propojení Go s Pythonem s využitím cgo a ctypes
https://www.root.cz/clanky/propojeni-go-s-pythonem-s-vyuzitim-cgo-a-ctypes/ - Propojení Go s Pythonem s využitím cgo a ctypes (2. část)
https://www.root.cz/clanky/propojeni-go-s-pythonem-s-vyuzitim-cgo-a-ctypes-2-cast/
Smyčka se uzavírá: Go v roli skriptovacího jazyka
Jen pro úplnost se zmiňme o tom, že i samotný jazyk Go může být použit v roli skriptovacího jazyka, což je ovšem pro staticky typovaný a (obecně) překládaný jazyk poněkud zvláštní role.
Prvním interpretrem programovacího jazyka Go, o kterém se dnes zmíníme, je interpret nazvaný Gore. S tímto projektem jsme se již v seriálu o programovacím jazyce Go jednou setkali. Připomeňme si, že tento projekt nabízí (z pohledu uživatele – programátora) interpret plnohodnotného jazyka Go, který je navíc vybaven interaktivní smyčkou REPL. Ta nabízí základní editační příkazy, historii zadaných příkazů, hledání v historii příkazů atd. – tedy dnes již standardní a očekávané vlastnosti.
Díky využití projektu gocode nabízí interaktivní smyčka REPL i automatické doplňování příkazů, což je samozřejmě velmi užitečná technologie (a opět – dnes již očekávaná jako standard). Projekt Gore je dostupný na adrese https://github.com/x-motemen/gore. Interně je řešen vlastně dosti triviálním způsobem: používá totiž příkaz go run pro překlad a spuštění zadávaného kódu po zápisu každého příkazu nebo bloku. Díky rychlosti překladače jazyka Go je sice toto zdržení nepatrné a pravděpodobně si ho v případě menších skriptů ani nevšimnete, nicméně se nejedná o standardní chování, které se od interpretrů očekává a taktéž se některé příkazy a knihovní funkce mohou chovat „divně“, protože se ztrácí kontext. Například se může jednat o přečtení času, vygenerování náhodného čísla, přístup do databáze atd.
Druhý interpret, o kterém se dnes zmíníme, se jmenuje Yaegi. Tento interpret pracuje běžným způsobem (tj. bez automatického generování Go kódu) a je určen především pro vložení (embedding) do dalších aplikací. Interaktivní smyčka REPL je sice taktéž implementována, ale nenabízí příliš mnoho vlastností, které jsou dnes očekávány: automatické doplňování příkazů, editace vstupního řádku, historie příkazů atd.
Další interpret programovacího jazyka Go se jmenuje Gomacro. S tímto interpretrem jsme se již dříve setkali v článku Gophernotes: kombinace interaktivního prostředí Jupyteru s jazykem Go, protože jazyk Go je do prostředí Jupyter Notebooku integrován právě s využitím tohoto interpretru. Ovšem Gomacro není navázáno na Jupyter Notebook a lze ho použít samostatně (ovládá se tedy z terminálu s využitím REPLu, jak je dobrým zvykem). Podobně, jako tomu bylo u výše uvedených interpretrů, i zde jsou možnosti programovacího jazyka Go rozšířeny o další pseudopříkazy. A zapomenout nesmíme ani na debugger, který je součástí tohoto interpretru.
Skriptovací jazyk expr
Konečně se dostáváme k ústřednímu tématu dnešnímu článku, tj. k popisu jazyka nazvaného jednoduše (a v tomto případě velmi přesně) expr. Jedná se o doménově specifický jazyk (DSL), který umožňuje zápis jednoduchých výrazů (odtud je ostatně odvozeno i jeho jméno), ale například i podmínek a rozvětvení typu if/else atd. Tento jazyk dokáže pracovat s datovými typy známými přímo z Go, a to včetně polí a map. Zajímavé je, že i když se jedná o skriptovací jazyk, je prováděna silná typová kontrola zapsaných skriptů.
Důležité jsou i bezpečnostní záruky, které expr nabízí. V první řadě nemají skripty přístup k žádným funkcím ani metodám Go programu. Pokud je vyžadováno, aby skripty nějaké takové funkce či metody volaly, musí se jejich reference explicitně předat do prostředí skriptu (což si pochopitelně ukážeme). Skripty jsou spuštěny tak, že jsou od sebe izolovány, takže například „nevidí“ proměnné jiných skriptů. A další zajímavou vlastností je, že expr neumožňuje zápis smyček a vlastně ani rekurze, takže lze odhadnout, že všechny skripty budou dokončeny (nezůstanou počítat nekonečnou smyčku apod.). Tento jazyk tedy není Turingovsky kompletní, ovšem to je právě jeho výhodou.
Další informace o jazyku expr je možné najít v jeho GitHub repositáři nebo v online dokumentaci.
Instalace balíčku expr
Překladač jazyka expr je dostupný ve formě běžného balíčku jazyka Go, takže jeho instalace (do projektu) je provedena standardním způsobem. Nejdříve si necháme vygenerovat kostru nového projektu:
$ go mod init go-expr go: creating new go.mod: module go-expr
Dále do nově vytvořeného projektu nainstalujeme balíček expr:
$ go get github.com/expr-lang/expr go: added github.com/expr-lang/expr v1.17.8
Projektový soubor go.mod by měl vypadat takto:
module go-expr go 1.24.10 require github.com/expr-lang/expr v1.17.8 // indirect
Soubor go.sum (automaticky generovaný příkazem go mod) by měl mít následující obsah:
github.com/expr-lang/expr v1.17.8 h1:W1loDTT+0PQf5YteHSTpju2qfUfNoBt4yw9+wOEU9VM= github.com/expr-lang/expr v1.17.8/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
Kostra programu v Go, který přeloží a vyhodnotí jednoduchý výraz
Kostra programu, ve kterém se přeloží skript, resp. přesněji řečeno jediný výraz, do bajtkódu s jeho následným spuštěním, vypadá následovně. Funkce jednotlivých částí programu jsou popsány v komentářích:
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
// definice (prázdného) prostředí skriptu
env := map[string]interface{}{}
// zdrojový kód s výrazem (nezapisují se žádné středníky atd.)
code := `6*7`
// překlad skriptu (výrazu) do bajtkódu
fmt.Println("Compiling:", code)
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
panic(err)
}
fmt.Println("Compiled")
fmt.Println("Calling compiled program:")
// spuštění přeloženého skriptu
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
// pokud nedošlo k chybě, bude proměnná output obsahovat vypočtený výsledek
fmt.Println("Output:", output)
}
Překlad se provede standardním způsobem:
$ go build expression_1.go
Výsledkem je spustitelný binární soubor, který ovšem poněkud „nabobtnal“ na velikost přesahující 5 MB:
$ ls -l expression_1 -rwxr-xr-x. 1 ptisnovs ptisnovs 5524171 May 24 19:32 expression_1
Výsledný spustitelný soubor skutečně spustíme a ověříme si, že se provede překlad skriptu do bajtkódu s jeho následným spuštěním:
$ ./expression_1 Compiling: 6*7 Compiled Calling compiled program: Output: 42
Vyhodnocování výrazů, volání funkcí dostupných ze skriptu
Výrazy jsou v jazyku expr vyhodnocovány stejným způsobem, jako je tomu v mainstreamových programovacích jazycích, tj. jsou aplikována základní pravidla pro jejich prioritu a asociativitu. Dostupné jsou základní aritmetické operátory, a to včetně operace dělení modulo a ^ či ** pro umocnění. Relační operátory jsou zcela standardní, stejně jako logické operátory zapisované buď podobně jako v jazyce Go (speciálními znaky) či slovy and, or a not. Ovšem jazyk expr obsahuje i některé speciální operátory, s nimiž se seznámíme v dalších kapitolách.
V dalším příkladu je vyhodnocen složitější výraz se závorkami a s operátory s různými prioritami:
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
env := map[string]interface{}{}
code := `1+2+3+3*(5+7)`
fmt.Println("Compiling:", code)
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
panic(err)
}
fmt.Println("Compiled")
fmt.Println("Calling compiled program:")
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println("Output:", output)
}
Výsledek:
Compiling: 1+2+3+3*(5+7) Compiled Calling compiled program: Output: 42
Ve výrazech je možné volat i standardní funkce dostupné z interpretru. V případě numerických hodnot se jedná o funkce min, max, abs, ceil, floor a round (tedy o naprostý základ):
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
env := map[string]interface{}{}
code := `abs(-6)*7`
fmt.Println("Compiling:", code)
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
panic(err)
}
fmt.Println("Compiled")
fmt.Println("Calling compiled program:")
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println("Output:", output)
}
Opět se podívejme na výsledky:
Compiling: abs(-6)*7 Compiled Calling compiled program: Output: 42
Prostředí skriptu (environment), použití předaných parametrů ve skriptech
Velmi důležitý je koncept prostředí (environment). Jedná se o plnou definici symbolů, které jsou ze skriptu dostupné. Typicky se jedná o pojmenované parametry předávané do skriptu, například:
env := map[string]interface{}{
"foo": 6,
"bar": 7,
}
Ve skriptu je (samozřejmě pokud prostředí správně nadefinujeme) možné v tomto případě pracovat se symboly foo a bar, přičemž jsou tyto symboly navázány na hodnoty 6 a 7:
code := `foo*bar`
Ukažme si to na uceleném demonstračním příkladu:
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
env := map[string]interface{}{
"foo": 6,
"bar": 7,
}
code := `foo*bar`
fmt.Println("Compiling:", code)
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
panic(err)
}
fmt.Println("Compiled")
fmt.Println("Calling compiled program:")
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println("Output:", output)
}
Výsledek naznačuje, že se skutečně s foo a bar pracuje jako s pojmenovanými parametry:
Compiling: foo*bar Compiled Calling compiled program: Output: 42
Samozřejmě jsou podporovány i další operace s parametry foo a bar:
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
env := map[string]interface{}{
"foo": -6,
"bar": 7,
}
code := `abs(foo)*bar`
fmt.Println("Compiling:", code)
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
panic(err)
}
fmt.Println("Compiled")
fmt.Println("Calling compiled program:")
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println("Output:", output)
}
Výsledek opět odpovídá očekávání:
Compiling: abs(foo)*bar Compiled Calling compiled program: Output: 42
Dtto, ovšem nyní pro parametry, které obsahují řetězce:
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
env := map[string]interface{}{
"foo": "ja jsem foo",
"bar": "ja jsem bar",
}
code := `foo+", " + bar`
fmt.Println("Compiling:", code)
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
panic(err)
}
fmt.Println("Compiled")
fmt.Println("Calling compiled program:")
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println("Output:", output)
}
Výsledky:
Compiling: foo+", " + bar Compiled Calling compiled program: Output: ja jsem foo, ja jsem bar
Jednořádkové i víceřádkové výrazy s podmínkou
Mnohdy se v praxi setkáme s nutností práce s podmínkami, resp. přesněji řečeno s rozvětvením výpočtu na základě nějaké (vyhodnocené) podmínky. Jazyk expr podporuje ternární operátor, jenž například přímo v jazyce Go chybí. Jeho použití je následující:
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
env := map[string]interface{}{
"foo": -6,
"bar": 7,
}
code := `foo < 0 ? -foo*bar : foo*bar`
fmt.Println("Compiling:", code)
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
panic(err)
}
fmt.Println("Compiled")
fmt.Println("Calling compiled program:")
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println("Output:", output)
}
Na základě vyhodnocené podmínky se vykoná a vrátí buď výraz přímo za otazníkem, nebo naopak výraz za dvojtečkou:
Compiling: foo < 0 ? -foo*bar : foo*bar Compiled Calling compiled program: Output: 42
Ovšem použít lze i klíčová slova if a else. V následujícím příkladu je ukázáno, že se za těmito slovy nachází programové bloky zapisované do složených závorek. Ovšem důležitější je sémantika – poslední výraz v bloku je současně i hodnotou celého výrazu a v tomto případě i návratovou hodnotou skriptu (není nutné a ani to není možné zapisovat return):
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
env := map[string]interface{}{
"foo": -6,
"bar": 7,
}
code := `if foo < 0 {-foo*bar} else {foo*bar}`
fmt.Println("Compiling:", code)
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
panic(err)
}
fmt.Println("Compiled")
fmt.Println("Calling compiled program:")
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println("Output:", output)
}
Opět se podívejme na výsledky:
Compiling: if foo < 0 {-foo*bar} else {foo*bar}
Compiled
Calling compiled program:
Output: 42
Lokální proměnné definované ve skriptu
Ve skriptech naprogramovaných v jazyku expr je možné definovat lokální proměnné, a to formou příkazu začínajícího klíčovým slovem let. Vzhledem k tomu, že se jedná o příkaz, je nutné ho ukončit středníkem. Posledním řádkem skriptu je ovšem výraz, jehož vyhodnocená hodnota je současně i návratovou hodnotou skriptu (zde tedy středník být nemusí):
let foo = 6;
let bar = 7;
foo*bar
Samozřejmě si tento zápis opět ověříme překladem a spuštěním skriptu:
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
env := map[string]interface{}{}
code := `
let foo = 6;
let bar = 7;
foo*bar
`
fmt.Println("Compiling:", code)
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
panic(err)
}
fmt.Println("Compiled")
fmt.Println("Calling compiled program:")
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println("Output:", output)
}
Výsledek by měl v tomto případě vypadat následovně:
Compiling:
let foo = 6;
let bar = 7;
foo*bar
Compiled
Calling compiled program:
Output: 42
V jazyce expr je možné používat i pole, což může vypadat následovně. Povšimněte si velmi jednoduchého zápisu konstruktoru pole:
let array = [10,20,30,40];
array[1]
Otestování funkcionality i faktu, že prvky pole jsou číslovány od nuly:
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
env := map[string]interface{}{}
code := `
let array = [10,20,30,40];
array[1]
`
fmt.Println("Compiling:", code)
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
panic(err)
}
fmt.Println("Compiled")
fmt.Println("Calling compiled program:")
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println("Output:", output)
}
Výsledky získané po překladu a spuštění:
Compiling:
let array = [10,20,30,40];
array[1]
Compiled
Calling compiled program:
Output: 20
Možnost definice proměnných ve skriptu lze (pochopitelně) zkombinovat s řídicí strukturou if-else. V každé větvi se může nacházet libovolné množství příkazů a na konci výraz, který je vrácen:
if foo < 0 {
let result = -foo*bar;
result
} else {
let result = foo*bar;
result
}
Otestování:
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
env := map[string]interface{}{
"foo": -6,
"bar": 7,
}
code := `
if foo < 0 {
let result = -foo*bar;
result
} else {
let result = foo*bar;
result
}`
fmt.Println("Compiling:", code)
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
panic(err)
}
fmt.Println("Compiled")
fmt.Println("Calling compiled program:")
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println("Output:", output)
}
A výsledek:
Compiling:
if foo < 0 {
let result = -foo*bar;
result
} else {
let result = foo*bar;
result
}
Compiled
Calling compiled program:
Output: 42
Zápis struktury if-else ovšem může být oproti předchozímu skriptu rozdílný. Například (na rozdíl od Go) nezáleží na umístění složených závorek atd., takže i následující zdrojový kód se skriptem je plně korektní:
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
env := map[string]interface{}{
"foo": -6,
"bar": 7,
}
code := `
if foo < 0
{
let result = -foo*bar;
result
}
else
{
let result = foo*bar;
result
}`
fmt.Println("Compiling:", code)
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
panic(err)
}
fmt.Println("Compiled")
fmt.Println("Calling compiled program:")
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println("Output:", output)
}
Výsledek:
Compiling:
if foo < 0
{
let result = -foo*bar;
result
}
else
{
let result = foo*bar;
result
}
Compiled
Calling compiled program:
Output: 42
Skripty volající nativní funkce jazyka Go dostupné přes prostředí (environment)
V úvodní části dnešního článku jsme si řekli, že jednou z důležitých vlastností jazyka expr je fakt, že ze skriptů není možné volat libovolnou funkci nebo metodu definovanou v Go. Jedná se o formu sandboxingu, která zvyšuje bezpečnost aplikace. Jak se však má postupovat v případě, kdy naopak vyžadujeme, aby byla nějaká funkce dostupná? (ostatně skripty bez přístupu k nativnímu kódu do značné míry postrádají smysl). Řešení je snadné – do prostředí (environment) skriptu se kromě případných parametrů předají i (pojmenované) reference na funkce.
Příkladem může být implementace skriptu, který zobrazí text „Hello, world“. Aby to bylo možné, bylo nutné do prostředí skriptu přidat i referenci na funkci fmt.Println:
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
env := map[string]interface{}{
"println": fmt.Println,
}
code := `println("Hello, world!")`
fmt.Println("Compiling:", code)
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
panic(err)
}
fmt.Println("Compiled")
fmt.Println("Calling compiled program:")
_, err = expr.Run(program, env)
if err != nil {
panic(err)
}
}
Otestování:
Compiling: println("Hello, world!")
Compiled
Calling compiled program:
Hello, world!
Ukažme si pro úplnost způsob předání mapy do skriptu. Skriptu předáme mapu s informacemi o verzi a skript z obsahu mapy složí řetězec, který následně vytiskne:
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
env := map[string]interface{}{
"name": "Go",
"version": map[string]int{
"major": 1,
"minor": 26,
"patch": 3,
},
"println": fmt.Println,
}
code := `
let major = string(version["major"]);
let minor = string(version["minor"]);
let patch = string(version["patch"]);
let msg = name + " " + major + "." + minor + "." + patch;
println(msg)
`
fmt.Println("Compiling:", code)
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
panic(err)
}
fmt.Println("Compiled")
fmt.Println("Calling compiled program:")
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println("Output:", output)
}
Výsledek testu:
Compiling:
let major = string(version["major"]);
let minor = string(version["minor"]);
let patch = string(version["patch"]);
let msg = name + " " + major + "." + minor + "." + patch;
println(msg)
Compiled
Calling compiled program:
Go 1.26.3
Output: 10
Jazyk expr se v jednom ohledu podobá jazyku Lua – při přístupu k prvkům mapy je totiž možné použít i tečkovou notaci mapa.selektor, nikoli zápis mapa[selektor]. I tuto vlastnost si pochopitelně ukážeme:
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
env := map[string]interface{}{
"name": "Go",
"version": map[string]int{
"major": 1,
"minor": 26,
"patch": 3,
},
"println": fmt.Println,
}
code := `
let major = string(version.major);
let minor = string(version.minor);
let patch = string(version.patch);
let msg = name + " " + major + "." + minor + "." + patch;
println(msg)
`
fmt.Println("Compiling:", code)
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
panic(err)
}
fmt.Println("Compiled")
fmt.Println("Calling compiled program:")
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println("Output:", output)
}
Výsledek:
Compiling:
let major = string(version.major);
let minor = string(version.minor);
let patch = string(version.patch);
let msg = name + " " + major + "." + minor + "." + patch;
println(msg)
Compiled
Calling compiled program:
Go 1.26.3
Output: 10
Přetížení operátorů
Další zajímavou vlastností jazyka expr je možnost přetížit jeho operátory. Ukažme si, jak se přetíží operátor * pro dvojici celých čísel (typová kontrola je prováděna před spuštěním skriptu). Nejdříve si nadefinujeme pomocnou funkci s realizací potřebné operace:
func mul(x, y int) int {
return 2 * x * y
}
Následně nesmíme zapomenout na „registraci“ funkce:
env := map[string]interface{}{
"foo": 6,
"bar": 7,
"mul": mul,
}
Nový operátor se registruje v době překladu skriptu. Povšimněte si, že provádíme mapování mezi zápisem operátoru * a jménem uloženým v prostředí skriptu:
program, err := expr.Compile(code, expr.Env(env), expr.Operator("*", "mul"))
Otestování:
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func mul(x, y int) int {
return 2 * x * y
}
func main() {
env := map[string]interface{}{
"foo": 6,
"bar": 7,
"mul": mul,
}
code := `foo*bar`
fmt.Println("Compiling:", code)
program, err := expr.Compile(code, expr.Env(env), expr.Operator("*", "mul"))
if err != nil {
panic(err)
}
fmt.Println("Compiled")
fmt.Println("Calling compiled program:")
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println("Output:", output)
}
Výsledky:
Compiling: foo*bar Compiled Calling compiled program: Output: 84
Abychom si ověřili, že se skutečně volá nová implementace operátoru, přetížíme jeden relační operátor. Jeho nová implementace je realizována Go funkcí compare, která kromě výpočtu relace zobrazí prováděnou operaci:
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func compare(x, y int) bool {
fmt.Printf("Comparing %d with %d\n", x, y)
return x < y
}
func main() {
env := map[string]interface{}{
"foo": 6,
"bar": 7,
"lt": compare,
}
code := `foo<bar`
fmt.Println("Compiling:", code)
program, err := expr.Compile(code, expr.Env(env), expr.Operator("<", "lt"))
if err != nil {
panic(err)
}
fmt.Println("Compiled")
fmt.Println("Calling compiled program:")
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println("Output:", output)
}
Ze zobrazených zpráv je patrné, že se skutečně volá nová implementace operátoru:
Compiling: foo<bar Compiled Calling compiled program: Comparing 6 with 7 Output: true
Zřetězení operací s využitím operátoru |
Další potenciálně užitečná vlastnost programovacího jazyka expr spočívá v možnosti použití operátoru | (pipe) pro zřetězení operací, tedy pro vytvoření kolony známé z unixových systémů a převzaté i do DOSu/Windows. Výsledek výpočtu na levé straně operátoru | je předán funkci na straně pravé:
foo | abs() * bar
Otestování této technologie je snadné:
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
env := map[string]interface{}{
"foo": -6,
"bar": 7,
}
code := `foo | abs() * bar`
fmt.Println("Compiling:", code)
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
panic(err)
}
fmt.Println("Compiled")
fmt.Println("Calling compiled program:")
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println("Output:", output)
}
Výsledky ukazují, že operátor kolony je skutečně interpretován korektně::
Compiling: foo | abs() * bar Compiled Calling compiled program: Output: 42
Ovšem zápis může být i složitější a může vypadat například takto:
foo | abs() * bar | negate()
Otestování:
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
env := map[string]interface{}{
"foo": -6,
"bar": 7,
"negate": func(x int) int { return -x },
}
code := `foo | abs() * bar | negate()`
fmt.Println("Compiling:", code)
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
panic(err)
}
fmt.Println("Compiled")
fmt.Println("Calling compiled program:")
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println("Output:", output)
}
Výsledky:
Compiling: foo | abs() * bar | negate() Compiled Calling compiled program: Output: -42
Práce s chybějícími hodnotami: operátory ?? a ?.
V praxi se velmi často musí pracovat s chybějícími či volitelnými hodnotami. Takové hodnoty jsou v jazyku expr (ale i v Go, a když jsme u toho, tak i v jazyce Lua či LISPech) reprezentovány typem+hodnotou nil. Aby nebylo nutné neustále zapisovat podmínku ve stylu if xyzzy != nil (což programátoři používající jazyk Go velmi dobře znají), obsahuje expr dvojici operátorů ?? a ?..
Operátor ?? nahrazuje podmínku na hodnotu nil. Konkrétně:
foo ?? hodnota
odpovídá zápisu:
foo != nil ? foo : hodnota
To nám umožňuje přístup k parametrům, které mohou, ale také nemusí být nastaveny (jsou rovny nil). Povšimněte si, že pro úplnost předáváme i parametr bar nastavený na false, abychom si ověřili, že se v jazyce expr rozlišuje mezi nil a false:
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
env := map[string]interface{}{
"foo": 1,
"bar": false,
"baz": nil,
"println": fmt.Println,
}
code := `
println("foo=", foo ?? "*** nil ***");
println("bar=", bar ?? "*** nil ***");
println("baz=", baz ?? "*** nil ***");
`
fmt.Println("Compiling:", code)
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
panic(err)
}
fmt.Println("Compiled")
fmt.Println("Calling compiled program:")
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println("Output:", output)
}
Výsledky (především je zajímavý řádek s baz=):
Compiling:
println("foo=", foo ?? "*** nil ***");
println("bar=", bar ?? "*** nil ***");
println("baz=", baz ?? "*** nil ***");
Compiled
Calling compiled program:
foo= 1
bar= false
baz= *** nil ***
Output: 17
Druhý operátor .? realizuje přístup k prvkům mapy nebo struktury, který může být nastaven na nil. Mnohdy totiž potřebujeme provést výraz typu:
struktura.prvek1.prvek2.prvek3
Ovšem pokud mohou být prvky nastaveny na nil, vedlo by to k běhové chybě. Řešení spočívá v zápisu:
struktura?.prvek1?.prvek2?.prvek3
Ovšem musíme si dát pozor na správné uvedení typů. Následující skript nebude spuštěn. Překlad skončí chybou, protože hodnota nil přiřazená do prvku „user3“ nemá korektní typ:
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
type User struct {
Name string
}
func main() {
user1 := new(User)
user1.Name = "Pavel"
user2 := new(User)
user2.Name = "Petr"
env := map[string]interface{}{
"user1": user1,
"user2": user2,
"user3": nil,
"println": fmt.Println,
}
code := `
println(user1?.Name);
println(user2?.Name);
println(user3?.Name);
`
fmt.Println("Compiling:", code)
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
panic(err)
}
fmt.Println("Compiled")
fmt.Println("Calling compiled program:")
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println("Output:", output)
}
Výsledek:
Compiling:
println(user1?.Name);
println(user2?.Name);
println(user3?.Name);
panic: type nil has no field Name (4:21)
| println(user3?.Name);
| ....................^
goroutine 1 [running]:
main.main()
/tmp/ramdisk/go-expr/optional_1.go:36 +0x371
exit status 2
Korektní zápis vypadá následovně:
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
type User struct {
Name string
}
func main() {
user1 := new(User)
user1.Name = "Pavel"
user2 := new(User)
user2.Name = "Petr"
env := map[string]interface{}{
"user1": user1,
"user2": user2,
"user3": (*User)(nil),
"println": fmt.Println,
}
code := `
println(user1?.Name);
println(user2?.Name);
println(user3?.Name);
`
fmt.Println("Compiling:", code)
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
panic(err)
}
fmt.Println("Compiled")
fmt.Println("Calling compiled program:")
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println("Output:", output)
}
Nyní již vše proběhne v pořádku:
Compiling:
println(user1?.Name);
println(user2?.Name);
println(user3?.Name);
Compiled
Calling compiled program:
Pavel
Petr
<nil>
Output: 6
Překlad skriptů do AST
Při zpracování skriptů zapsaných v jazyku expr se postupně provádí jednotlivé dílčí kroky. Díky rozdělení celého zpracování do několika od sebe do značné míry izolovaných kroků je zajištěna velká flexibilita a možnost případného relativně snadného rozšiřování o další syntaktické prvky, existuje možnost použití jediné sady nástrojů více jazyky, lze přidat podporu pro různé výstupní formáty (překlad do nativního kódu nebo do WebAssembly atd.), podporu speciální filtry apod. (nehledě na to, že každá činnost je založena na odlišné teorii). Celý průběh zpracování vypadá při určitém zjednodušení následovně:
- Na začátku zpracování se nachází takzvaný lexer, který postupně načítá jednotlivé znaky ze vstupního řetězce (resp. ze vstupního souboru) a vytváří z nich lexikální tokeny.
- Výstup z lexeru může procházet libovolným počtem filtrů sloužících pro odstranění nebo (častěji) modifikaci jednotlivých tokenů; ať již jejich typů či přímo textu, který tvoří hodnotu tokenu. Díky existenci filtrů je například možné nechat si zvýraznit vybrané bílé znaky, slova se speciálním významem v komentářích (TODO:, FIX:) apod. Některé lexery obsahují filtr přímo ve svém modulu. V případě jazyka expr ovšem nemáme k tomuto rozhraní přímý přístup.
- Sekvence tokenů tvoří základ pro syntaktickou analýzu. Nástroj, který syntaktickou analýzu provádí, se většinou nazývá parser a proto se taktéž někdy setkáme s pojmem parsing (tento termín je ovšem chybně používán i v těch případech, kdy se provádí „pouze“ lexikální analýza). Výsledkem činnosti parseru je vhodně zvolená datová struktura, typicky abstraktní syntaktický strom (AST); někdy též strom derivační.
Mnohdy může být užitečné mít možnost zobrazení vytvořeného AST. Knihovna expr tuto operaci skutečně podporuje. Její praktickou implementaci si ukážeme v navazující kapitole.
Zobrazení výsledného AST
V případě, že budeme chtít zobrazit celý abstraktní syntaktický strom skriptu, ať již se jedná o jednoduchý výraz, nebo o dlouhý skript s podmínkami atd., lze postupovat dvěma způsoby. Buď je možné nejdříve získat celý AST a následně přes vzor visitor realizovat průchod stromem. Nebo, což je jednodušší, se použije funkce ast.Dump, které se předá kořenový uzel AST a funkce celý strom vytiskne. V dnešním článku použijeme druhou možnost, která je (z pohledu velikosti potřebného kódu) snazší.
Nejdříve si necháme vytisknout AST pro velmi jednoduchý výraz „6*7“:
package main
import (
"fmt"
"log"
"github.com/expr-lang/expr/ast"
"github.com/expr-lang/expr/parser"
)
func main() {
code := `6*7`
tree, err := parser.Parse(code)
if err != nil {
log.Fatal(err)
}
fmt.Println(ast.Dump(tree.Node))
}
Výsledkem bude textová podoba AST, ze které je patrné, že uzly s hodnotami obsahují nejenom hodnotu, ale i její typ (skript je silně typovaný). Celý AST tvoří kořen (představující operátor *) a dva hodnotové uzly:
BinaryNode{
Operator: "*",
Left: IntegerNode{
Value: 6,
},
Right: IntegerNode{
Value: 7,
},
}
Pochopitelně nám nic nebrání v zobrazení složitějších AST, například abstraktního syntaktického stromu pro složitější výraz se závorkami a operátory s různou prioritou:
package main
import (
"fmt"
"log"
"github.com/expr-lang/expr/ast"
"github.com/expr-lang/expr/parser"
)
func main() {
code := `1+2+3+3*(5+7)`
tree, err := parser.Parse(code)
if err != nil {
log.Fatal(err)
}
fmt.Println(ast.Dump(tree.Node))
}
Opět si v textové podobě AST povšimněte hodnotových uzlů s explicitně definovanými datovými typy hodnot:
BinaryNode{
Operator: "+",
Left: BinaryNode{
Operator: "+",
Left: BinaryNode{
Operator: "+",
Left: IntegerNode{
Value: 1,
},
Right: IntegerNode{
Value: 2,
},
},
Right: IntegerNode{
Value: 3,
},
},
Right: BinaryNode{
Operator: "*",
Left: IntegerNode{
Value: 3,
},
Right: BinaryNode{
Operator: "+",
Left: IntegerNode{
Value: 5,
},
Right: IntegerNode{
Value: 7,
},
},
},
}
V posledním demonstračním příkladu si necháme zobrazit AST skriptu, který je již poněkud složitější. Obsahuje totiž podmínku s rozvětvením realizovaným přes if a else, ve skriptu jsou definovány lokální proměnné atd.:
package main
import (
"fmt"
"log"
"github.com/expr-lang/expr/ast"
"github.com/expr-lang/expr/parser"
)
func main() {
code := `
if foo < 0 {
let result = -foo*bar;
result
} else {
let result = foo*bar;
result
}`
tree, err := parser.Parse(code)
if err != nil {
log.Fatal(err)
}
fmt.Println(ast.Dump(tree.Node))
}
V zobrazené textové podobě AST stojí za povšimnutí například uzly Expr, které reprezentují vrácení vypočtené hodnoty (a tedy vlastně odpovídají příkazu return, kdyby ho ovšem jazyk epxr vyžadoval):
ConditionalNode{
Ternary: false,
Cond: BinaryNode{
Operator: "<",
Left: IdentifierNode{
Value: "foo",
},
Right: IntegerNode{
Value: 0,
},
},
Exp1: VariableDeclaratorNode{
Name: "result",
Value: BinaryNode{
Operator: "*",
Left: UnaryNode{
Operator: "-",
Node: IdentifierNode{
Value: "foo",
},
},
Right: IdentifierNode{
Value: "bar",
},
},
Expr: IdentifierNode{
Value: "result",
},
},
Exp2: VariableDeclaratorNode{
Name: "result",
Value: BinaryNode{
Operator: "*",
Left: IdentifierNode{
Value: "foo",
},
Right: IdentifierNode{
Value: "bar",
},
},
Expr: IdentifierNode{
Value: "result",
},
},
}
Varianta podobného skriptu, ovšem s ternárním operátorem:
package main
import (
"fmt"
"log"
"github.com/expr-lang/expr/ast"
"github.com/expr-lang/expr/parser"
)
func main() {
code := `foo < 0 ? -foo*bar : foo*bar`
tree, err := parser.Parse(code)
if err != nil {
log.Fatal(err)
}
fmt.Println(ast.Dump(tree.Node))
}
Výsledná textová podoba AST, ve kterém se ternární operátor vyskytuje jako samostatný uzel s podmínkou a oběma větvemi:
ConditionalNode{
Ternary: true,
Cond: BinaryNode{
Operator: "<",
Left: IdentifierNode{
Value: "foo",
},
Right: IntegerNode{
Value: 0,
},
},
Exp1: BinaryNode{
Operator: "*",
Left: UnaryNode{
Operator: "-",
Node: IdentifierNode{
Value: "foo",
},
},
Right: IdentifierNode{
Value: "bar",
},
},
Exp2: BinaryNode{
Operator: "*",
Left: IdentifierNode{
Value: "foo",
},
Right: IdentifierNode{
Value: "bar",
},
},
}
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. V případě, že nebudete chtít klonovat celý repositář, můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
Odkazy na Internetu
- Repositář s balíčkem expr
https://github.com/expr-lang/expr - Syntaxe jazyka expr
https://expr-lang.org/docs/language-definition - Pískoviště pro odzkoušení možností jazyka expr
https://expr-lang.org/playground - yaegi
https://github.com/traefik/yaegi - gore
https://github.com/x-motemen/gore - gomacro
https://github.com/cosmos72/gomacro - Stránky jazyka Go
https://go.dev/ - Interpreter (computing)
https://en.wikipedia.org/wiki/Interpreter_(computing) - Scripting language
https://en.wikipedia.org/wiki/Scripting_language - Scripting languages
https://en.wikipedia.org/wiki/List_of_programming_languages_by_type#Scripting_languages - PYPL PopularitY of Programming Language
https://pypl.github.io/PYPL.html - Tiobe index
https://www.tiobe.com/tiobe-index/ - GopherJS: transpřekladač z jazyka Go do JavaScriptu
https://www.root.cz/clanky/gopherjs-transprekladac-z-jazyka-go-do-javascriptu/ - Technologie WebAssembly a GopherJS: předávání argumentů mezi Go a JavaScriptem
https://www.root.cz/clanky/technologie-webassembly-a-gopherjs-predavani-argumentu-mezi-go-a-javascriptem/ - Technologie WebAssembly a GopherJS: předávání argumentů mezi Go a JavaScriptem (dokončení)
https://www.root.cz/clanky/technologie-webassembly-a-gopherjs-predavani-argumentu-mezi-go-a-javascriptem-dokonceni/ - Difference Between Compiler and Interpreter
https://www.geeksforgeeks.org/difference-between-compiler-and-interpreter/ - What are the best interpreted programming languages?
https://www.slant.co/topics/15913/~interpreted-programming-languages - Go Interpreter
https://github.com/go-interpreter/ - Compiler vs Interpreter: A Detailed Comparison
https://www.theknowledgeacademy.com/blog/compiler-vs-interpreter/ - Gomacro: code generation made easy and fun
https://github.com/cosmos72/gomacro/blob/master/doc/code_generation.pdf - gopher-lua
https://github.com/yuin/gopher-lua - go-lua
https://github.com/Shopify/go-lua - golua
https://github.com/aarzilli/golua - gpython
https://github.com/go-python/gpython - GIMP Home Page
http://www.gimp.org/ - GIMP Python Documentation
https://www.gimp.org/docs/python/ - Writing GIMP Scripts and Plug-Ins
http://gimpbook.com/scripting/ - GIMP Scripts and Plug-ins (slajdy)
http://gimpbook.com/scripting/slides/index.html - Use Python to write plug-ins for GIMP
http://www.ibm.com/developerworks/library/os-autogimp/index.html - A Script-Fu Tutorial
http://www.linuxtopia.org/online_books/graphics_tools/gimp_user_manual/en/gimp-using-script-fu-tutorial.html - A Script-Fu Tutorial
http://docs.gimp.org/en/gimp-using-script-fu-tutorial.html - Null coalescing operator
https://en.wikipedia.org/wiki/Null_coalescing_operator - Evil: kombinace editačních příkazů Vimu a síly Emacsu
https://www.root.cz/clanky/evil-kombinace-editacnich-prikazu-vimu-a-sily-emacsu/ - Úpravy Emacsu a tvorba nových modulů s využitím Emacs Lispu
https://www.root.cz/clanky/upravy-emacsu-a-tvorba-novych-modulu-s-vyuzitim-emacs-lispu/ - Úpravy Emacsu a tvorba nových modulů s využitím Emacs Lispu (2)
https://www.root.cz/clanky/upravy-emacsu-s-emacs-lisp-zakladni-konstrukce-jazyka/ - Úpravy Emacsu s Emacs Lisp: všemocné makro cl-loop a knihovna dash
https://www.root.cz/clanky/upravy-emacsu-s-emacs-lisp-vsemocne-makro-cl-loop-a-knihovna-dash/ - Úpravy Emacsu s Emacs Lisp: možnosti nabízené knihovnou Dash
https://www.root.cz/clanky/upravy-emacsu-s-emacs-lisp-moznosti-nabizene-knihovnou-dash/ - Úpravy Emacsu s Emacs Lisp: dokončení popisu Emacs Lispu
https://www.root.cz/clanky/upravy-emacsu-s-emacs-lisp-dokonceni-popisu-emacs-lispu/ - Úpravy Emacsu s Emacs Lisp: základní interní datové struktury Emacsu
https://www.root.cz/clanky/upravy-emacsu-s-emacs-lisp-manipulace-se-zakladnimi-datovymi-strukturami-emacsu/ - Základy použití režimu org-mode v Emacsu
https://www.root.cz/clanky/zaklady-pouziti-rezimu-org-mode-v-emacsu/ - Pokročilejší možnosti nabízené režimem org-mode v Emacsu
https://www.root.cz/clanky/pokrocilejsi-moznosti-nabizene-rezimem-org-mode-v-emacsu/ - Spacemacs: to nejlepší z editorů Emacs a Vim
https://www.root.cz/clanky/spacemacs-to-nejlepsi-z-editoru-emacs-a-vim/ - gpython
https://github.com/go-python/gpython - grumpy
https://github.com/grumpyhome/grumpy - gopy
https://github.com/go-python/gopy - Propojení Go s Pythonem s využitím cgo a ctypes
https://www.root.cz/clanky/propojeni-go-s-pythonem-s-vyuzitim-cgo-a-ctypes/ - Propojení Go s Pythonem s využitím cgo a ctypes (2. část)
https://www.root.cz/clanky/propojeni-go-s-pythonem-s-vyuzitim-cgo-a-ctypes-2-cast/
Autor: Pavel Tišnovský 2026
