github.com/zyedidia/knit@v1.1.2-0.20230901152954-f7d4e39a0e24/vm.go (about)

     1  package knit
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"io/fs"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"regexp"
    12  	"runtime"
    13  	"strconv"
    14  	"strings"
    15  
    16  	"github.com/gobwas/glob"
    17  	"github.com/kballard/go-shellquote"
    18  	"github.com/zyedidia/generic/stack"
    19  	lua "github.com/zyedidia/gopher-lua"
    20  	luar "github.com/zyedidia/gopher-luar"
    21  	"github.com/zyedidia/knit/expand"
    22  )
    23  
    24  // A LuaVM tracks the Lua state and keeps a stack of directories that have been
    25  // entered.
    26  type LuaVM struct {
    27  	L     *lua.LState
    28  	wd    *stack.Stack[string]
    29  	shell string // shell used to execute commands
    30  	flags Flags  // flags are accessible to Lua programs
    31  }
    32  
    33  // An LRule is an un-parsed Lua representation of a build rule.
    34  type LRule struct {
    35  	Contents string
    36  	File     string
    37  	Line     int
    38  }
    39  
    40  func (r LRule) String() string {
    41  	return "$ " + r.Contents
    42  }
    43  
    44  // An LRuleSet is a list of LRules.
    45  type LRuleSet []LRule
    46  
    47  func (rs LRuleSet) String() string {
    48  	buf := &bytes.Buffer{}
    49  	buf.WriteString("r{\n")
    50  	for _, r := range rs {
    51  		buf.WriteString(strings.TrimSpace(r.String()) + "\n")
    52  	}
    53  	buf.WriteByte('}')
    54  	return buf.String()
    55  }
    56  
    57  // An LBuildSet is a list of rules associated with a directory.
    58  type LBuildSet struct {
    59  	Dir  string
    60  	rset LRuleSet
    61  	// list of build sets, relative to the root buildset
    62  	bsets []LBuildSet
    63  }
    64  
    65  func (b *LBuildSet) Add(vals *lua.LTable, vm *LuaVM) {
    66  	vals.ForEach(func(key lua.LValue, val lua.LValue) {
    67  		switch v := val.(type) {
    68  		case *lua.LUserData:
    69  			switch u := v.Value.(type) {
    70  			case LBuildSet:
    71  				u.Dir = filepath.Join(vm.Wd(), u.Dir)
    72  				b.bsets = append(b.bsets, u)
    73  			case LRuleSet:
    74  				b.rset = append(b.rset, u...)
    75  			case LRule:
    76  				b.rset = append(b.rset, u)
    77  			default:
    78  				vm.Err(fmt.Errorf("invalid buildset item: %v of type %v", u, v.Type()))
    79  			}
    80  		case *lua.LTable:
    81  			b.Add(v, vm)
    82  		default:
    83  			vm.Err(fmt.Errorf("invalid buildset item: %v of type %v", v, v.Type()))
    84  		}
    85  	})
    86  }
    87  
    88  func (bs *LBuildSet) String() string {
    89  	buf := &bytes.Buffer{}
    90  	buf.WriteString("b({\n")
    91  	buf.WriteString(bs.rset.String() + ", ")
    92  	for _, s := range bs.bsets {
    93  		buf.WriteString(s.String() + ", ")
    94  	}
    95  	buf.WriteString("\n}, ")
    96  	buf.WriteString(strconv.Quote(bs.Dir))
    97  	buf.WriteByte(')')
    98  	return buf.String()
    99  }
   100  
   101  // NewLuaVM constructs a new VM, and adds all the default built-ins.
   102  func NewLuaVM(shell string, flags Flags) *LuaVM {
   103  	// TODO: make this only enabled in debug mode (the stack trace)
   104  	L := lua.NewState(lua.Options{SkipOpenLibs: true, IncludeGoStackTrace: true})
   105  	vm := &LuaVM{
   106  		L:     L,
   107  		wd:    stack.New[string](),
   108  		shell: shell,
   109  		flags: flags,
   110  	}
   111  	vm.wd.Push(".")
   112  
   113  	vm.OpenDefaults()
   114  	vm.OpenKnit()
   115  
   116  	rvar, rexpr := vm.ExpandFuncs()
   117  
   118  	// Rules
   119  	mkrule := func(rule string, file string, line int) LRule {
   120  		// ignore errors during Lua-time rule expansion
   121  		s, _ := expand.Expand(rule, rvar, rexpr, false)
   122  		return LRule{
   123  			Contents: s,
   124  			File:     file,
   125  			Line:     line,
   126  		}
   127  	}
   128  	rmt := luar.MT(L, LRule{})
   129  	L.SetField(rmt.LTable, "__tostring", luar.New(L, func(r LRule) string {
   130  		return r.String()
   131  	}))
   132  	L.SetGlobal("_rule", luar.New(L, mkrule))
   133  	L.SetGlobal("rule", luar.New(L, func(rule string) LRule {
   134  		dbg, ok := L.GetStack(1)
   135  		file := "<rule>"
   136  		line := 0
   137  		if ok {
   138  			L.GetInfo("nSl", dbg, nil)
   139  			file = dbg.Source
   140  			line = dbg.CurrentLine
   141  		}
   142  		return mkrule(rule, file, line)
   143  	}))
   144  	L.SetGlobal("rulefile", luar.New(L, func(file string) LRule {
   145  		data, err := os.ReadFile(file)
   146  		if err != nil {
   147  			vm.Err(err)
   148  		}
   149  		return LRule{
   150  			Contents: string(data),
   151  			File:     file,
   152  			Line:     1,
   153  		}
   154  	}))
   155  
   156  	// Rule sets
   157  	rsmt := luar.MT(L, LRuleSet{})
   158  	L.SetField(rsmt.LTable, "__add", luar.New(L, func(r1, r2 LRuleSet) LRuleSet {
   159  		rules := make(LRuleSet, len(r1)+len(r2))
   160  		copy(rules, r1)
   161  		copy(rules[len(r1):], r2)
   162  		return rules
   163  	}))
   164  	L.SetField(rsmt.LTable, "__tostring", luar.New(L, func(rs LRuleSet) string {
   165  		return rs.String()
   166  	}))
   167  	L.SetGlobal("r", luar.New(L, func(ruletbls ...[]LRule) LRuleSet {
   168  		rules := make(LRuleSet, 0)
   169  		for _, rs := range ruletbls {
   170  			rules = append(rules, rs...)
   171  		}
   172  		return rules
   173  	}))
   174  
   175  	// Build sets
   176  	bsmt := luar.MT(L, LBuildSet{})
   177  	L.SetField(bsmt.LTable, "__tostring", luar.New(L, func(bs LBuildSet) string {
   178  		return bs.String()
   179  	}))
   180  	L.SetField(bsmt.LTable, "__add", luar.New(L, func(bs LBuildSet, lv lua.LValue) LBuildSet {
   181  		// TODO: copy bs instead of modifying it
   182  		switch u := lv.(type) {
   183  		case *lua.LUserData:
   184  			switch u := u.Value.(type) {
   185  			case LRule:
   186  				bs.rset = append(bs.rset, u)
   187  			case LRuleSet:
   188  				bs.rset = append(bs.rset, u...)
   189  			case LBuildSet:
   190  				bs.bsets = append(bs.bsets, u)
   191  			}
   192  		case *lua.LTable:
   193  			bs.Add(u, vm)
   194  		}
   195  		return bs
   196  	}))
   197  
   198  	L.SetGlobal("b", L.NewFunction(func(L *lua.LState) int {
   199  		lv := L.Get(1)
   200  		vals, ok := lv.(*lua.LTable)
   201  		if !ok {
   202  			vm.Err(fmt.Errorf("requires table, but got value %v", lv.Type()))
   203  		}
   204  		dir := L.OptString(2, ".")
   205  		b := LBuildSet{
   206  			Dir: filepath.Join(vm.Wd(), dir),
   207  		}
   208  		b.Add(vals, vm)
   209  		L.Push(luar.New(L, b))
   210  		return 1
   211  	}))
   212  
   213  	// Directory management
   214  	L.SetGlobal("dcall", luar.New(L, func(fn *lua.LFunction, args ...lua.LValue) lua.LValue {
   215  		var dbg lua.Debug
   216  		_, err := L.GetInfo(">nSl", &dbg, fn)
   217  		if err != nil {
   218  			vm.Err(err)
   219  			return lua.LNil
   220  		}
   221  		from := vm.EnterDir(filepath.Dir(dbg.Source))
   222  		vm.L.Push(fn)
   223  		for _, a := range args {
   224  			vm.L.Push(a)
   225  		}
   226  		vm.L.Call(len(args), lua.MultRet)
   227  		vm.LeaveDir(from)
   228  		return vm.L.Get(-1)
   229  	}))
   230  	L.SetGlobal("dcallfrom", luar.New(L, func(dir string, fn *lua.LFunction, args ...lua.LValue) lua.LValue {
   231  		from := vm.EnterDir(dir)
   232  		vm.L.Push(fn)
   233  		for _, a := range args {
   234  			vm.L.Push(a)
   235  		}
   236  		vm.L.Call(len(args), lua.MultRet)
   237  		vm.LeaveDir(from)
   238  		return vm.L.Get(-1)
   239  	}))
   240  	L.SetGlobal("rel", luar.New(L, func(files []string) *lua.LTable {
   241  		wd := vm.Wd()
   242  		if wd == "." {
   243  			return GoStrSliceToTable(vm.L, files)
   244  		}
   245  		rels := make([]string, 0, len(files))
   246  		for _, f := range files {
   247  			rels = append(rels, filepath.Join(wd, f))
   248  		}
   249  		return GoStrSliceToTable(vm.L, rels)
   250  	}))
   251  
   252  	// Include
   253  	L.SetGlobal("include", luar.New(L, func(path string) lua.LValue {
   254  		from := vm.EnterDir(filepath.Dir(path))
   255  		val, err := vm.DoFile(filepath.Base(path))
   256  		vm.LeaveDir(from)
   257  		if err != nil {
   258  			vm.Err(err)
   259  			return nil
   260  		}
   261  		return val
   262  	}))
   263  
   264  	L.SetGlobal("choose", luar.New(L, func(vals ...lua.LValue) lua.LValue {
   265  		for _, v := range vals {
   266  			if v != nil && v.Type() != lua.LTNil {
   267  				return v
   268  			}
   269  		}
   270  		return lua.LNil
   271  	}))
   272  
   273  	L.SetGlobal("sel", luar.New(L, func(cond bool, a, b lua.LValue) lua.LValue {
   274  		if cond {
   275  			return a
   276  		}
   277  		return b
   278  	}))
   279  
   280  	L.SetGlobal("tobool", luar.New(L, func(b lua.LValue) lua.LValue {
   281  		// nil just passes through
   282  		if b == nil || b.Type() == lua.LTNil {
   283  			return b
   284  		}
   285  		switch v := b.(type) {
   286  		case lua.LString:
   287  			// a string becomes false if it is falsy, otherwise true
   288  			switch v {
   289  			case "false", "FALSE", "off", "OFF", "0":
   290  				return lua.LFalse
   291  			}
   292  		case lua.LBool:
   293  			// booleans remain the same
   294  			return v
   295  		}
   296  		// anything else is true
   297  		return lua.LTrue
   298  	}))
   299  
   300  	// TODO: should we override the default tostring and print?
   301  
   302  	// Lua string formatting
   303  	format := func(s string) string {
   304  		s, err := expand.Expand(s, rvar, rexpr, true)
   305  		if err != nil {
   306  			vm.Err(err)
   307  		}
   308  		return s
   309  	}
   310  	// expand and throw an error if something is invalid
   311  	L.SetGlobal("f", luar.New(L, format))
   312  	L.SetGlobal("_format", luar.New(L, format))
   313  	// expand without throwing an error for invalid expansions
   314  	L.SetGlobal("expand", luar.New(L, func(s string) string {
   315  		ret, _ := expand.Expand(s, rvar, rexpr, true)
   316  		return ret
   317  	}))
   318  
   319  	// Lua eval
   320  	L.SetGlobal("eval", luar.New(L, func(s string) lua.LValue {
   321  		file := "<eval>"
   322  		wd := vm.Wd()
   323  		if wd != "." {
   324  			file = filepath.Join(wd, file)
   325  		}
   326  		v, err := vm.Eval(strings.NewReader("return "+s), file)
   327  		if err != nil {
   328  			vm.Err(err)
   329  			return lua.LNil
   330  		}
   331  		return v
   332  	}))
   333  
   334  	L.SetGlobal("use", luar.New(L, func(v *lua.LTable) {
   335  		globals := L.GetGlobal("_G").(*lua.LTable)
   336  		v.ForEach(func(key, val lua.LValue) {
   337  			globals.RawSet(key, val)
   338  		})
   339  	}))
   340  
   341  	return vm
   342  }
   343  
   344  // EnterDir changes into 'dir' and returns the path of the directory that was
   345  // changed out of.
   346  func (vm *LuaVM) EnterDir(dir string) string {
   347  	wd, err := os.Getwd()
   348  	if err != nil {
   349  		vm.Err(err)
   350  	}
   351  	err = os.Chdir(dir)
   352  	if err != nil {
   353  		vm.Err(err)
   354  	}
   355  	vm.wd.Push(dir)
   356  	return wd
   357  }
   358  
   359  // LeaveDir returns to the directory 'to' (usually the value returned by
   360  // 'EnterDir').
   361  func (vm *LuaVM) LeaveDir(to string) {
   362  	vm.wd.Pop()
   363  	err := os.Chdir(to)
   364  	if err != nil {
   365  		vm.Err(err)
   366  	}
   367  }
   368  
   369  // Wd returns the current working directory.
   370  func (vm *LuaVM) Wd() string {
   371  	return vm.wd.Peek()
   372  }
   373  
   374  // Err causes the VM to Lua-panic with 'err'.
   375  func (vm *LuaVM) Err(err error) {
   376  	vm.ErrStr(err.Error())
   377  }
   378  
   379  // ErrStr causes the VM to Lua-panic with a string message 'err'.
   380  func (vm *LuaVM) ErrStr(err string) {
   381  	vm.L.Error(lua.LString(err), 1)
   382  }
   383  
   384  // Eval runs the Lua code in 'r' with the filename 'file' and all local/global
   385  // variables available in the current context. Returns the value that was
   386  // generated, or a possible error.
   387  func (vm *LuaVM) Eval(r io.Reader, file string) (lua.LValue, error) {
   388  	if fn, err := vm.L.Load(r, file); err != nil {
   389  		return nil, err
   390  	} else {
   391  		vm.L.SetFEnv(fn, getVars(vm.L))
   392  		vm.L.Push(fn)
   393  		err = vm.L.PCall(0, lua.MultRet, nil)
   394  		if err != nil {
   395  			return nil, err
   396  		}
   397  		return vm.L.Get(-1), nil
   398  	}
   399  }
   400  
   401  // DoFile executes the Lua code inside 'file'. The file will be executed from
   402  // the current directory, but the filename displayed for errors will be
   403  // relative to the previous working directory.
   404  func (vm *LuaVM) DoFile(file string) (lua.LValue, error) {
   405  	f, err := os.Open(file)
   406  	defer f.Close()
   407  	if err != nil {
   408  		return lua.LNil, err
   409  	}
   410  	if vm.Wd() != "." {
   411  		file = filepath.Join(vm.Wd(), file)
   412  	}
   413  	if fn, err := vm.L.Load(f, file); err != nil {
   414  		return nil, err
   415  	} else {
   416  		vm.L.Push(fn)
   417  		err = vm.L.PCall(0, lua.MultRet, nil)
   418  		if err != nil {
   419  			return nil, err
   420  		}
   421  		return vm.L.Get(-1), nil
   422  	}
   423  }
   424  
   425  // ExpandFuncs returns a set of functions used for expansion. The first expands
   426  // by looking up variables in the current Lua context, and the second evaluates
   427  // arbitrary Lua expressions.
   428  func (vm *LuaVM) ExpandFuncs() (func(string) (string, error), func(string) (string, error)) {
   429  	return func(name string) (string, error) {
   430  			v := vm.getVar(vm.L, name)
   431  			if v == nil || v.Type() == lua.LTNil {
   432  				return "", fmt.Errorf("expand: variable '%s' does not exist", name)
   433  			}
   434  			return LToString(v), nil
   435  		}, func(expr string) (string, error) {
   436  			v, err := vm.Eval(strings.NewReader("return "+expr), strconv.Quote(expr))
   437  			if err != nil {
   438  				return "", fmt.Errorf("expand: %w", err)
   439  			} else if v == nil || v.Type() == lua.LTNil {
   440  				return "nil", nil
   441  			}
   442  			return LToString(v), nil
   443  		}
   444  }
   445  
   446  // OpenDefaults opens all default Lua libraries: package, base, table, debug,
   447  // io, math, os, string.
   448  func (vm *LuaVM) OpenDefaults() {
   449  	for _, pair := range []struct {
   450  		n string
   451  		f lua.LGFunction
   452  	}{
   453  		{lua.LoadLibName, lua.OpenPackage}, // Must be first
   454  		{lua.BaseLibName, lua.OpenBase},
   455  		{lua.TabLibName, lua.OpenTable},
   456  		{lua.DebugLibName, lua.OpenDebug},
   457  		{lua.IoLibName, lua.OpenIo},
   458  		{lua.MathLibName, lua.OpenMath},
   459  		{lua.OsLibName, lua.OpenOs},
   460  		{lua.StringLibName, lua.OpenString},
   461  	} {
   462  		if err := vm.L.CallByParam(lua.P{
   463  			Fn:      vm.L.NewFunction(pair.f),
   464  			NRet:    0,
   465  			Protect: true,
   466  		}, lua.LString(pair.n)); err != nil {
   467  			panic(err)
   468  		}
   469  	}
   470  }
   471  
   472  // OpenKnit makes the 'knit' library available as a preloaded module.
   473  func (vm *LuaVM) OpenKnit() {
   474  	pkg := vm.pkgknit()
   475  	loader := func(L *lua.LState) int {
   476  		L.Push(pkg)
   477  		return 1
   478  	}
   479  	vm.L.PreloadModule("knit", loader)
   480  }
   481  
   482  // Returns a table containing all values exposed as part of the 'knit' library.
   483  func (vm *LuaVM) pkgknit() *lua.LTable {
   484  	pkg := vm.L.NewTable()
   485  
   486  	vm.L.SetField(pkg, "trim", luar.New(vm.L, strings.TrimSpace))
   487  	vm.L.SetField(pkg, "os", luar.New(vm.L, runtime.GOOS))
   488  	vm.L.SetField(pkg, "arch", luar.New(vm.L, runtime.GOARCH))
   489  	vm.L.SetField(pkg, "flags", luar.New(vm.L, vm.flags))
   490  	vm.L.SetField(pkg, "join", luar.New(vm.L, func(strs ...[]string) *lua.LTable {
   491  		if len(strs) == 0 {
   492  			return nil
   493  		}
   494  		size := 0
   495  		for _, slc := range strs {
   496  			size += len(slc)
   497  		}
   498  		result := make([]string, 0, size)
   499  		for _, slc := range strs {
   500  			result = append(result, slc...)
   501  		}
   502  		return GoStrSliceToTable(vm.L, result)
   503  	}))
   504  	vm.L.SetField(pkg, "glob", luar.New(vm.L, func(pattern string) *lua.LTable {
   505  		f, err := filepath.Glob(pattern)
   506  		if err != nil {
   507  			vm.Err(err)
   508  		}
   509  		return GoStrSliceToTable(vm.L, f)
   510  	}))
   511  	vm.L.SetField(pkg, "rglob", luar.New(vm.L, func(path, pattern string) *lua.LTable {
   512  		g, err := glob.Compile(pattern)
   513  		if err != nil {
   514  			vm.Err(err)
   515  			return nil
   516  		}
   517  		files := []string{}
   518  		err = filepath.Walk(path, func(path string, info fs.FileInfo, err error) error {
   519  			if err != nil {
   520  				return err
   521  			}
   522  			if g.Match(info.Name()) {
   523  				files = append(files, path)
   524  			}
   525  			return nil
   526  		})
   527  		if err != nil {
   528  			vm.Err(err)
   529  			return nil
   530  		}
   531  		return GoStrSliceToTable(vm.L, files)
   532  	}))
   533  	vm.L.SetField(pkg, "dir", luar.New(vm.L, func(path string) string {
   534  		return filepath.Dir(path)
   535  	}))
   536  	vm.L.SetField(pkg, "base", luar.New(vm.L, func(path string) string {
   537  		return filepath.Base(path)
   538  	}))
   539  	vm.L.SetField(pkg, "abs", luar.New(vm.L, func(path string) string {
   540  		p, err := filepath.Abs(path)
   541  		if err != nil {
   542  			vm.Err(err)
   543  		}
   544  		return p
   545  	}))
   546  	vm.L.SetField(pkg, "prefix", luar.New(vm.L, func(in []string, prefix string) *lua.LTable {
   547  		s := make([]string, 0, len(in))
   548  		for _, v := range in {
   549  			s = append(s, prefix+v)
   550  		}
   551  		return GoStrSliceToTable(vm.L, s)
   552  	}))
   553  	vm.L.SetField(pkg, "suffix", luar.New(vm.L, func(in []string, suffix string) *lua.LTable {
   554  		s := make([]string, 0, len(in))
   555  		for _, v := range in {
   556  			s = append(s, v+suffix)
   557  		}
   558  		return GoStrSliceToTable(vm.L, s)
   559  	}))
   560  	vm.L.SetField(pkg, "extrepl", luar.New(vm.L, func(in []string, ext, repl string) *lua.LTable {
   561  		patstr := fmt.Sprintf("%s$", regexp.QuoteMeta(ext))
   562  		s, err := replace(in, patstr, repl)
   563  		if err != nil {
   564  			vm.Err(err)
   565  		}
   566  		return GoStrSliceToTable(vm.L, s)
   567  	}))
   568  	vm.L.SetField(pkg, "repl", luar.New(vm.L, func(in []string, patstr, repl string) *lua.LTable {
   569  		s, err := replace(in, patstr, repl)
   570  		if err != nil {
   571  			vm.Err(err)
   572  		}
   573  		return GoStrSliceToTable(vm.L, s)
   574  	}))
   575  	vm.L.SetField(pkg, "filterout", luar.New(vm.L, func(in []string, exclude []string) *lua.LTable {
   576  		removed := make([]string, 0, len(in))
   577  		exmap := make(map[string]bool)
   578  		for _, e := range exclude {
   579  			exmap[e] = true
   580  		}
   581  		for _, s := range in {
   582  			if !exmap[s] {
   583  				removed = append(removed, s)
   584  			}
   585  		}
   586  		return GoStrSliceToTable(vm.L, removed)
   587  	}))
   588  	vm.L.SetField(pkg, "shell", luar.New(vm.L, func(shcmd string) string {
   589  		cmd := exec.Command(vm.shell, "-c", shcmd)
   590  		b, err := cmd.Output()
   591  		if err != nil {
   592  			vm.Err(err)
   593  		}
   594  		return string(bytes.TrimSpace(b))
   595  	}))
   596  	vm.L.SetField(pkg, "addpath", luar.New(vm.L, func(path string) {
   597  		if !filepath.IsAbs(path) {
   598  			wd, err := os.Getwd()
   599  			if err != nil {
   600  				vm.Err(err)
   601  			}
   602  			path = filepath.Join(wd, path)
   603  		}
   604  		lv := vm.L.GetField(vm.L.GetField(vm.L.Get(lua.EnvironIndex), "package"), "path")
   605  		if lv, ok := lv.(lua.LString); ok {
   606  			vm.L.SetField(vm.L.GetField(vm.L.Get(lua.EnvironIndex), "package"), "path", lua.LString(filepath.Join(path, "?.knit;"))+lua.LString(filepath.Join(path, "?.lua;"))+lv)
   607  		} else {
   608  			vm.ErrStr("package.path must be a string")
   609  		}
   610  	}))
   611  	vm.L.SetField(pkg, "knit", luar.New(vm.L, func(flags string) string {
   612  		path, err := os.Executable()
   613  		if err != nil {
   614  			vm.Err(err)
   615  		}
   616  		cmd := exec.Command(vm.shell, "-c", shellquote.Join(path)+" "+flags)
   617  		b, err := cmd.Output()
   618  		if err != nil {
   619  			vm.Err(err)
   620  		}
   621  		return string(bytes.TrimSpace(b))
   622  	}))
   623  	return pkg
   624  }
   625  
   626  func replace(in []string, patstr, repl string) ([]string, error) {
   627  	rgx, err := regexp.Compile(patstr)
   628  	if err != nil {
   629  		return nil, err
   630  	}
   631  	outs := make([]string, len(in))
   632  	for i, v := range in {
   633  		outs[i] = rgx.ReplaceAllString(v, repl)
   634  	}
   635  	return outs, nil
   636  }
   637  
   638  func addLocals(L *lua.LState, locals *lua.LTable) *lua.LTable {
   639  	dbg, ok := L.GetStack(1)
   640  	if ok {
   641  		for j := 0; ; j++ {
   642  			name, val := L.GetLocal(dbg, j)
   643  			if name == "" {
   644  				break
   645  			}
   646  			locals.RawSetString(name, val)
   647  		}
   648  	}
   649  	return locals
   650  }
   651  
   652  func getVars(L *lua.LState) *lua.LTable {
   653  	globals := L.GetGlobal("_G").(*lua.LTable)
   654  	return addLocals(L, globals)
   655  }
   656  
   657  func (vm *LuaVM) getVar(L *lua.LState, v string) lua.LValue {
   658  	dbg, ok := L.GetStack(1)
   659  	vars := make(map[string]lua.LValue)
   660  	if ok {
   661  		for j := 0; ; j++ {
   662  			name, val := L.GetLocal(dbg, j)
   663  			if name == "" {
   664  				break
   665  			}
   666  			vars[name] = val
   667  		}
   668  		if lv, ok := vars[v]; ok {
   669  			return lv
   670  		}
   671  	}
   672  	globals := L.GetGlobal("_G").(*lua.LTable)
   673  	return globals.RawGet(lua.LString(v))
   674  }
   675  
   676  func (vm *LuaVM) SetVar(name string, val interface{}) {
   677  	if slc, ok := val.([]string); ok {
   678  		vm.L.SetGlobal(name, GoStrSliceToTable(vm.L, slc))
   679  	} else {
   680  		vm.L.SetGlobal(name, luar.New(vm.L, val))
   681  	}
   682  }
   683  
   684  // LToString converts a Lua value to a string.
   685  func LToString(v lua.LValue) string {
   686  	switch v := v.(type) {
   687  	case *lua.LUserData:
   688  		switch u := v.Value.(type) {
   689  		case []string:
   690  			return strings.Join(u, " ")
   691  		default:
   692  			return fmt.Sprintf("%v", u)
   693  		}
   694  	case *lua.LTable:
   695  		if v.Len() == 0 {
   696  			return LTableToString(v)
   697  		}
   698  		return LArrayToString(v)
   699  	default:
   700  		return fmt.Sprintf("%v", v)
   701  	}
   702  }
   703  
   704  func GoStrSliceToTable(L *lua.LState, arr []string) *lua.LTable {
   705  	tbl := L.NewTable()
   706  	mt := L.NewTable()
   707  	L.SetField(mt, "__tostring", luar.New(L, func(s []string) string {
   708  		return strings.Join(s, " ")
   709  	}))
   710  	L.SetField(mt, "__add", luar.New(L, func(s1, s2 []string) *lua.LTable {
   711  		tbl := L.NewTable()
   712  		for _, val := range s1 {
   713  			tbl.Append(lua.LString(val))
   714  		}
   715  		for _, val := range s2 {
   716  			tbl.Append(lua.LString(val))
   717  		}
   718  		return tbl
   719  	}))
   720  	L.SetMetatable(tbl, mt)
   721  	for _, val := range arr {
   722  		tbl.Append(lua.LString(val))
   723  	}
   724  	return tbl
   725  }
   726  
   727  // LTableToString converts a Lua table to a string.
   728  func LTableToString(v *lua.LTable) string {
   729  	buf := &bytes.Buffer{}
   730  	v.ForEach(func(k, v lua.LValue) {
   731  		buf.WriteString(fmt.Sprintf("%s=%s", LToString(k), LToString(v)))
   732  		buf.WriteByte(' ')
   733  	})
   734  	return buf.String()
   735  }
   736  
   737  // LArrayToString converts a Lua array (table with length) to a string.
   738  func LArrayToString(v *lua.LTable) string {
   739  	size := v.Len()
   740  	buf := &bytes.Buffer{}
   741  	i := 0
   742  	v.ForEach(func(_, v lua.LValue) {
   743  		buf.WriteString(LToString(v))
   744  		if i < size-1 {
   745  			buf.WriteByte(' ')
   746  		}
   747  		i++
   748  	})
   749  	return buf.String()
   750  }
   751  
   752  // MakeTable creates a global Lua table called 'name', with the key-value pairs
   753  // from 'vals'.
   754  func (vm *LuaVM) MakeTable(name string, vals []assign) {
   755  	t := vm.L.NewTable()
   756  	vm.L.SetGlobal(name, t)
   757  	for _, a := range vals {
   758  		vm.L.SetField(t, a.name, luar.New(vm.L, a.value))
   759  	}
   760  }