前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >golang with script

golang with script

原创
作者头像
王磊-字节跳动
修改2021-10-21 21:03:09
3.1K0
修改2021-10-21 21:03:09
举报
文章被收录于专栏:01ZOO01ZOO

背景

go 是一种静态语言,运行需要先编译。实际我们在使用过程中,有时候希望 go 能够像脚步语言一样执行一些动态的命令,这种功能至少有以下的好处:

  • 学习使用,作为一种 repl 工具验证语法
  • 快速验证某个包的功能,或者某种远程资源内容
  • 作为工程的嵌入代码,提供灵活性,比如作为一个爬虫项目,抓取脚本的改动肯定不希望整个工程都要重新编译
  • 做为测试脚本使用,脚本语言开发快,验证快,比较适合开发一些测试工具

开源项目

工具

语法

作为 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

说明

  1. 工具整理于 202110
  2. 作为嵌入脚本,都要考虑能否使用 go 语言自带包或者开发第三方库来丰富他的功能,这点 gopher-lua 比较方便
  3. 作为嵌入脚本,要降低使用成本,使用已经存在的、简单的语言的优势比较明显,比如 lua、starlark
  4. 能否动态 import 第三方库对于作为 repl 使用是一个很重要的功能,这点只有 gomacro 支持
  5. 可用性得分档为 很低/低/中/高/很高
  6. 主要有两种实现,VM 或者 Interpreter,一般来说 VM 实现的性能会高不少【基于 vm 有一些优化策略】,不过并不绝对。比如 starlark-go 文档中认为用 go 实现 vm 效率并没有优势,但是目前 starlark-go 的实现也是 vm (docs 比较陈旧,代码已经是 vm 实现了)

扩展

使用上面点任何一种内核实现,要面对的一个重要问题是,如何进行高效的扩展。比如使用 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 等第三方包,而整个实现也百行左右代码。

代码语言:txt
复制
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 等很多第三方包:

代码语言:txt
复制
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 就可以

代码语言:txt
复制
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 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 开源项目
    • 说明
    • 扩展
    • 演示
    • 更多
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档