github.com/arnodel/golua@v0.0.0-20230215163904-e0b5347eaaa1/lib/packagelib/packagelib.go (about) 1 package packagelib 2 3 import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "strings" 9 10 rt "github.com/arnodel/golua/runtime" 11 ) 12 13 var ( 14 pkgKey = rt.StringValue("package") 15 preloadKey = rt.StringValue("preload") 16 pathKey = rt.StringValue("path") 17 configKey = rt.StringValue("config") 18 loadedKey = rt.StringValue("loaded") 19 searchersKey = rt.StringValue("searchers") 20 ) 21 22 const defaultPath = `./?.lua;./?/init.lua` 23 24 // Loader is used to register libraries 25 type Loader struct { 26 // Function that creates the package and returns it 27 Load func(r *rt.Runtime) (rt.Value, func()) 28 29 // Function that cleans up at the end (optional) 30 Cleanup func(r *rt.Runtime) 31 32 // Name of the package 33 Name string 34 } 35 36 // Run will create the package, associate it with its name in the global env and 37 // cache it. 38 func (l Loader) Run(r *rt.Runtime) func() { 39 pkg, cleanup := l.Load(r) 40 if l.Name == "" || pkg.IsNil() { 41 return cleanup 42 } 43 r.SetEnv(r.GlobalEnv(), l.Name, pkg) 44 err := savePackage(r, l.Name, pkg) 45 if err != nil { 46 panic(fmt.Sprintf("Unable to load %s: %s", l.Name, err)) 47 } 48 return cleanup 49 } 50 51 // LibLoader allows loading the package lib. 52 var LibLoader = Loader{ 53 Load: load, 54 Name: "package", 55 } 56 57 func load(r *rt.Runtime) (rt.Value, func()) { 58 env := r.GlobalEnv() 59 pkg := rt.NewTable() 60 pkgVal := rt.TableValue(pkg) 61 r.SetRegistry(pkgKey, pkgVal) 62 r.SetTable(pkg, loadedKey, rt.TableValue(rt.NewTable())) 63 r.SetTable(pkg, preloadKey, rt.TableValue(rt.NewTable())) 64 searchers := rt.NewTable() 65 r.SetTable(searchers, rt.IntValue(1), rt.FunctionValue(searchPreloadGoFunc)) 66 r.SetTable(searchers, rt.IntValue(2), rt.FunctionValue(searchLuaGoFunc)) 67 r.SetTable(pkg, searchersKey, rt.TableValue(searchers)) 68 r.SetTable(pkg, pathKey, rt.StringValue(defaultPath)) 69 r.SetTable(pkg, configKey, rt.StringValue(defaultConfig.String())) 70 71 r.SetEnvGoFunc(pkg, "searchpath", searchpath, 4, false) 72 r.SetEnvGoFunc(env, "require", require, 1, false) 73 74 return pkgVal, nil 75 } 76 77 type config struct { 78 dirSep string 79 pathSep string 80 placeholder string 81 windowsExecPlaceholder string 82 suffixSep string 83 } 84 85 func (c *config) String() string { 86 return fmt.Sprintf("%s\n%s\n%s\n%s\n%s", 87 c.dirSep, c.pathSep, c.placeholder, 88 c.windowsExecPlaceholder, c.suffixSep) 89 } 90 91 var defaultConfig = config{"/", ";", "?", "!", "-"} 92 93 func getConfig(pkg *rt.Table) *config { 94 conf := new(config) 95 *conf = defaultConfig 96 confStr, ok := pkg.Get(configKey).TryString() 97 if !ok { 98 return conf 99 } 100 lines := strings.Split(string(confStr), "\n") 101 if len(lines) >= 1 { 102 conf.dirSep = lines[0] 103 } 104 if len(lines) >= 2 { 105 conf.pathSep = lines[1] 106 } 107 if len(lines) >= 3 { 108 conf.placeholder = lines[2] 109 } 110 if len(lines) >= 4 { 111 conf.windowsExecPlaceholder = lines[3] 112 } 113 if len(lines) >= 5 { 114 conf.suffixSep = lines[4] 115 } 116 return conf 117 } 118 119 func require(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { 120 if err := c.Check1Arg(); err != nil { 121 return nil, err 122 } 123 name, err := c.StringArg(0) 124 if err != nil { 125 return nil, err 126 } 127 nameVal := c.Arg(0) 128 pkg := pkgTable(t.Runtime) 129 130 // First check is the module is already loaded 131 loaded, ok := pkg.Get(loadedKey).TryTable() 132 if !ok { 133 return nil, errors.New("package.loaded must be a table") 134 } 135 next := c.Next() 136 if mod := loaded.Get(nameVal); !mod.IsNil() { 137 t.Push1(next, mod) 138 return next, nil 139 } 140 141 // If not, then go through the searchers 142 searchers, ok := pkg.Get(searchersKey).TryTable() 143 if !ok { 144 return nil, errors.New("package.searchers must be a table") 145 } 146 147 for i := int64(1); ; i++ { 148 searcher := searchers.Get(rt.IntValue(i)) 149 if searcher.IsNil() { 150 err = fmt.Errorf("could not find package '%s'", name) 151 break 152 } 153 res := rt.NewTerminationWith(c, 2, false) 154 if err = rt.Call(t, searcher, []rt.Value{nameVal}, res); err != nil { 155 break 156 } 157 loader := res.Get(0) 158 // We got a loader, so call it 159 if _, ok := loader.TryCallable(); ok { 160 val := res.Get(1) 161 res = rt.NewTerminationWith(c, 2, false) 162 if err = rt.Call(t, loader, []rt.Value{nameVal, val}, res); err != nil { 163 break 164 } 165 mod := rt.BoolValue(true) 166 if r0 := res.Get(0); !r0.IsNil() { 167 mod = r0 168 } 169 t.SetTable(loaded, nameVal, mod) 170 t.Push1(next, mod) 171 return next, nil 172 } 173 } 174 return nil, err 175 } 176 177 func searchpath(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { 178 var ( 179 name, path string 180 sep = "." 181 conf = *getConfig(pkgTable(t.Runtime)) 182 rep = conf.dirSep 183 ) 184 185 err := c.CheckNArgs(2) 186 if err == nil { 187 name, err = c.StringArg(0) 188 } 189 if err == nil { 190 path, err = c.StringArg(1) 191 } 192 if err == nil && c.NArgs() >= 3 { 193 sep, err = c.StringArg(2) 194 } 195 if err == nil && c.NArgs() >= 4 { 196 rep, err = c.StringArg(3) 197 } 198 if err != nil { 199 return nil, err 200 } 201 conf.dirSep = string(rep) 202 found, templates := searchPath(string(name), string(path), string(sep), &conf) 203 next := c.Next() 204 if found != "" { 205 t.Push1(next, rt.StringValue(found)) 206 } else { 207 t.Push(next, rt.NilValue, rt.StringValue("tried: "+strings.Join(templates, "\n"))) 208 } 209 return next, nil 210 } 211 212 func searchPath(name, path, dot string, conf *config) (string, []string) { 213 namePath := strings.Replace(name, dot, conf.dirSep, -1) 214 templates := strings.Split(path, conf.pathSep) 215 for i, template := range templates { 216 searchpath := strings.Replace(template, conf.placeholder, namePath, -1) 217 f, err := os.Open(searchpath) 218 f.Close() 219 if err == nil { 220 return searchpath, nil 221 } 222 templates[i] = searchpath 223 } 224 return "", templates 225 } 226 227 func searchPreload(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { 228 if err := c.Check1Arg(); err != nil { 229 return nil, err 230 } 231 s, err := c.StringArg(0) 232 if err != nil { 233 return nil, err 234 } 235 loader := pkgTable(t.Runtime).Get(preloadKey).AsTable().Get(rt.StringValue(s)) 236 return c.PushingNext1(t.Runtime, loader), nil 237 } 238 239 func searchLua(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { 240 if err := c.Check1Arg(); err != nil { 241 return nil, err 242 } 243 s, err := c.StringArg(0) 244 if err != nil { 245 return nil, err 246 } 247 pkg := pkgTable(t.Runtime) 248 path, ok := pkg.Get(pathKey).TryString() 249 if !ok { 250 return nil, errors.New("package.path must be a string") 251 } 252 conf := getConfig(pkg) 253 found, templates := searchPath(string(s), string(path), ".", conf) 254 next := c.Next() 255 if found == "" { 256 t.Push1(next, rt.StringValue(strings.Join(templates, "\n"))) 257 } else { 258 t.Push1(next, rt.FunctionValue(loadLuaGoFunc)) 259 t.Push1(next, rt.StringValue(found)) 260 } 261 return next, nil 262 } 263 264 var ( 265 loadLuaGoFunc = rt.NewGoFunction(loadLua, "loadlua", 2, false) 266 searchLuaGoFunc = rt.NewGoFunction(searchLua, "searchlua", 1, false) 267 searchPreloadGoFunc = rt.NewGoFunction(searchPreload, "searchpreload", 1, false) 268 ) 269 270 func loadLua(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { 271 if err := c.CheckNArgs(2); err != nil { 272 return nil, err 273 } 274 // Arg 0 is the module name - dunno what to do with it. 275 filePath, err := c.StringArg(1) 276 if err != nil { 277 return nil, err 278 } 279 src, readErr := ioutil.ReadFile(string(filePath)) 280 if readErr != nil { 281 return nil, fmt.Errorf("error reading file: %s", readErr) 282 } 283 clos, compErr := t.LoadFromSourceOrCode(string(filePath), src, "bt", rt.TableValue(t.GlobalEnv()), true) 284 if compErr != nil { 285 return nil, fmt.Errorf("error compiling file: %s", compErr) 286 } 287 return rt.Continue(t, rt.FunctionValue(clos), c.Next()) 288 } 289 290 func pkgTable(r *rt.Runtime) *rt.Table { 291 return r.Registry(pkgKey).AsTable() 292 } 293 294 func savePackage(r *rt.Runtime, name string, val rt.Value) error { 295 pkg := pkgTable(r) 296 297 // First check is the module is already loaded 298 loaded, ok := pkg.Get(loadedKey).TryTable() 299 if !ok { 300 return errors.New("package.loaded must be a table") 301 } 302 r.SetTable(loaded, rt.StringValue(name), val) 303 return nil 304 }