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  }