Obsah
1. Go v roli skriptovacího programovacího jazyka
2. Skriptovací jazyky v současnosti
4. Instalace projektu Gore a spuštění interaktivní smyčky REPL
5. První kroky s interpretrem Gore
6. Speciální pseudopříkazy používané v REPL projektu Gore
7. Získání typu výrazu a omezené možnosti této techniky
9. Způsob generování zdrojových kódů interpretrem Gore
12. Interpret Yaegi zabudovaný do aplikace
14. Instalace Gomacra s jeho spuštěním
15. Interní příkazy interpretru Gomacro
16. Inspektor v interpretru Gomacro
17. Debugger integrovaný do Gomacra
18. Interpret Gomacro zabudovaný do aplikace
19. Repositář s demonstračními příklady
1. Go v roli skriptovacího programovacího jazyka
Pro čtenáře seriálu o programovacím jazyce Go pravděpodobně nebude tvrzení, že Go je překládaný (kompilovaný) programovací jazyk, žádným překvapením. Zdrojové kódy psané v jazyce Go se skutečně před svým spuštěním nejdříve překládají, a to buď do nativního kódu zvolené platformy (x86, x86–64, ARM 32bit, Aarch64, RISC-V atd.), nebo do bajtkódu WebAssembly. Alternativně je možné použít transpřekladač (transpiler) GopherJS, který dokáže zdrojové kódy psané v jazyce Go transformovat do JavaScriptu. Touto technologií jsme se zabývali v [1], [2] a [3].
Výhody překladače, resp. přesněji řečeno překladače programovacího jazyka se silným typovým systémem, jsou pravděpodobně většině vývojářů zřejmé – vyšší rychlost běhu aplikací, obecně menší paměťové nároky procesů, na cílovém počítači nemusí být nainstalován žádný interpret a v neposlední řadě je poměrně velká část chyb (bohužel spíše těch triviálních) objevena již překladačem. Navíc je programovací jazyk Go známý tím, že jeho překladač je velmi rychlý – mnohdy tak rychlý, že je fáze překladu prakticky „neviditelná“ (v závislosti na velikosti projektu se může jednat například o 200 ms atd. – což je z pohledu lidského vývojáře prakticky okamžitě a cyklus edit-compile-run se tak de facto zkracuje na edit-run). Ostatně příkaz go run se skrytím procesu překladu a následným odstraněním spustitelného souboru snaží tvářit jako interpretovaný jazyk.
Nicméně existují oblasti, v nichž se prosadil dosti odlišný způsob vývoje, který je založen na přímé interakci (resp. dialogu) mezi uživatelem a počítačem. V tom nejjednodušším případě se jedná o systémy vybavené interaktivní smyčkou REPL (Read Eval Print Loop), jejichž poněkud primitivní podobu si někteří mohou pamatovat z dob osmibitových mikropočítačů a interpretrů jazyka BASIC. Nicméně klasický REPL najdeme například i v Pythonu a dalších moderních skriptovacích jazycích (pro Python navíc existují různá jeho rozšíření, například v podobě IPythonu). V současnosti se mnohdy původní REPL nahrazuje spíše rozhraním ve stylu diáře (notebooku). Příkladem je projekt Jupyter Notebook, který v současnosti podporuje velké množství skriptovacích jazyků.
Kromě existence REPLu je důležitá i další technologie – rozšíření nějaké aplikace o možnost jejího ovládání pomocí skriptů. Tato technologie dokáže dosti podstatným způsobem rozšířit možnosti takové aplikace, a to klidně o řád nebo i několik řádů. Možnosti skriptování je vybavena relativně velká část aplikací, ať již se to týká programátorských textových editorů, nástrojů typu office, CADů a 3D modelovacích programů, ale například i takové „maličkosti“, jakou jsou prohlížeče HTML stránek (zde je zvětšení možností o několik řádů asi nejvíce patrné). To znamená, že bychom uvítali, kdyby i námi vyvíjené aplikace tuto možnost nabízely.
Go jakožto překládaný jazyk do této kategorie nepatří, ovšem i přesto vzniklo hned několik projektů, které se snaží o využití Go jakožto interaktivního skriptovacího jazyka. A právě některé z těchto projektů si představíme v dnešním článku.
2. Skriptovací jazyky v současnosti
V současnosti se v praxi používá poměrně velké množství různých skriptovacích jazyků. A nutno dodat, že se v některých případech jedná o velmi populární jazyky, o čemž se ostatně můžeme snadno přesvědčit například na stránkách indexu Tiobe nebo je to ještě více patrné na stránce PYPL PopularitY of Programming Language. V tabulce zobrazené pod tímto odstavcem jsou vypsány některé populární (či v minulosti populární – příkladem je ActionScript) skriptovací jazyky. Ty jsou seřazeny podle svého jména, ovšem nechybí zde ani Python, JavaScript či jazyk R (ten je ovšem používán spíše mimo profesionální IT komunitu; můžeme ho pokládat za doménově specifický jazyk):
Skriptovací jazyk |
---|
AppleScript |
AWK |
BeanShell |
Bash |
Ch |
ActionScript |
JavaScript |
Game Maker Language |
Julia |
Groovy |
Korn shell |
Lua |
Perl |
PHP |
PowerShell |
Python |
R |
Rebol |
Rexx |
Ruby |
S-lang |
Tcl |
VBScript |
3. Projekt Gore
Prvním interpretrem programovacího jazyka Go, se kterým se dnes seznámí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.
4. Instalace projektu Gore a spuštění interaktivní smyčky REPL
Ještě před samotnou instalací projektu Gore je nutné nainstalovat nástroj gocode, o němž jsme se zmínili již v předchozí kapitole. Je dosti pravděpodobné, že pokud vyvíjíte programy v jazyce Go, máte již gocode nainstalován, což lze velmi snadno otestovat:
$ whereis gocode gocode: /home/ptisnovs/go/bin/gocode
V případě, že gocode nainstalován není, použijte následující příkaz, který nainstaluje jeho poslední dostupnou verzi:
$ go install github.com/mdempsky/gocode@latest go: downloading github.com/mdempsky/gocode v0.0.0-20200405233807-4acdcbdea79d go: finding module for package golang.org/x/tools/go/gcexportdata go: downloading golang.org/x/tools v0.28.0 go: found golang.org/x/tools/go/gcexportdata in golang.org/x/tools v0.28.0
Ověříme si, že gocode lze spustit
$ gocode -help Usage: gocode [-s] [-f=] [-in=] [-sock=] [-addr=] [] Flags: -addr string address for tcp socket (default "127.0.0.1:37373") -builtin propose completions for built-in functions and types -cache use the cache importer -debug enable server-side debug mode -f string output format (vim | emacs | sexp | nice | csv | json) (default "nice") -fallback-to-source if importing a package fails, fallback to the source importer -ignore-case do case-insensitive matching -in string use this file instead of stdin input -s run a server instead of a client -sock string socket type (unix | tcp | none) (default "unix") -source use source importer -unimported-packages propose completions for standard library packages not explicitly imported Commands: autocomplete [] main autocompletion command exit terminate the gocode daemon
Ve druhém kroku již nainstalujeme samotný projekt Gore; opět jeho poslední verzi:
$ go install github.com/x-motemen/gore/cmd/gore@latest go: downloading github.com/x-motemen/gore v0.5.7 go: downloading github.com/peterh/liner v1.2.2 go: downloading golang.org/x/tools v0.13.0 go: downloading github.com/motemen/go-quickfix v0.0.0-20230925231438-5cf0001766ff go: downloading golang.org/x/text v0.13.0 go: downloading github.com/mattn/go-runewidth v0.0.15 go: downloading golang.org/x/sys v0.12.0 go: downloading golang.org/x/mod v0.12.0 go: downloading github.com/rivo/uniseg v0.4.4
Ověříme, že je příkaz gore dostupný z shellu:
$ whereis gore gore: /home/ptisnovs/go/bin/gore
Nakonec gore spustíme, resp. si necháme zobrazit nápovědu:
$ gore --help gore - A Go REPL Version: 0.5.7 (rev: HEAD/go1.22.1) Synopsis: % gore Options: -autoimport formats and adjusts imports automatically -context string import packages, functions, variables and constants from external golang source files -pkg string the package where the session will be run inside -version print gore version
Samotný interpret se spouští bez přepínačů:
$ gore :gore version 0.5.7 :help for help
Po spuštění interpretru by se měla zobrazit jednořádková nápověda (viz předchozí výpis) a gore očekává uživatelské příkazy. Například si můžeme zobrazit jeho krátkou nápovědu zadáním :help (včetně dvojtečky na začátku):
gore> :help :import <package> import a package :type <expr> print the type of expression :print print current source :write [<file>] write out current source :clear clear the codes :doc <expr or pkg> show documentation :help show this help :quit quit the session
5. První kroky s interpretrem Gore
V projektu Gore je možné, ostatně podobně jako v klasických interpretrech, zadávat jednotlivé příkazy, které se ihned vykonají a vypíše se jejich návratová hodnota. Pokud se jedná o běžné výrazy, je to snadné, což si můžeme ukázat na několika příkladech:
gore> 1+2 3 gore> nil nil gore> "foo" + "bar" "foobar" gore> "foobar"[2:6] "obar"
Pracovat je možné i s proměnnými, u kterých se dá s výhodou využít speciální operátor pro jejich deklaraci s přiřazením a s odvozením datového typu:
gore> x:=6 6 gore> y:=7 7 gore> x*y 42 gore> x<y true
Samotné výrazy jsou vždy pouze vyhodnoceny a poté interpretr „zapomene“, že byly vyhodnocovány. Naproti tomu deklarace proměnných se stává součástí interně vytvářeného programu (zdrojového kódu). Ten si můžeme zobrazit pseudopříkazem :print:
gore> :print package main import "github.com/k0kubun/pp/v3" func __gore_p(xs ...any) { for _, x := range xs { pp.Println(x) } } func main() { x := 6; y := 7 }
Povšimněte si, že se v postupně doplňované funkci main skutečně nachází deklarace proměnných. A interpret Gore nás odstíní od chyb typu „proměnná X je deklarovaná, ale nepoužívá se“ (což je dobrá detekce pro překladač, ale nikoli pro interaktivní interpret).
6. Speciální pseudopříkazy používané v REPL projektu Gore
S takzvanými pseudopříkazy jsme se již částečně setkali v předchozích dvou kapitolách. Tyto pseudopříkazy začínají znakem dvojtečky a můžeme je použít například pro získání dokumentace. Následující pseudopříkaz například vypíše dokumentaci k zabudované funkci println:
gore> :doc println func println(args ...Type) The println built-in function formats its arguments in an implementation-specific way and writes the result to standard error. Spaces are always added between arguments and a newline is appended. Println is useful for bootstrapping and debugging; it is not guaranteed to stay in the language.
Dalším často používaným pseudopříkazem je příkaz :import, který je v Gore možné použít kdykoli, nikoli jen na začátku balíčku (pojem balíček je ovšem v rámci REPLu poněkud mlhavý):
gore> :import fmt
Ihned po importu balíčku je možné získat nápovědu k jeho funkcím, konstantám, typům a metodám:
gore> :doc fmt.Println func Println(a ...interface{}) (n int, err error) Println formats using the default formats for its operands and writes to standard output. Spaces are always added between operands and a newline is appended. It returns the number of bytes written and any write error encountered.
Totéž například platí i pro další standardní balíček time a jeho funkci Sleep:
gore> :import time gore> :doc time.Sleep func Sleep(d Duration) Sleep pauses the current goroutine for at least the duration d. A negative or zero duration causes Sleep to return immediately.
Velmi užitečný je i pseudopříkaz :write jméno_souboru, který zapíše zapsané definice do specifikovaného souboru, který by měl být později přeložitelný překladačem jazyka Go (viz též seznam demonstračních příkladů, které vznikly právě tímto způsobem).
7. Získání typu výrazu a omezené možnosti této techniky
Pseudopříkazem :type můžeme zjistit typ výrazu, resp. přesněji řečeno typ vypočtené hodnoty. Jedná se o operaci, ke které se interpretry velmi dobře hodí (možná ještě lépe než debuggery):
gore> :type 42 int
Nemusíme zjišťovat jen typ konstanty (konstantního výrazu), ale i výrazů poněkud složitějších:
gore> :type 6*7 int
Pravdivostní výraz:
gore> :type 1==2 bool
Totéž platí i pro řetězce:
gore> :type "foo" string
Řezy
gore> :type []int{1,2,3} []int
Pole (mimochodem lze takto velmi dobře pochopit rozdíl mezi řezy a poli):
gore> :type [...]int{1,2,3} [3]int
Ovšem pozor je nutné si dát na to, abychom nezjišťovali typy proměnných (i když by to bylo velmi užitečné). V tomto ohledu je projekt Gore dosti omezený:
gore> x:=6*7 42
Pokus o zjištění typu proměnné x skončí s chybou:
gore> :type x panic: file not found for pos = 1 (gore_session.go:1:1) [recovered] panic: file not found for pos = 1 (gore_session.go:1:1)
8. Automatický import balíčků
V interpretru Gore pochopitelně můžeme přímo volat libovolnou funkci z implicitního balíčku. Takové funkce totiž není zapotřebí importovat. Pro úplnost – jedná se o následující funkce (jejich seznam lze nalézt například ve specifikaci jazyka Go, ale většinu pravděpodobně dobře znáte a používáte):
append cap clear close complex copy delete imag len make max min new panic print println real recover
To v praxi znamená, že si například můžeme nechat vypsat výsledek nějakého výrazu funkcí println:
gore> println(6*7) 42
Popř. si můžeme vypsat obsah proměnné:
gore> x:=10 10 gore> println(x) 10
Vypisovat hodnoty pochopitelně můžeme i z programové smyčky (spustí se automaticky po zápisu uzavírací závorky a odeslání řádku klávesou Enter):
gore> for i := range 10 { ..... println(i) ..... } 0 1 2 3 4 5 6 7 8 9
Ovšem co se stane ve chvíli, kdy například budeme chtít namísto funkce println zavolat například fmt.Println nebo fmt.Printf? Můžeme si to vyzkoušet:
gore> fmt.Println("foo") undefined: fmt
Interpret v tomto případě (korektně) napsal, že nezná identifikátor fmt.
Import balíčku můžeme kdykoli provést pseudopříkazem :import, například takto:
gore> :import fmt gore> fmt.Printf("%5.3f", 1/3.) 0.3335 nil
Ovšem interpret Gore nám dokáže práci ještě více zjednodušit, a to v případě, že povolíme takzvaný automatický import balíčků. K tomuto účelu je nutné použít přepínač –autoimport:
$ gore --autoimport
Můžeme si to velmi snadno otestovat spuštěním nové seance interpretru:
gore version 0.5.7 :help for help gore> x:=6*7 42 gore> fmt.Println(x) 42 3 nil
Poslední volání vypsalo tři hodnoty:
42 3 nil
První hodnota byla vypsána přímo volanou funkcí a je v okně interpretru zvýrazněna odlišnou barvou. A další dvě hodnoty označují počet zapsaných bajtů a případnou chybu – viz hlavičku této funkce:
func Println(a ...any) (n int, err error)
9. Způsob generování zdrojových kódů interpretrem Gore
Díky tomu, že interpret Gore obsahuje i podporu pro pseudopříkaz :print, si můžeme nechat vypsat zdrojový kód v jazyce Go, který je generovaný na pozadí – a to po odeslání každého příkazu, ať již jednořádkového či víceřádkového. Ukažme si, jak může vypadat celé „sezení“ v němž uživatel postupně do interpretru zadává příkazy a deklarace a posléze si nechá vypsat vygenerovaný zdrojový kód pseudopříkazem :print:
gore version 0.5.7 :help for help gore> x:=42 42 gore> :print
Vypíše se:
package main import "github.com/k0kubun/pp/v3" func __gore_p(xs ...any) { for _, x := range xs { pp.Println(x) } } func main() { x := 42 }
Doplníme další deklaraci a opět si necháme vypsat zdrojový kód:
gore> y:=10 10 gore> :print package main import "github.com/k0kubun/pp/v3" func __gore_p(xs ...any) { for _, x := range xs { pp.Println(x) } } func main() { x := 42; y := 10 }
Do zdrojového kódu se doplní i přímo vykonané příkazy:
gore> println(x) 42 gore> :print package main import "github.com/k0kubun/pp/v3" func __gore_p(xs ...any) { for _, x := range xs { pp.Println(x) } } func main() { x := 42; y := 10; println(x) }
Dtto pro další příkaz:
gore> println(y) 42 10 gore> :print package main import "github.com/k0kubun/pp/v3" func __gore_p(xs ...any) { for _, x := range xs { pp.Println(x) } } func main() { x := 42; y := 10; println(x); println(y) }
Chování interpretru při povolení automatického importu balíčků:
$ gore --autoimport gore version 0.5.7 :help for help gore> x:=6*7 42 gore> fmt.Println(x) 42 3 nil gore> :print package main import ( "fmt" "github.com/k0kubun/pp/v3" ) func __gore_p(xs ...any) { for _, x := range xs { pp.Println(x) } } func main() { x := 6 * 7; _, _ = fmt.Println(x) }
Do kódu lze přidávat i funkce, datové typy atd.:
gore> func add(x, y int) int { ..... return x+y ..... } gore> add(3,7) 10 gore> :print package main import "github.com/k0kubun/pp/v3" func __gore_p(xs ...any) { for _, x := range xs { pp.Println(x) } } func main() { _ = add(3, 7) } func add(x, y int) int { return x + y }
Datové typy:
gore> type User struct { ..... Name string ..... Surname string ..... } gore> :print package main import "github.com/k0kubun/pp/v3" func __gore_p(xs ...any) { for _, x := range xs { pp.Println(x) } } func main() {} type User struct { Name string Surname string }
Použití uživatelského datového typu:
gore> u:=User{"John", "Doe"} main.User{ Name: "John", Surname: "Doe", } gore> :print package main import "github.com/k0kubun/pp/v3" func __gore_p(xs ...any) { for _, x := range xs { pp.Println(x) } } func main() { u := User{"John", "Doe"} } type User struct { Name string Surname string }
10. Interpet Yaegi
Druhý interpret, s nímž se v dnešním článku seznámí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.
Instalace tohoto interpretru je snadná a rychlá (nemá další závislosti):
$ go install github.com/traefik/yaegi/cmd/yaegi@latest go: downloading github.com/traefik/yaegi v0.16.1
Po instalaci si ověříme, zda je interpret dostupný:
$ yaegi --help Yaegi is a Go interpreter. Usage: yaegi [command] [arguments] The commands are: extract generate a wrapper file from a source package help print usage information run execute a Go program from source test execute test functions in a Go package version print version Use "yaegi help <command>" for more information about a command. If no command is given or if the first argument is not a command, then the run command is assumed.
11. REPL interpretru Yaegi
Interaktivní smyčka interpretru Yaegi je v porovnání s ostatními dvěma interpretry spíše dosti primitivní. Nenajdeme zde ani historii příkazů ani jejich vyhledávání v historii. Dokonce tento interpret nenabízí ani žádné pseudopříkazy; pouze akceptuje vstupní výrazy a příkazy a ihned je vyhodnocuje. Je tomu tak z toho důvodu, že se Yaegi má používat spíše jako interpret vestavěný do aplikací a nikoli přímo z REPLu.
I přesto se v krátkosti podívejme na to, jak může vypadat interaktivní „sezení“ s interpretem Yaegi. Pracujeme v něm s výrazy, deklaracemi proměnných, funkcemi z jiných balíčků i s deklaracemi vlastních funkcí:
> 1+2 : 3 > x:=6 : 6 > y:=7 : 7 > x*y : 42 > z:=x*y : 42 > z : 42 > import "fmt" : 0xc00019dca0 > fmt.Printf("%T\n", z) int : 4 > func add(a, b int) int { return a+b } : 0xc00058f5a0 > add(1, 2) : 3 >
12. Interpret Yaegi zabudovaný do aplikace
Interpret Yaegi je možné, podobně jako dále popsaný interpret Gomacro, zabudovat do vyvíjené aplikace. Ve stručnosti se podívejme, jakým způsobem to může být realizováno.
Inicializace interpretru pomocí New a spuštění skriptu metodou Eval. Vypíše se výsledek skriptu, tedy hodnota 42:
package main import ( "fmt" "github.com/traefik/yaegi/interp" "github.com/traefik/yaegi/stdlib" ) func RunYaegi(script string) any { interpreter := interp.New(interp.Options{}) interpreter.Use(stdlib.Symbols) ret, err := interpreter.Eval(script) if err != nil { panic(err) } return ret } func main() { fmt.Println(RunYaegi("6*7")) }
Složitější skript s deklaracemi proměnných a využitím jejich hodnot. Tento skript vrací hodnotu poslední deklarované proměnné (v Gomacru tomu ovšem bude jinak!):
package main import ( "fmt" "github.com/traefik/yaegi/interp" "github.com/traefik/yaegi/stdlib" ) const script = ` x:=6 y:=7 z:=x*y ` func RunYaegi(script string) any { interpreter := interp.New(interp.Options{}) interpreter.Use(stdlib.Symbols) ret, err := interpreter.Eval(script) if err != nil { panic(err) } return ret } func main() { fmt.Println(RunYaegi(script)) }
Úprava předchozího skriptu tak, že se v něm bude explicitně vracet hodnota nějakého výrazu. Toto chování je kompatibilní s dále popsaným Gomacrem:
package main import ( "fmt" "github.com/traefik/yaegi/interp" "github.com/traefik/yaegi/stdlib" ) const script = ` x:=6 y:=7 z:=x*y z ` func RunYaegi(script string) any { interpreter := interp.New(interp.Options{}) interpreter.Use(stdlib.Symbols) ret, err := interpreter.Eval(script) if err != nil { panic(err) } return ret } func main() { fmt.Println(RunYaegi(script)) }
Deklarace funkce, kterou bude možné později zavolat přes reflexi. Na rozdíl od Gomacra však není možné tuto funkci zavolat z toho samého skriptu:
package main import ( "fmt" "github.com/traefik/yaegi/interp" "github.com/traefik/yaegi/stdlib" ) const script = ` func multiply(a, b int) int { return a*b } ` func RunYaegi(script string) any { interpreter := interp.New(interp.Options{}) interpreter.Use(stdlib.Symbols) ret, err := interpreter.Eval(script) if err != nil { panic(err) } return ret } func main() { fmt.Println(RunYaegi(script)) }
13. Interpret Gomacro
Další interpret programovacího jazyka Go, se kterým se v dnešním článku setkáme, 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 a s nímž se blíže seznámíme v samostatné kapitole.
Samotný název projektu Gomacro vychází z toho, že je podporována i tvorba a expanze maker. Ovšem pod pojmem makro si musíme představit LISPovská makra (resp. makra pojatá tak, jak je tomu v LISPovských programovacích jazycích) a nikoli primitivní makra jazyka C. S koncepcí maker se podrobněji seznámíme v samostatném článku, protože se vyžaduje znalost AST (abstraktního syntaktického stromu) a způsobu jeho zpracování v jazyce Go.
14. Instalace Gomacra s jeho spuštěním
Instalace tohoto projektu se opět provede, jak je ve světě jazyka Go zvykem, příkazem „go get“:
$ go install github.com/cosmos72/gomacro@latest go: downloading github.com/cosmos72/gomacro v0.0.0-20240506194242-2ff796e3da10 go: downloading github.com/peterh/liner v1.2.2 go: downloading golang.org/x/tools v0.14.0 go: downloading github.com/mattn/go-runewidth v0.0.15 go: downloading github.com/rivo/uniseg v0.2.0 go: downloading golang.org/x/sys v0.13.0 go: downloading golang.org/x/mod v0.13.0
Dále je vhodné se přesvědčit o tom, že je adresář $HOME/go/bin zařazen do proměnné prostředí PATH (což platí pro všechny tři zmíněné interpretry). V opačném případě by totiž nebylo možné příkaz gomacro volat odkudkoli (což platí i pro další nástroje naprogramované v jazyku Go a instalované příkazem „go get“).
Pokud je $HOME/go/bin vložen do proměnné prostředí PATH, bude možné Gomacro spustit, například s přepínačem –help:
$ gomacro --help usage: gomacro [OPTIONS] [files-and-dirs] Recognized options: -c, --collect collect declarations and statements, to print them later -e, --expr EXPR evaluate expression -f, --force-overwrite option -w will overwrite existing files -g, --genimport [PATH] write x_package.go bindings for specified import path and exit. Use "gomacro -g ." or omit path to import the current dir. Used in "//go:generate gomacro -g ." directives. -h, --help show this help and exit -i, --repl interactive. start a REPL after evaluating expression, files and dirs. default: start a REPL only if no expressions, files or dirs are specified -m, --macro-only do not execute code, only parse and macroexpand it. useful to run gomacro as a Go preprocessor -n, --no-trap do not trap panics in the interpreter -t, --trap trap panics in the interpreter (default) -s, --silent silent. do NOT show startup message, prompt, and expressions results. default when executing files and dirs. -v, --verbose verbose. show startup message, prompt, and expressions results. default when executing an expression. -vv, --very-verbose as -v, and in addition show the type of expressions results. default when executing a REPL -w, --write-decls write collected declarations and statements to *.go files. implies -c -x, --exec execute parsed code (default). disabled by -m Options are processed in order, except for -i that is always processed as last. Collected declarations and statements can be also written to standard output or to a file with the REPL command :write
Spuštění interpretru:
$ gomacro // Welcome to gomacro. Type :help for help, :copy for copyright and license. // This is free software with ABSOLUTELY NO WARRANTY. gomacro>
// warning: could not find package "github.com/cosmos72/gomacro" in $GOPATH = "/home/tester/go/", assuming package is located in "/home/tester/go/src/github.com/cosmos72/gomacro"
15. Interní příkazy interpretru Gomacro
I interpret Gomacro má svůj vlastní repertoár interních příkazů (či pseudopříkazů). Ty začínají dvojtečkou, takže se zde používá stejný formát zápisu, jako v případě projektu Gore. Všechny tyto pseudopříkazy si můžeme vypsat pseudopříkazem :help:
gomacro> :help // type Go code to execute it. example: func add(x, y int) int { return x + y } // interpreter commands: :copyright show copyright and license :debug EXPR debug expression or statement interactively :env [NAME] show available functions, variables and constants in current package, or from imported package NAME :help show this help :inspect EXPR|TYPE inspect expression or type interactively :options [OPTS] show or toggle interpreter options :package "PKGPATH" switch to package PKGPATH, importing it if possible :quit quit the interpreter :unload "PKGPATH" remove package PKGPATH from the list of known packages. later attempts to import it will trigger a recompile :write [FILE] write collected declarations and/or statements to standard output or to FILE use :opt Declarations and/or :opt Statements to start collecting them
V dalších kapitolách se seznámíme zejména s pseudopříkazy :inspect (inspektor) a :debug (debugger). Ovšem zajímavý je i způsob manipulace s balíčky s využitím :package a :unload. Protože se zde přímo nepracuje s postupně vytvářeným zdrojovým kódem v jazyce Go, nelze použít příkaz :print, ovšem můžeme si nechat vypsat všechny zadané příkazy a deklarace pseudopříkazem :write.
16. Inspektor v interpretru Gomacro
Jedním z nejužitečnějších nástrojů Gomacra je takzvaný inspector. Ten nám umožňuje například prozkoumat výrazy:
gomacro> :inspect 1*(2+3) 1*(2+3) = {int 5} // untyped.Lit 0. Kind = int // untyped.Kind 1. Val = 5 // constant.Value 2. basicTypes = [<nil> 0x11060b0 0x11060b0 0x11060b0 0x11060b0 0x11060b0 0x11060b0 0x11060b0 0x11060b0 0x11060b0 0x11060b0 0x11060b0 0x11060b0 0x11060b0 0x11060b0 0x11060b0 0x11060b0 <nil> <nil> <nil> <nil> <nil> <nil> <nil> 0x11060b0 <nil> 0x11060b0] // *[]xreflect.Type // type ? for inspector help
Prozkoumat lze i volání funkcí, kdy se mj. zobrazí jejich výsledek:
gomacro> import "fmt" gomacro> :inspect fmt.Printf("%d\n", 42) // warning: expression returns 2 values, using only the first one: [int error] 42 fmt.Printf("%d\n", 42) = 3 // int // type ? for inspector help
Popř. lze vypsat metody aplikovatelné pro zvoleného příjemce (receiver). Pro numerické výrazy jsou to metody datového typu int (ty se nevolají jménem, ale typicky nějakým operátorem):
inspect fmt.Printf("%d\n", 42)> methods methods of int: m0. func (int).Add(int, int) int m1. func (int).And(int, int) int m2. func (int).AndNot(int, int) int m3. func (int).Cmp(int) int m4. func (int).Equal(int) bool m5. func (int).Less(int) bool m6. func (int).Lsh(int, uint8) int m7. func (int).Mul(int, int) int m8. func (int).Neg(int) int m9. func (int).Not(int) int m10. func (int).Or(int, int) int m11. func (int).Quo(int, int) int m12. func (int).Rem(int, int) int m13. func (int).Rsh(int, uint8) int m14. func (int).Sub(int, int) int m15. func (int).Xor(int, int) int
Výpis takzvaného prostředí, tj. dostupných funkcí a datových typů:
gomacro> :env // ----- builtin binds ----- Eval = {0x1213270 func(interface{}, interface{}) interface{}} // fast.Function EvalKeepUntyped = {0x1213320 func(interface{}, interface{}) interface{}} // fast.Function EvalType = {0x1213660 func(interface{}, interface{}) reflect.Type} // fast.Function Interp = {0x1213240 func(interface{}) interface{}} // fast.Function MacroExpand = {0x1214310 func(interface{}, interface{}) (go/ast.Node, bool)} // fast.Function MacroExpand1 = {0x1214400 func(interface{}, interface{}) (go/ast.Node, bool)} // fast.Function MacroExpandCodeWalk = {0x12144f0 func(interface{}, interface{}) (go/ast.Node, bool)} // fast.Function Parse = {0x1216310 func(string, interface{}) interface{}} // fast.Function append = 0x120efd0 // fast.Builtin cap = 0x120fb30 // fast.Builtin close = 0x1210290 // fast.Builtin complex = 0x1210880 // fast.Builtin copy = 0x1211f00 // fast.Builtin delete = 0x1212ae0 // fast.Builtin false = {bool false} // untyped.Lit imag = 0x1216e30 // fast.Builtin len = 0x1213960 // fast.Builtin make = 0x1214bd0 // fast.Builtin new = 0x1215bb0 // fast.Builtin nil = nil // <nil> panic = 0x1216020 // fast.Builtin print = 0x1216a60 // fast.Builtin println = 0x1216a60 // fast.Builtin real = 0x1216e30 // fast.Builtin recover = 0x12180e0 // fast.Builtin true = {bool true} // untyped.Lit // ----- builtin types ----- Pointer = unsafe.Pointer // unsafe.Pointer bool = bool // bool byte = uint8 // uint8 complex128 = complex128 // complex128 complex64 = complex64 // complex64 error = error // interface float32 = float32 // float32 float64 = float64 // float64 int = int // int int16 = int16 // int16 int32 = int32 // int32 int64 = int64 // int64 int8 = int8 // int8 rune = int32 // int32 string = string // string uint = uint // uint uint16 = uint16 // uint16 uint32 = uint32 // uint32 uint64 = uint64 // uint64 uint8 = uint8 // uint8 uintptr = uintptr // uintptr // ----- main binds ----- fmt = {fmt "fmt", 19 binds, 6 types} // *fast.Import
Prozkoumání vlastností zvoleného datového typu:
gomacro> :inspect bool bool = false // bool // type ? for inspector help inspect bool> methods methods of bool: m0. func (bool).Equal(bool) bool m1. func (bool).Not(bool) bool
17. Debugger integrovaný do Gomacra
Již v předchozích kapitolách jsme se zmínili o interním příkazu :debug, kterým se spouští debugger zabudovaný do nástroje Gomacro. Ukažme si použití tohoto nástroje na několika příkladech.
Nejprve si nadeklarujeme jednoduchou funkci pro součet dvou celých čísel:
gomacro> func add(x, y int) int { . . . . z := x . . . . z += y . . . . return z . . . . }
Tuto funkci pochopitelně můžeme přímo zavolat a získáme její výsledek:
gomacro> add(1,2) 3 // int
Ovšem můžeme ji zavolat i v rámci debuggeru, a to následujícím způsobem:
gomacro> :debug add(1,2) // stopped at repl.go:1:1 IP=0, call depth=1. type ? for debugger help func add(x, y int) int { ^^^
V debuggeru (změní se výzva) lze používat několik nových příkazů, které se píšou bez dvojtečky a lze je typicky zkrátit na jediný znak (n=next atd.):
// debugger commands: backtrace show call stack env [NAME] show available functions, variables and constants in current scope, or from imported package NAME ? show this help help show this help inspect EXPR inspect expression interactively kill [EXPR] terminate execution with panic(EXPR) print EXPR print expression, statement or declaration list show current source code continue resume normal execution finish run until the end of current function next execute a single statement, skipping functions step execute a single statement, entering functions vars show local variables
Nejčastějším příkazem bude s (step) a n (next). Tyto příkazy jsou určeny pro krokování:
debug> n // stopped at repl.go:2:1 IP=1, call depth=1. type ? for debugger help z := x ^^^ debug> n // stopped at repl.go:3:1 IP=2, call depth=1. type ? for debugger help z += y ^^^ debug> n // stopped at repl.go:4:8 IP=3, call depth=1. type ? for debugger help return z ^^^ debug> n 3 // int
Zobrazit si můžeme i všechny v danou chvíli dostupné symboly. Lokální proměnné jsou zobrazeny na konci:
debug> env ... ... ... // ----- binds ----- x = 1 // int y = 2 // int z = 3 // int
Příkazem print se vypíše obsah téměř jakéhokoli výrazu, což je při ladění velmi užitečné:
debug> print z 3 // int debug> n // stopped at repl.go:3:1 IP=2, call depth=1. type ? for debugger help z += y ^^^ debug> print z 1 // int
Ve druhé ukázce použijeme složitější funkci, konkrétně funkci pro výpočet faktoriálu:
gomacro> import "math/big" gomacro> func factorial(n *big.Int) *big.Int { . . . . one := big.NewInt(1) . . . . if n.Cmp(big.NewInt(0)) <= 0 { . . . . return one . . . . } . . . . return one.Mul(n, factorial(one.Sub(n, one))) . . . . } gomacro>
Základní otestování funkcionality:
gomacro> factorial(big.NewInt(10)) 3628800 // *math/big.Int
Ladění / krokování této funkce:
gomacro> :debug factorial(big.NewInt(10)) // stopped at repl.go:1:1 IP=0, call depth=1. type ? for debugger help func factorial(n *big.Int) *big.Int { ^^^
Jedná se o rekurzivní funkci, takže se bude hodit příkaz backtrace, resp. jen b pro zobrazení jejího volání (adres a argumentů uložených na zásobníku):
debug> backtrace 0xc000472000 func factorial(n=<*big.Int Value> <*math/big.Int>) <*big.Int Value> <*math/big.Int> 0xc0004720a0 func factorial(n=<*big.Int Value> <*math/big.Int>) <*big.Int Value> <*math/big.Int> 0xc000472140 func factorial(n=<*big.Int Value> <*math/big.Int>) <*big.Int Value> <*math/big.Int>
18. Interpret Gomacro zabudovaný do aplikace
I Gomacro je možné zabudovat přímo do vyvíjené aplikace a tak rozšířit její možnosti (typicky o řád, jak jsme si řekli v úvodní kapitole). Podívejme se na čtyři demonstrační příklady, které tyto možnosti ukazují.
Spuštění triviálního skriptu s výrazem „6*7“ a výpisem výsledku tohoto výrazu (opět se zde používá klasická funkce New následovaná voláním metody Eval):
package main import ( "fmt" "github.com/cosmos72/gomacro/fast" ) func RunGomacro(script string) any { interp := fast.New() vals, _ := interp.Eval(script) return vals[0].ReflectValue() } func main() { fmt.Println(RunGomacro("6*7")) }
Složitější víceřádkový skript s deklarací proměnných. Tento skript nevrací žádnou hodnotu, takže do kódu je doplněn test, zda skript vrací hodnotu (hodnoty) či nikoli:
package main import ( "fmt" "github.com/cosmos72/gomacro/fast" ) const script = ` x:=6 y:=7 z:=x*y ` func RunGomacro(script string) any { interp := fast.New() vals, _ := interp.Eval(script) if len(vals) < 1 { return "no value" } return vals[0].ReflectValue() } func main() { fmt.Println(RunGomacro(script)) }
Doplnění skriptu o výraz „z“, který způsobí, že se vrátí hodnota proměnné s tímto jménem:
package main import ( "fmt" "github.com/cosmos72/gomacro/fast" ) const script = ` x:=6 y:=7 z:=x*y z ` func RunGomacro(script string) any { interp := fast.New() vals, _ := interp.Eval(script) if len(vals) < 1 { return "no value" } return vals[0].ReflectValue() } func main() { fmt.Println(RunGomacro(script)) }
A konečně deklarace funkce uvnitř skriptu s voláním této funkce:
package main import ( "fmt" "github.com/cosmos72/gomacro/fast" ) const script = ` func multiply(a, b int) int { return a*b } x:=6 y:=7 multiply(x, y) ` func RunGomacro(script string) any { interp := fast.New() vals, _ := interp.Eval(script) if len(vals) < 1 { return "no value" } return vals[0].ReflectValue() } func main() { fmt.Println(RunGomacro(script)) }
19. Repositář s demonstračními příklady
Zdrojové kódy interně vygenerované interpretrem Gore byly uloženy do Git repositáře, jenž je dostupný na adrese https://github.com/RedHatOfficial/GoCourse. Jednotlivé demonstrační příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý (dnes již poměrně rozsáhlý) repositář:
Další čtveřice příkladů ukazuje použití Gomacra pro spuštění skriptů přímo z aplikace psané v jazyku Go:
# | Příklad | Stručný popis | Adresa |
---|---|---|---|
1 | gomacro_eval1.go | spuštění jednoduchého skriptu s jediným výrazem | https://github.com/RedHatOfficial/GoCourse/blob/master/lesson13/gomacro/gomacro_eval1.go |
2 | gomacro_eval2.go | skript s deklaracemi proměnných, který nevrací žádnou hodnotu | https://github.com/RedHatOfficial/GoCourse/blob/master/lesson13/gomacro/gomacro_eval2.go |
3 | gomacro_eval3.go | skript s deklaracemi proměnných, který vrací hodnotu | https://github.com/RedHatOfficial/GoCourse/blob/master/lesson13/gomacro/gomacro_eval3.go |
4 | gomacro_eval4.go | skript obsahující deklaraci funkce, která je posléze zavolána | https://github.com/RedHatOfficial/GoCourse/blob/master/lesson13/gomacro/gomacro_eval4.go |
Poslední čtveřice příkladů ukazuje použití Yaegi pro spuštění skriptů přímo z aplikace psané v jazyku Go:
# | Příklad | Stručný popis | Adresa |
---|---|---|---|
1 | yaegi_eval1.go | spuštění jednoduchého skriptu s jediným výrazem | https://github.com/RedHatOfficial/GoCourse/blob/master/lesson13/yaegi/yaegi_eval1.go |
2 | yaegi_eval2.go | skript s deklaracemi proměnných, který nevrací žádnou hodnotu | https://github.com/RedHatOfficial/GoCourse/blob/master/lesson13/yaegi/yaegi_eval2.go |
3 | yaegi_eval3.go | skript s deklaracemi proměnných, který vrací hodnotu | https://github.com/RedHatOfficial/GoCourse/blob/master/lesson13/yaegi/yaegi_eval3.go |
4 | yaegi_eval4.go | skript obsahující deklaraci funkce | https://github.com/RedHatOfficial/GoCourse/blob/master/lesson13/yaegi/yaegi_eval4.go |
20. Odkazy na Internetu
- 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/ - Package names
https://go.dev/blog/package-names - The Go Programming Language Specification
https://go.dev/ref/spec - GoDoc: dokumentace k balíčkům
https://godoc.org/ - Go (programming language), Wikipedia
https://en.wikipedia.org/wiki/Go_(programming_language) - Go: the Good, the Bad and the Ugly (dnes již v některých ohledech zastaralé)
https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/ - 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/ - Gophernotes
https://github.com/gopherdata/gophernotes - Jupyter Notebook – nástroj pro programátory, výzkumníky i lektory
https://www.root.cz/clanky/jupyter-notebook-nastroj-pro-programatory-vyzkumniky-i-lektory/ - 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 - Top 13 Scripting Languages You Should Pay Attention To
https://kinsta.com/blog/scripting-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