All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nl.weeaboo.lua2.stdlib.PackageLib Maven / Gradle / Ivy

package nl.weeaboo.lua2.stdlib;

import static nl.weeaboo.lua2.vm.LuaBoolean.TRUE;
import static nl.weeaboo.lua2.vm.LuaConstants.EMPTYSTRING;
import static nl.weeaboo.lua2.vm.LuaConstants.META_INDEX;
import static nl.weeaboo.lua2.vm.LuaConstants.NONE;
import static nl.weeaboo.lua2.vm.LuaNil.NIL;
import static nl.weeaboo.lua2.vm.LuaValue.listOf;
import static nl.weeaboo.lua2.vm.LuaValue.tableOf;
import static nl.weeaboo.lua2.vm.LuaValue.valueOf;
import static nl.weeaboo.lua2.vm.LuaValue.varargsOf;

import javax.annotation.Nullable;

import nl.weeaboo.lua2.LuaException;
import nl.weeaboo.lua2.compiler.ScriptLoader;
import nl.weeaboo.lua2.io.LuaSerializable;
import nl.weeaboo.lua2.lib.LuaBoundFunction;
import nl.weeaboo.lua2.lib.VarArgFunction;
import nl.weeaboo.lua2.vm.LuaFunction;
import nl.weeaboo.lua2.vm.LuaString;
import nl.weeaboo.lua2.vm.LuaTable;
import nl.weeaboo.lua2.vm.LuaThread;
import nl.weeaboo.lua2.vm.LuaValue;
import nl.weeaboo.lua2.vm.Varargs;

/**
 * Package library
 */
@LuaSerializable
public final class PackageLib extends LuaModule {

    private static final long serialVersionUID = 1L;

    private static final LuaString S_M = valueOf("_M");
    private static final LuaString S_NAME = valueOf("_NAME");
    private static final LuaString S_PACKAGE = valueOf("_PACKAGE");
    private static final LuaString S_DOT = valueOf(".");
    private static final LuaString S_LOADERS = valueOf("loaders");
    private static final LuaString S_LOADED = valueOf("loaded");
    private static final LuaString S_PRELOAD = valueOf("preload");
    private static final LuaString S_PATH = valueOf("path");
    private static final LuaString S_CPATH = valueOf("cpath");
    private static final LuaString S_SENTINEL = valueOf("\u0001");

    public LuaTable loadedTable = new LuaTable();
    public LuaTable packageTable;

    PackageLib() {
        super("package");
    }

    @Override
    protected void registerAdditional(LuaTable globals, LuaTable libTable) throws LuaException {
        super.registerAdditional(globals, libTable);

        packageTable = libTable;
        packageTable.rawset(S_LOADED, loadedTable);
        packageTable.rawset(S_PRELOAD, new LuaTable());
        packageTable.rawset(S_PATH, valueOf("?.lua"));
        packageTable.rawset(S_CPATH, valueOf(""));

        packageTable.rawset(S_LOADERS, listOf(new LuaValue[] {
                new PreloadLoader(packageTable),
                new LuaLoader(packageTable)
        }));

        loadedTable.rawset("package", packageTable);
    }

    /**
     * This method is not implemented.
     */
    @LuaBoundFunction
    public Varargs loadlib(Varargs args) {
        args.checkstring(1);
        return varargsOf(NIL, valueOf("dynamic libraries not enabled"), valueOf("absent"));
    }

    /**
     * package.seeall (module)
     * 

* Sets a metatable for module with its __index field referring to the global environment, so that this * module inherits values from the global environment. To be used as an option to function module. */ @LuaBoundFunction public Varargs seeall(Varargs args) { LuaTable t = args.checktable(1); LuaValue m = t.getmetatable(); if (m.isnil()) { m = tableOf(); t.setmetatable(m); } LuaThread running = LuaThread.getRunning(); m.set(META_INDEX, running.getfenv()); return NONE; } /** * require (modname) * * Loads the given module. The function starts by looking into the package.loaded table to determine * whether modname is already loaded. If it is, then require returns the value stored at * package.loaded[modname]. Otherwise, it tries to find a loader for the module. * * To find a loader, require is guided by the package.loaders array. By changing this array, we can change * how require looks for a module. The following explanation is based on the default configuration for * package.loaders. * * First require queries package.preload[modname]. If it has a value, this value (which should be a * function) is the loader. Otherwise require searches for a Lua loader using the path stored in * package.path. If that also fails, it searches for a C loader using the path stored in package.cpath. If * that also fails, it tries an all-in-one loader (see package.loaders). * * Once a loader is found, require calls the loader with a single argument, modname. If the loader returns * any value, require assigns the returned value to package.loaded[modname]. If the loader returns no * value and has not assigned any value to package.loaded[modname], then require assigns true to this * entry. In any case, require returns the final value of package.loaded[modname]. * * If there is any error loading or running the module, or if it cannot find any loader for the module, * then require signals an error. */ @LuaBoundFunction(global = true) public Varargs require(Varargs args) { LuaString name = args.checkstring(1); LuaValue loaded = loadedTable.get(name); if (loaded.toboolean()) { if (loaded == S_SENTINEL) { throw new LuaException("loop or previous error loading module '" + name + "'"); } return loaded; } /* else must load it; iterate over available loaders */ LuaTable tbl = packageTable.get(S_LOADERS).checktable(); StringBuilder sb = new StringBuilder(); LuaValue chunk; int i = 1; do { LuaValue loader = tbl.get(i); if (loader.isnil()) { throw new LuaException("module '" + name + "' not found: " + name + sb); } /* call loader with module name as argument */ chunk = loader.call(name); if (chunk.isfunction()) { break; } if (chunk.isstring()) { sb.append(chunk.tojstring()); } i++; } while (true); // load the module using the loader loadedTable.set(name, S_SENTINEL); LuaValue result = chunk.call(name); if (!result.isnil()) { loadedTable.set(name, result); } else if ((result = loadedTable.get(name)) == S_SENTINEL) { loadedTable.set(name, result = TRUE); } return result; } /** * module (name [, ...]) * * Creates a module. If there is a table in package.loaded[name], this table is the module. Otherwise, if * there is a global table t with the given name, this table is the module. Otherwise creates a new table * t and sets it as the value of the global name and the value of package.loaded[name]. This function also * initializes t._NAME with the given name, t._M with the module (t itself), and t._PACKAGE with the * package name (the full module name minus last component; see below). Finally, module sets t as the new * environment of the current function and the new value of package.loaded[name], so that require returns * t. * * If name is a compound name (that is, one with components separated by dots), module creates (or reuses, * if they already exist) tables for each component. For instance, if name is a.b.c, then module stores * the module table in field c of field b of global a. * * This function may receive optional options after the module name, where each option is a function to be * applied over the module. */ @LuaBoundFunction(global = true) public Varargs module(Varargs args) { LuaThread running = LuaThread.getRunning(); LuaString modname = args.checkstring(1); final int n = args.narg(); LuaValue value = loadedTable.get(modname); LuaValue module; if (!value.istable()) { /* not found? */ /* try global variable (and create one if it does not exist) */ LuaValue globals = running.getfenv(); module = findtable(globals, modname); if (module == null) { throw new LuaException("name conflict for module '" + modname + "'"); } loadedTable.set(modname, module); } else { module = value; } /* check whether table already has a _NAME field */ LuaValue name = module.get(S_NAME); if (name.isnil()) { module.set(S_M, module); int e = modname.lastIndexOf(S_DOT); module.set(S_NAME, modname); module.set(S_PACKAGE, (e < 0 ? EMPTYSTRING : modname.substring(0, e + 1))); } // set the environment of the current function LuaFunction f = running.getCallstackFunction(1); if (f == null) { throw new LuaException("no calling function"); } if (!f.isclosure()) { throw new LuaException("'module' not called from a Lua function"); } f.setfenv(module); // apply the functions for (int i = 2; i <= n; i++) { args.arg(i).call(module); } // returns no results return NONE; } /** * @param table the table at which to start the search * @param fname the name to look up or create, such as "abc.def.ghi" * @return the table for that name, possible a new one, or null if a non-table has that name already. */ private static final @Nullable LuaValue findtable(LuaValue table, LuaString fname) { int b; int e = -1; do { e = fname.indexOf(S_DOT, b = e + 1); if (e < 0) { e = fname.length(); } LuaString key = fname.substring(b, e); LuaValue val = table.rawget(key); if (val.isnil()) { /* no such field? */ LuaTable field = new LuaTable(); /* new table for field */ table.set(key, field); table = field; } else if (!val.istable()) { /* field has a non-table value? */ return null; } else { table = val; } } while (e < fname.length()); return table; } @LuaSerializable private static final class PreloadLoader extends VarArgFunction { private static final long serialVersionUID = 1L; private final LuaTable packageTable; public PreloadLoader(LuaTable packageTable) { this.packageTable = packageTable; } @Override public Varargs invoke(Varargs args) { LuaString name = args.checkstring(1); LuaValue preloadTable = packageTable.get(S_PRELOAD); if (!preloadTable.istable()) { throw new LuaException("'package.preload' must be a table"); } LuaValue entry = preloadTable.get(name); if (entry.isnil()) { return valueOf("no field package.preload['" + name + "']"); } else { return entry; } } } @LuaSerializable private static final class LuaLoader extends VarArgFunction { private static final long serialVersionUID = 1L; private final LuaTable packageTable; public LuaLoader(LuaTable packageTable) { this.packageTable = packageTable; } @Override public Varargs invoke(Varargs args) { String name = args.checkjstring(1); // get package path LuaValue pp = packageTable.get(S_PATH); if (!pp.isstring()) { return valueOf("package.path is not a string"); } String path = pp.tojstring(); // check the path elements int e = -1; int n = path.length(); StringBuilder sb = new StringBuilder(); name = name.replace('.', '/'); while (e < n) { // find next template int b = e + 1; e = path.indexOf(';', b); if (e < 0) { e = path.length(); } String template = path.substring(b, e); // create filename by replacing wildcards String filename = template.replace("?", name); // try loading the file Varargs v = ScriptLoader.loadFile(filename); if (v.arg1().isfunction()) { return v.arg1(); } // report error sb.append("\n\t'" + filename + "': " + v.arg(2)); } return valueOf(sb.toString()); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy