go 是一种静态语言,运行需要先编译。实际我们在使用过程中,有时候希望 go 能够像脚步语言一样执行一些动态的命令,这种功能至少有以下的好处:
工具 | 语法 | 作为 repl 可用性 | 作为嵌入脚本可用性 | 原理 | 备注 |
---|---|---|---|---|---|
直接 go run | golang | 很低 | 中 | go run | 可以在go 语言文件上面加 env 标识,同时 对 go run 封装一下, 比如这样,可以动态获取包 |
golang like | 高(v0.7) | 高 | 两种模式,bytecode 模式使用 VM 实现,还有一种模式使用代码生成 + go run | v0.7 支持 repl,master 已经删除 | |
golang like | 高 | 高 | VM | 官方 bench 表示性能在同类中很高 | |
lua | 高 | 高 | VM | Shopify 实现 | |
lua | 高 | 很高 | VM | 文档丰富,扩展方便 | |
ECMAScript/JavaScript | 高 | 中 | VM | - | |
starlark(python 子集) | 高 | 高 | VM | Google 出品,语法是 python 子集,学习成本低且使用舒适 | |
python | 中 | 中 | Interpreter | 成熟度不高 | |
golang | 高 | 高 | Interpreter | traefik 出品 | |
golang | 中 | 很低 | 代码生成 + Interpreter | 只能用于做语法验证工具, 交互比较友好, 不能用于内嵌 | |
JavaScript | 中 | 高 | Interpreter | 项目比较活跃 | |
golang like | 中 | 中 | Interpreter | - | |
golang like | 高 | 低 | 代码生成 + go run | 作为 repl 工具还可以,支持代码提示 | |
golang like | 很高 | 中 | 代码生成 + Interpreter | 作为 repl 工具的最佳选择, 支持 import 第三方包, 功能非常健全,这个 go jupyter kernel 就是基于 gomacro |
使用上面点任何一种内核实现,要面对的一个重要问题是,如何进行高效的扩展。比如使用 gopher-lua,只是支持 lua 语言肯定不够,需要使用一些 lua 库。 gopher-lua 提供了一种 lua 调用 go 函数的方式,使得使用 go 编写 gopher-lua 可用的第三方库变成可能。事实上 gopher-lua 的介绍里面已经给出了一些第三方库的链接, 比如 http, json, yaml 等常见的第三方库都有了。
但是这还是不够,go 语言已经有非常丰富的生态,如果用一种很简单的办法直接使用 go 语言的库,那就很方便了。解决这个问题的办法是这样的一个库:https://github.com/layeh/gopher-luar 这个库的思路是通过 reflect 的办法封装 go 语言的库,方法,和类型,在 go 语言和 lua 语言之间做自动的映射,那么做 lua 的一个第三方库就变得非常方便了.
举例如下,例子作为我的 gopher-lua 第三方包已经提交到 github, 目前已经支持 http, strings, regexp, ioutil, exec, crypto, json/yaml, colly, resty 等第三方包,而整个实现也百行左右代码。
func NewLuaStateWithLib(opts ...lua.Options) *lua.LState {
L := lua.NewState(opts...)
InstallAll(L)
return L
}
func InstallAll(L *lua.LState) {
L.SetGlobal("http", luar.New(L, &Http{}))
L.SetGlobal("regexp", luar.New(L, &Regexp{}))
L.SetGlobal("ioutil", luar.New(L, &IoUtil{}))
L.SetGlobal("exec", luar.New(L, &Exec{}))
L.SetGlobal("time", luar.New(L, &Time{}))
L.SetGlobal("resty", luar.New(L, &Resty{}))
L.SetGlobal("colly", luar.New(L, &Colly{}))
// more... pls refer to: github.com/vadv/gopher-lua-libs
}
// Http ----------------------------------------------------------------------------------------------------------------
type Http struct{}
func (h Http) DefaultClient() *http.Client { return http.DefaultClient }
// Regex ---------------------------------------------------------------------------------------------------------------
type Regexp struct{}
func (r Regexp) Compile(a string) (*regexp.Regexp, error) { return regexp.Compile(a) }
func (r Regexp) Match(a string, b []byte) (bool, error) { return regexp.Match(a, b) }
func (r Regexp) MatchString(a string, b string) (bool, error) { return regexp.MatchString(a, b) }
// IoUtils -------------------------------------------------------------------------------------------------------------
type IoUtil struct{}
func (i IoUtil) ReadAll(a io.Reader) ([]byte, error) { return ioutil.ReadAll(a) }
func (i IoUtil) ReadFile(filename string) ([]byte, error) { return os.ReadFile(filename) }
func (i IoUtil) WriteFile(a string, b []byte, c fs.FileMode) error { return os.WriteFile(a, b, c) }
func (i IoUtil) ReadDir(dirname string) ([]fs.FileInfo, error) { return ioutil.ReadDir(dirname) }
type Url struct{}
func (u Url) Parse(a string) (*url.URL, error) { return url.Parse(a) }
// Cmd -----------------------------------------------------------------------------------------------------------------
type Exec struct{}
func (c Exec) Cmd(a ...string) *exec.Cmd { return exec.Command(a[0], a[1:]...) }
func (c Exec) Run(a ...string) ([]byte, []byte, error) {
out, err := bytes.NewBuffer(nil), bytes.NewBuffer(nil)
cmd := exec.Command(a[0], a[1:]...)
cmd.Stdout, cmd.Stderr = out, err
err1 := cmd.Run()
return out.Bytes(), err.Bytes(), err1
}
type Time struct{}
func (t Time) Parse(a, b string) (time.Time, error) { return time.Parse(a, b) }
func (t Time) Now() time.Time { return time.Now() }
// Colly ---------------------------------------------------------------------------------------------------------------
type Colly struct{}
func (c Colly) New() *colly.Collector { return colly.NewCollector() }
// Resty ---------------------------------------------------------------------------------------------------------------
type Resty struct{}
func (c Resty) New() *resty.Client { r := resty.New(); return r }
func (c Resty) NewRequestWithResult(a M) *resty.Request {
r := resty.New().NewRequest().SetResult(&a)
return r
}
gopher-luar 的实现思路,我进一步的扩展到了 starlark-go,形成了 starlark-go-lib, 在这个包里面,我提供了类似 gopher-luar 的基础设施,使得给 starlark-go 做一个第三方包变得极其容易,比如下面的例子, 使用很简单的代码就给starlark-go 提供了 http 等很多第三方包:
func InstallAllExampleModule(d starlark.StringDict) {
for _, v := range exampleModules {
d[v.Name] = v
}
}
var exampleModules = []*starlarkstruct.Module{
GreetModule,
{
Name: "modules",
Members: starlark.StringDict{
"all": ToValue(func() (ret []string) {
for _, v := range starlark.Universe {
if m, ok := v.(*starlarkstruct.Module); ok {
ret = append(ret, m.Name)
}
}
return
}),
"inspect": ToValue(func(a string) (ret []string) {
if v, ok := starlark.Universe[a]; ok {
if m, ok := v.(*starlarkstruct.Module); ok {
for x, y := range m.Members {
ret = append(ret, fmt.Sprintf("%s: [%s, %s]", x, y.Type(), y.String()))
}
}
}
return
}),
},
},
{
Name: "http",
Members: starlark.StringDict{
"get": ToValue(http.Get),
"pos": ToValue(http.Post),
"defaultClient": ToValue(http.DefaultClient),
},
},
{
Name: "ioutil",
Members: starlark.StringDict{
"read_all": ToValue(ioutil.ReadAll),
"read_file": ToValue(os.ReadFile),
"write_file": ToValue(os.WriteFile),
"read_dir": ToValue(os.ReadDir),
},
},
{
Name: "strings",
Members: starlark.StringDict{
"contains": ToValue(strings.Contains),
"split": ToValue(strings.Split),
},
},
{
Name: "url",
Members: starlark.StringDict{
"parse": ToValue(url.Parse),
},
},
{
Name: "resty",
Members: starlark.StringDict{
"new": ToValue(resty.New),
},
},
}
为了使用这几个包更方便,我做了一个 go 实现了 jupyter kernel 的模板 u2takey/gopyter, 这个包对 gophernotes 的实现进行了改进,通过这个模板实现你自己的 kernel 也会很简单,只用实现如下的 interface 就可以
type KernelInterface interface {
Init() error
Eval(outerr OutErr, code string) (val []interface{}, err error)
Name() string
}
这个包里面提供了带大量第三方库的 lua-go, stark-go 的 kernel 实现。最终使用示例如下:
gopher-luar 和 starlark-go-lib 已经把在 gopher-lua 和 starlark-go 中使用 go 语言的第三方包变得极其容易。当然可以做得更好,我们可以支持类似 import 的语句,自动下载第三方包,并作语法分析,生成到 gopher-lua 和 starlark-go 中作为第三方包,逻辑上并不困难。不过真的有必要做到这一步吗,因为作为 repl 工具,gomacro 已经做得足够好了,还是止步于此吧。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。