Obsah
1. Programovací jazyk Go a skriptovací jazyky
3. Obecné skriptovací jazyky versus doménově specifické jazyky (DSL)
5. Příklad typu „Hello world!“
9. Předání parametrů pro vyhodnocení výrazu
10. Předání parametrů, které se ve výrazu nepoužijí
14. Přístup k prvkům datové struktury
15. Indexy pole předané v parametru
16. Jména prvků datové struktury předaná v parametru
17. Zkrácený přístup k prvkům datové struktury s využitím tečkové notace
19. Repositář s demonstračními příklady
1. Programovací jazyk Go a skriptovací jazyky
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. Většinou je tato možnost rozšiřování možností aplikace s využitím skriptů výhodná pro všechny spolupracující strany – firma produkující daný software vlastně zadarmo získá další aplikační oblast, zákazník či uživatel si takto může aplikaci upravit ke svým potřebám a popř. na úpravách může spolupracovat i další společnost/vývojáři nezávislí na autorech původního software (dlouholetým příkladem z praxe jsou například CADy a jejich nadstavby).
Prozatím jsme se v praktické části seriálu o Go věnovali způsobům 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).
2. Doménově specifické jazyky
Doménově specifické jazyky (DSL – Domain-Specific Language) jsou velmi důležitou součástí informatiky a mnohé z nich jsou velmi úspěšné a rozšířené do mnoha oblastí. Za připomenutí stojí například jazyk pro popis regulárních výrazů, jenž je podporován jak mnoha nástroji, tak i knihovnami, popř. je přímo součástí některých obecných programovacích jazyků (Perl apod.). I v některých dalších případech je tento přístup velmi užitečný a rozšířený, ostatně například SQL je s velkou pravděpodobností nejpopulárnějším samostatně chápaným doménově specifickým jazykem neboli DSL vůbec, protože umožňuje snadné optimalizace dotazů a vůbec pokládání dotazů čitelným, pochopitelným a přenositelným způsobem. Dalším doménově specifickým jazykem, s nímž jsme se již na stránkách Roota v rámci několika článků seznámili, je jazyk Gherkin určený pro popis chování systémů a pro psaní BDD testů. Dalším příkladem je PostScript.
3. Obecné skriptovací jazyky versus doménově specifické jazyky (DSL)
Mnohé z doménově specifických jazyků nejsou Turingovsky kompletní (úplný), což však není nedostatek, ale mnohdy 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“ (právě proto jsou regulární výrazy regulární).
4. Projekt Gval
V dnešním článku si popíšeme doménově specifický jazyk nazvaný Gval, což je zkratka odvozená ze sousloví „Go eVALuate“. Toto sousloví je poměrně přiléhavé, protože se skutečně jedná o DSL zaměřený na vyhodnocování (evaluate) výrazů, přičemž tyto výrazy mohou být v některých případech i poměrně komplikované. Jejich syntaxe je odvozena od samotného programovacího jazyka Go, jak ostatně uvidíme v navazujících kapitolách, v nichž si ukážeme praktické příklady.
Tento DSL lze použít například při zpracování konfiguračních souborů (například můžeme nastavit limit počtu workerů na výraz „2*CPU_threads“ namísto pracného nastavování konkrétní číselné hodnoty) nebo i v dialozích a formulářích grafického uživatelského rozhraní. Příkladem může být vstupní pole pro odhad pracnosti, do kterého takto můžeme zadat „3*8*60“ namísto nutnosti počítání výsledné hodnoty v nějakém jiném nástroji (ostatně informační systémy jsou tady od toho, aby nám práci zjednodušovaly a nikoli ji dělaly složitější – což se mnohdy děje).
5. Příklad typu „Hello world!“
Podívejme se nyní na způsob předání výrazu do knihovny gval, na způsob jeho vyhodnocení a předání výsledku. Kostra příkladu získaného přímo ze stránek projektu může vypadat následovně:
package main import ( "fmt" "os" "github.com/PaesslerAG/gval" ) func main() { // parametry předávané vyhodnocovanému výrazu parameters := map[string]interface{}{"name": "World"} // vyhodnocení výrazu value, err := gval.Evaluate(`"Hello " + name + "!"`, parameters) if err != nil { // kód pro zpracování chyby při vyhodnocování výrazu fmt.Println(err) os.Exit(1) } // výpis výsledku výrazu fmt.Print(value) }
Celý tento příklad je postaven okolo funkce Evaluate, jejíž hlavička vypadá následovně:
func Evaluate(expression string, parameter interface{}, opts ...Language) (interface{}, error)
Jak je už z hlavičky této funkce patrné, předá se jí především vlastní výraz reprezentovaný řetězcem. Dále je možné předat i parametry, což jsou dvojice jméno+hodnota, které můžeme v rámci výrazu většinou považovat za pojmenované konstanty (takto tedy aplikace uživatelům nabízí část svého interního stavu). Poslední parametr je nepovinný a umožňuje rozšiřovat možnosti doménově specifického jazyka. O této možnosti se zmíníme v navazujícím článku.
Funkce Evaluate vrací dvě hodnoty – v první řadě výsledek vyhodnoceného výrazu a taktéž informaci o případné chybě, která může nastat jak při parsingu výrazu, tak i při jeho vyhodnocování. Povšimněte si především toho, že výsledná hodnota je typu interface{}, což v programovacím jazyce Go značí libovolnou hodnotu (každý datový typ splňuje prázdné rozhraní). O případné přetypování se tedy musí postarat sám programátor, a to i s případnou kontrolou chyb – typ výsledné hodnoty totiž přímo závisí na vstupu od uživatele!
Pro spuštění příkladu je nutné vytvořit i projektový soubor příkazem:
$ go mod init jméno_projektu
Po překladu a spuštění by měl projektový soubor vypadat takto:
module gval01 go 1.16 require github.com/PaesslerAG/gval v1.1.1
A soubor se seznamem všech závislostí (i tranzitivních závislostí) bude vypadat následovně:
github.com/PaesslerAG/gval v1.1.1 h1:4d7pprU9876+m3rc08X33UjGip8oV1kkm8Gh5GBuTss= github.com/PaesslerAG/gval v1.1.1/go.mod h1:Fa8gfkCmUsELXgayr8sfL/sw+VzCVoa03dcOcR/if2w= github.com/PaesslerAG/jsonpath v0.1.0/go.mod h1:4BzmtoM/PI8fPO4aQGIusjGxGir2BzcV0grWtFzq1Y8=
6. Aritmetický výraz
Ve druhém demonstračním příkladu je ukázáno vyhodnocení velmi jednoduchého aritmetického výrazu, který obsahuje pouze (celo)číselné konstanty. Jedná se o jednoduchou modifikaci prvního příkladu, na které budeme stavět i další demonstrační příklady:
package main import ( "fmt" "os" "github.com/PaesslerAG/gval" ) func main() { // parametry předávané vyhodnocovanému výrazu parameters := make(map[string]interface{}) // vyhodnocení výrazu value, err := gval.Evaluate("6*7", parameters) if err != nil { // kód pro zpracování chyby při vyhodnocování výrazu fmt.Println(err) os.Exit(1) } // výpis výsledku výrazu fmt.Print(value) }
7. Priority operátorů
Ve výrazech, a to jak ve výrazech aritmetických, tak i logických, je definována a dodržena priorita operátorů. Ta je přímo odvozena od specifikace programovacího jazyka Go. Tabulka priorit je v Go poměrně jednoduchá, zejména v porovnání s tabulkami, které mnozí čtenáři pravděpodobně znají z jazyků C, C++ či Javy. Nejvyšší prioritu mají unární operátory (s jediným operandem) a následně existuje pouze pět priorit operátorů binárních, které si můžete zapamatovat s využitím mnemotechnické pomůcky MACAO:
Úroveň | Operátory | Mnemotechnická pomůcka |
---|---|---|
1 | * / % | Multiplicative |
2 | + – | Additive |
3 | == > < >= <= != | Comparison |
4 | && | And |
5 | || | Or |
Podívejme se nyní, jak se vyhodnotí výraz s aditivními a multiplikativními operátory:
package main import ( "fmt" "os" "github.com/PaesslerAG/gval" ) func main() { // parametry předávané vyhodnocovanému výrazu parameters := make(map[string]interface{}) // vyhodnocení výrazu value, err := gval.Evaluate("2 + 4*10", parameters) if err != nil { // kód pro zpracování chyby při vyhodnocování výrazu fmt.Println(err) os.Exit(1) } // výpis výsledku výrazu fmt.Print(value) }
Výsledek je korektní: 42.
8. Parametry ve výrazech
Ve výrazech vyhodnocovaných knihovnou gval je možné používat i jména parametrů. Prozatím si ukažme, jak vypadá takový výraz:
2*x + y
Nyní se můžeme pokusit o vyhodnocení takového výrazu – viz též zdrojový kód uvedený pod tímto odstavcem:
package main import ( "fmt" "os" "github.com/PaesslerAG/gval" ) func main() { // parametry předávané vyhodnocovanému výrazu parameters := make(map[string]interface{}) // vyhodnocení výrazu value, err := gval.Evaluate("2*x + y", parameters) if err != nil { // kód pro zpracování chyby při vyhodnocování výrazu fmt.Println(err) os.Exit(1) } // výpis výsledku výrazu fmt.Print(value) }
Výraz je sice po syntaktické stránce zapsán korektně, ovšem nelze vyhodnotit, protože vyhodnocovací engine nemá přístup k parametrům x a y. Dojde tedy k chybě:
can not evaluate 2*x + y: invalid operation (float64) * (<nil>) exit status 1
9. Předání parametrů pro vyhodnocení výrazu
Parametry, které mají být viditelné ve vyhodnocovaném výrazu, je nutné explicitně vytvořit a přidat jím nějakou hodnotu. Tento krok je sice zdánlivě zbytečně zdlouhavý, ale právě díky explicitně nastaveným parametrům je striktně oddělen stavový prostor aplikace od vyhodnocovaného výrazu – tedy jedná se o další formu sandboxingu.
Příklad deklarace mapy s parametry (ty mohou mít libovolnou hodnotu):
// parametry předávané vyhodnocovanému výrazu parameters := map[string]interface{}{ "x": 10, "y": 20, }
Takto definovanou mapu lze předat funkci gval.Evaluate:
package main import ( "fmt" "os" "github.com/PaesslerAG/gval" ) func main() { // parametry předávané vyhodnocovanému výrazu parameters := map[string]interface{}{ "x": 10, "y": 20, } // vyhodnocení výrazu value, err := gval.Evaluate("2*x + y", parameters) if err != nil { // kód pro zpracování chyby při vyhodnocování výrazu fmt.Println(err) os.Exit(1) } // výpis výsledku výrazu fmt.Print(value) }
Výsledkem bude v tomto případě hodnota 40 (podle očekávání).
10. Předání parametrů, které se ve výrazu nepoužijí
Funkci gval.Evaluate je možné předat větší množství parametrů, než bude skutečně ve výrazu využito. V tomto případě se ani nenahlásí varování (bylo by ostatně ve většině případů zbytečné). V dalším demonstračním příkladu předáváme čtyři parametry, ovšem ve skutečnosti jsou ve výrazu použity jen dva z nich:
package main import ( "fmt" "os" "github.com/PaesslerAG/gval" ) func main() { // parametry předávané vyhodnocovanému výrazu parameters := map[string]interface{}{ "x": 10, "y": 20, "z": 0, "w": -1, } // vyhodnocení výrazu value, err := gval.Evaluate("2*x + y", parameters) if err != nil { // kód pro zpracování chyby při vyhodnocování výrazu fmt.Println(err) os.Exit(1) } // výpis výsledku výrazu fmt.Print(value) }
I v tomto případě se výraz vyhodnotí a vrátí hodnotu 40.
11. Logické operátory
Ve výrazech vyhodnocovaných knihovnou gval je možné použít i logické operátory a taktéž logické konstanty true a false. Následuje jednoduchý demonstrační příklad, který je založen na použití relačních operací, které vrací pravdivostní hodnotu true či false:
package main import ( "fmt" "os" "github.com/PaesslerAG/gval" ) func main() { // parametry předávané vyhodnocovanému výrazu parameters := map[string]interface{}{ "x": 10, "y": 20, } // vyhodnocení výrazu value, err := gval.Evaluate("2*x > y-5", parameters) if err != nil { // kód pro zpracování chyby při vyhodnocování výrazu fmt.Println(err) os.Exit(1) } // výpis výsledku výrazu fmt.Print(value) }
Nepatrně složitější příklad používá logickou spojku && neboli logický součin:
package main import ( "fmt" "os" "github.com/PaesslerAG/gval" ) func main() { // parametry předávané vyhodnocovanému výrazu parameters := map[string]interface{}{ "x": 10, "y": 20, } // vyhodnocení výrazu value, err := gval.Evaluate("x < y && 2*x > y-5", parameters) if err != nil { // kód pro zpracování chyby při vyhodnocování výrazu fmt.Println(err) os.Exit(1) } // výpis výsledku výrazu fmt.Print(value) }
12. Ternární operátor
Ve výrazech vyhodnocovaných knihovnou gval je možné použít i takzvaný ternární operátor. Ten byl představen v programovacím jazyku C a odtud se rozšířil i do mnohých dalších programovacích jazyků, včetně Javy, JavaScriptu či C#. V jazyce Go tento operátor použit není, ovšem ve výrazech je velmi užitečný (nemáme zde totiž možnost použít konstrukci if-else) a proto byl do gval přidán.
Pochopitelně se opět podíváme na způsob jeho využití:
package main import ( "fmt" "os" "github.com/PaesslerAG/gval" ) func main() { // parametry předávané vyhodnocovanému výrazu parameters := map[string]interface{}{ "x": 10, "y": 20, } // vyhodnocení výrazu value, err := gval.Evaluate("x<y ? \"mensi\":\"vetsi\"", parameters) if err != nil { // kód pro zpracování chyby při vyhodnocování výrazu fmt.Println(err) os.Exit(1) } // výpis výsledku výrazu fmt.Print(value) }
V tomto případě je však výhodnější řetězec s výrazem zapsat do zpětných apostrofů, čímž obejdeme nutnost použití \" pro zápis jednoduchých uvozovek:
package main import ( "fmt" "os" "github.com/PaesslerAG/gval" ) func main() { // parametry předávané vyhodnocovanému výrazu parameters := map[string]interface{}{ "x": 10, "y": 20, } // vyhodnocení výrazu value, err := gval.Evaluate(`x<y ? "mensi":"vetsi"`, parameters) if err != nil { // kód pro zpracování chyby při vyhodnocování výrazu fmt.Println(err) os.Exit(1) } // výpis výsledku výrazu fmt.Print(value) }
13. Přístup k prvkům pole
Mezi parametry, které se předávají k vyhodnocení výrazem, mohou být i pole nebo řezy (slice):
parameters := map[string]interface{}{ "x": 10, "y": 20, "arr": []int{10, 20, 30}, }
Ve výrazu se k prvkům polí přistupuje běžným způsobem – použitím celočíselných indexů, které začínají od nuly (tedy naprosto stejně, jako je tomu v jazyce Go):
package main import ( "fmt" "os" "github.com/PaesslerAG/gval" ) func main() { // parametry předávané vyhodnocovanému výrazu parameters := map[string]interface{}{ "x": 10, "y": 20, "arr": []int{10, 20, 30}, } // vyhodnocení výrazu value, err := gval.Evaluate("arr[0] + arr[1] + arr[2]", parameters) if err != nil { // kód pro zpracování chyby při vyhodnocování výrazu fmt.Println(err) os.Exit(1) } // výpis výsledku výrazu fmt.Print(value) }
14. Přístup k prvkům datové struktury
Ve výrazu vyhodnocovaném knihovnou gval je možné přistupovat i k prvkům datové struktury. V následujícím demonstračním příkladu je použita tato jednoduchá datová struktura:
type User struct { ID uint32 Name string Surname string }
Následně je vytvořena instance této struktury a všechny tři její prvky jsou naplněny daty:
user := User{ 1, "Pepek", "Vyskoč"}
V případě, že tento objekt předáme jako parametr:
parameters := map[string]interface{}{ "x": 10, "y": 20, "arr": []int{10, 20, 30}, "user": user, }
…je možné přistupovat k prvkům parametru s využitím běžných hranatých závorek, tedy stejně, jako je tomu v samotném jazyku Go při práci s mapami (nikoli s datovými strukturami!):
`"Jméno: " + user["Name"] + "\nPříjmení: " + user["Surname"]`
Následuje úplný zdrojový kód výše popsaného demonstračního příkladu:
package main import ( "fmt" "os" "github.com/PaesslerAG/gval" ) // User je uživatelsky definovaná datová struktura s viditelnými atributy type User struct { ID uint32 Name string Surname string } func main() { user := User{ 1, "Pepek", "Vyskoč"} // parametry předávané vyhodnocovanému výrazu parameters := map[string]interface{}{ "x": 10, "y": 20, "arr": []int{10, 20, 30}, "user": user, } // vyhodnocení výrazu value, err := gval.Evaluate(`"Jméno: " + user["Name"] + "\nPříjmení: " + user["Surname"]`, parameters) if err != nil { // kód pro zpracování chyby při vyhodnocování výrazu fmt.Println(err) os.Exit(1) } // výpis výsledku výrazu fmt.Print(value) }
15. Indexy pole předané v parametru
Ve třinácté kapitole jsme k prvkům pole přistupovali s využitím celočíselných indexů. Ovšem stejně dobře je možné použít hodnoty parametrů nebo nějaké složitější podvýrazy využívající parametry. Tento způsob je ukázán v dalším demonstračním příkladu, v němž se pro přístup k prvkům pole používají parametry nazvané index1 a index2:
package main import ( "fmt" "os" "github.com/PaesslerAG/gval" ) func main() { // parametry předávané vyhodnocovanému výrazu parameters := map[string]interface{}{ "index1": 0, "index2": 2, "arr": []int{10, 20, 30}, } // vyhodnocení výrazu value, err := gval.Evaluate("arr[index1] + arr[index2]", parameters) if err != nil { // kód pro zpracování chyby při vyhodnocování výrazu fmt.Println(err) os.Exit(1) } // výpis výsledku výrazu fmt.Print(value) }
16. Jména prvků datové struktury předaná v parametru
I k prvkům datové struktury můžeme přistupovat tak, že využijeme jméno prvku uložené v parametru (taktéž se může jednat o výsledek nějakého složitějšího podvýrazu, který se musí vyhodnotit na hodnotu typu řetězec). Takto reprezentované jméno prvku zapíšeme do hranaté závorky za jméno parametru nesoucího hodnotu datové struktury:
package main import ( "fmt" "os" "github.com/PaesslerAG/gval" ) // User je uživatelsky definovaná datová struktura s viditelnými atributy type User struct { ID uint32 Name string Surname string } func main() { user := User{ 1, "Pepek", "Vyskoč"} // parametry předávané vyhodnocovanému výrazu parameters := map[string]interface{}{ "selector1": "Name", "selector2": "Surname", "arr": []int{10, 20, 30}, "user": user, } // vyhodnocení výrazu value, err := gval.Evaluate(`"Jméno: " + user[selector1] + "\nPříjmení: " + user[selector2]`, parameters) if err != nil { // kód pro zpracování chyby při vyhodnocování výrazu fmt.Println(err) os.Exit(1) } // výpis výsledku výrazu fmt.Print(value) }
17. Zkrácený přístup k prvkům datové struktury s využitím tečkové notace
Podobně jako přímo v programovacím jazyce Go i při vyhodnocování výrazů knihovnou gval je možné pro přístup k prvkům datové struktury použít tečkovou notaci. Toto řešení je ukázáno v dnešním posledním demonstračním příkladu:
package main import ( "fmt" "os" "github.com/PaesslerAG/gval" ) // User je uživatelsky definovaná datová struktura s viditelnými atributy type User struct { ID uint32 Name string Surname string } func main() { user := User{ 1, "Pepek", "Vyskoč"} // parametry předávané vyhodnocovanému výrazu parameters := map[string]interface{}{ "user": user, } // vyhodnocení výrazu value, err := gval.Evaluate(`"Jméno: " + user.Name + "\nPříjmení: " + user.Surname`, parameters) if err != nil { // kód pro zpracování chyby při vyhodnocování výrazu fmt.Println(err) os.Exit(1) } // výpis výsledku výrazu fmt.Print(value) }
18. Obsah dalšího článku
Knihovna gval sice vypadá zdánlivě jednoduše, ovšem ve skutečnosti vývojářům nabízí i některé další zajímavé a potenciálně užitečné technologie. Jednou z nich je možnost přidat si vlastní syntaktické pravidlo do doménově specifického jazyka a tím vlastně tento jazyk rozšířit. Taktéž je možné přistupovat k metodám objektů, používat vnořené datové struktury atd. atd. – tyto podrobnosti, které nemusí být na první pohled patrné, si popíšeme v navazujícím článku.
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 nového 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ě stovku kilobajtů), můžete namísto toho použít odkazy na jednotlivé demonstrační příklady, které naleznete v následující tabulce:
20. Odkazy na Internetu
- A curated list of awesome Go frameworks, libraries and software
https://awesome-go.com/ - Gval na GitHubu
https://github.com/PaesslerAG/gval - Dokumentace k balíčku Gval
https://coveralls.io/github/PaesslerAG/gval?branch=master - Gval code coverage report
https://coveralls.io/github/PaesslerAG/gval?branch=master - Gopher-Lua
https://github.com/yuin/gopher-lua - Go-lua
https://github.com/Shopify/go-lua - Binder
https://github.com/alexeyco/binder - Kooperace mezi jazykem Lua a nativním (céčkovým) kódem
https://www.root.cz/clanky/kooperace-mezi-jazykem-lua-a-nativnim-ceckovym-kodem/ - Kooperace mezi jazykem Lua a nativním (céčkovým) kódem: knihovna FFI
https://www.root.cz/clanky/kooperace-mezi-jazykem-lua-a-nativnim-ceckovym-kodem-knihovna-ffi/ - Skriptovací jazyk Lua v aplikacích naprogramovaných v Go
https://www.root.cz/clanky/skriptovaci-jazyk-lua-v-aplikacich-naprogramovanych-v-go/ - Jazyk Joker: dialekt Clojure naprogramovaný v Go
https://www.root.cz/clanky/jazyk-joker-dialekt-clojure-naprogramovany-v-go/ - Behave na GitHubu
https://github.com/behave/behave - behave 1.2.6 (PyPi)
https://pypi.python.org/pypi/behave - Dokumentace k Behave
http://behave.readthedocs.io/en/latest/ - Příklady použití Behave
https://github.com/behave/behave.example - Domain-specific language
https://en.wikipedia.org/wiki/Domain-specific_language - Turingovská úplnost
https://cs.wikipedia.org/wiki/Turingovsk%C3%A1_%C3%BAplnost