写点什么

goscript: 遇见 yaegi 所带出的思路

作者:humboldt
  • 2022 年 5 月 12 日
  • 本文字数:1840 字

    阅读完需:约 6 分钟

最近,看见 python 出了个 pyscript ,据说可以在浏览器里面使用 python 脚本。


作为一个 gopher,怎么不想在浏览器里面也带个 go 呢?


遇见 yaegi


恰巧,最近,在 github 上,就看见有一个神奇的项目即 yaegi ,这个是一个"神奇"的 go 解释程序。


它实现了和宿主程序的无缝对接。包括类型映射等等。


比如,想要调用脚本里面的一个函数,我们可以:


package main
import "github.com/traefik/yaegi/interp"
const src = `package foofunc Bar(s string) string { return s + "-Foo" }`
func main() { i := interp.New(interp.Options{})
_, err := i.Eval(src) if err != nil { panic(err) }
v, err := i.Eval("foo.Bar") if err != nil { panic(err) }
bar := v.Interface().(func(string) string)
r := bar("Kung") println(r)}
复制代码

以上代码来自官方例子


由这个例子,我们可以看出,内嵌函数居然可以直接类型转换成 go 原生的函数,并且调用。


对 yaegi 有兴趣的,可以转官网: https://github.com/traefik/yaegi


当 yaegi 遇到浏览器


对了,yaegi 确实很酷,但是它根 goscript 有什么关系。它终归只是 go 的内嵌语言。


当然有关系了,因为我们还有一个杀器,那就是 wasm。wasm 可以将 go 语言直接编译成 web


先来看看,我们这个 demo 的简单界面。

左边为一个“超”强大的文本输入框。


再加上一个运行按钮 这就是我们的界面全部。


index.html 代码参考:

<html>
<head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <script src="static/wasm_exec.js"></script> <script> const go = new Go(); WebAssembly.instantiateStreaming(fetch("static/main.wasm"), go.importObject) .then((result) => go.run(result.instance)); </script></head>
<body> <textarea id="edit" style='width:600px;height:600px'>import "time"func main(){ for { time.Sleep(time.Second) fmt.Printf("hello\n") }} </textarea> <button onclick='call(document.getElementById("edit").value)'>运行</button></body>
</html>
复制代码


main.go 代码参考:


// main.gopackage main
import ( "fmt" "reflect" "syscall/js"
"github.com/traefik/yaegi/interp" "github.com/traefik/yaegi/stdlib")
var Symbols = map[string]map[string]reflect.Value{}
func call(this js.Value, args []js.Value) interface{} { defer func() { recover() }() fmt.Println("eval", args[0].String()) code := args[0].String() go func(code string) { defer func() { recover() }() i := interp.New(interp.Options{})
i.Use(stdlib.Symbols) i.Use(Symbols)
_, err := i.Eval(`import "fmt"`) if err != nil { fmt.Printf("%s\n", err) return } _, err = i.Eval(`import "global"`) if err != nil { fmt.Printf("%s\n", err) return } _, err = i.Eval(code) if err != nil { fmt.Printf("%s\n", err) return }
v, err := i.Eval("main.main") if err != nil { fmt.Printf("call main err:%s", err) }
main, ok := v.Interface().(func()) if ok { main() }
}(code) return nil}func init() { Symbols["global/global"] = map[string]reflect.Value{ // function, constant and variable definitions "Get": reflect.ValueOf(js.Global().Get), }}
func main() { kyes := js.Global().Get("Object").Get("keys").Invoke(js.Global()) for i := 0; i < kyes.Length(); i++ { key := kyes.Index(i).String() v := js.Global().Get(key) fmt.Printf("%v %v\n", key, v.Type()) } done := make(chan int, 0) js.Global().Set("call", js.FuncOf(call)) <-done}
复制代码


一切,也就这么多,100 行代码左右吧。


然后,在脚本里面如何调用浏览器函数?


看到代码中的 Symbols["global/global"] 了没? 我定义了一个 global 模块,专门用来获取 js 中的全局变量。


所以,我们可以:


仅需要:

global.Get("alert").Invoke("hello")
复制代码

就可以调用 js 的 alert 函数了。


当然,我们也可以将 alert 函数直接注册到 global 模块,那么就更方便了。


注意看代码的 68 行,那就是我写了一半的想法。


关于这个 demo,今天就写到这里了。


算是开了个坑,以后看什么时候再填一铲子。


补充:

如何编译:

GOOS=js GOARCH=wasm go build -o static/main.wasmcp "$(go env GOROOT)/misc/wasm/wasm_exec.js" static
复制代码


发布于: 刚刚阅读数: 3
用户头像

humboldt

关注

还未添加个人签名 2018.07.08 加入

还未添加个人简介

评论

发布
暂无评论
goscript: 遇见yaegi所带出的思路_Go_humboldt_InfoQ写作社区