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 }