Supply environment as extra argument when loading functions.

This commit is contained in:
James Roseborough
2013-01-21 21:37:44 +00:00
parent 17db89fd3b
commit 706d9ba47e
6 changed files with 259 additions and 87 deletions

View File

@@ -57,40 +57,43 @@ import org.luaj.vm2.Varargs;
* <p>
* For example, the following code will implement a library called "hyperbolic"
* with two functions, "sinh", and "cosh":
* <pre> {@code
<pre> {@code
* import org.luaj.vm2.LuaValue;
* public class hyperbolic extends org.luaj.vm2.lib.OneArgFunction {
* public hyperbolic() {}
* public LuaValue call(LuaValue arg) {
* switch ( opcode ) {
* case 0: {
* LuaValue t = tableOf();
* this.bind(t, hyperbolic.class, new String[] { "sinh", "cosh" }, 1 );
* env.set("hyperbolic", t);
* return t;
* }
* case 1: return valueOf(Math.sinh(arg.todouble()));
* case 2: return valueOf(Math.cosh(arg.todouble()));
* default: return error("bad opcode: "+opcode);
* }
* }
* }
* }</pre>
* The default constructor is both to instantiate the library
* in response to {@code require 'hyperbolic'} statement,
* provided it is on Javas class path,
* and to instantiate copies of the {@code hyperbolic}
* class when initializing library instances. .
* The instance returned by the default constructor will be invoked
* as part of library loading.
* In response, it creates two more instances, one for each library function,
* in the body of the {@code switch} statement {@code case 0}
* via the {@link #bind(LuaValue, Class, String[], int)} utility method.
* It also registers the table in the globals via the {@link #env}
* local variable, which should be the global environment unless
* it has been changed.
* {@code case 1} and {@code case 2} will be called when {@code hyperbolic.sinh}
* {@code hyperbolic.sinh} and {@code hyperbolic.cosh} are invoked.
* import org.luaj.vm2.lib.*;
*
* public class hyperbolic extends TwoArgFunction {
*
* public hyperbolic() {}
*
* public LuaValue call(LuaValue modname, LuaValue env) {
* LuaValue library = tableOf();
* library.set( "sinh", new sinh() );
* library.set( "cosh", new cosh() );
* env.set( "hyperbolic", library );
* return library;
* }
*
* static class sinh extends OneArgFunction {
* public LuaValue call(LuaValue x) {
* return LuaValue.valueOf(Math.sinh(x.checkdouble()));
* }
* }
*
* static class cosh extends OneArgFunction {
* public LuaValue call(LuaValue x) {
* return LuaValue.valueOf(Math.cosh(x.checkdouble()));
* }
* }
*}
*}</pre>
* The default constructor is used to instantiate the library
* in response to {@code require 'hyperbolic'} statement,
* provided it is on Java&quot;s class path.
* This instance is then invoked with 2 arguments: the name supplied to require(),
* and the environment for this function. The library may ignore these, or use
* them to leave side effects in the global environment, for example.
* In the previous example, two functions are created, 'sinh', and 'cosh', and placed
* into a global table called 'hyperbolic' using the supplied 'env' argument.
* <p>
* To test it, a script such as this can be used:
* <pre> {@code
@@ -108,14 +111,14 @@ import org.luaj.vm2.Varargs;
* <pre> {@code
* t table: 3dbbd23f
* hyperbolic table: 3dbbd23f
* k,v cosh cosh
* k,v sinh sinh
* k,v cosh function: 3dbbd128
* k,v sinh function: 3dbbd242
* sinh(.5) 0.5210953
* cosh(.5) 1.127626
* }</pre>
* <p>
* See the source code in any of the library functions
* such as {@link BaseLib} or {@link TableLib} for specific examples.
* such as {@link BaseLib} or {@link TableLib} for other examples.
*/
abstract public class LibFunction extends LuaFunction {

View File

@@ -148,28 +148,29 @@ public class PackageLib extends OneArgFunction {
/**
* 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
* 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.
*
* To find a loader, require is guided by the package.searchers sequence.
* By changing this sequence, we can change how require looks for a module.
* The following explanation is based on the default configuration for package.searchers.
*
* 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).
* (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 Java loader using
* the classpath, using the public default constructor, and casting the instance to LuaFunction.
*
* 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.
* Once a loader is found, require calls the loader with two arguments: modname and an extra value
* dependent on how it got the loader. If the loader came from a file, this extra value is the file name.
* If the loader is a Java instance of LuaFunction, this extra value is the environment.
* If the loader returns any non-nil value, require assigns the returned value to package.loaded[modname].
* If the loader does not return a non-nil 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 raises an error.
*/
public class require extends OneArgFunction {
public LuaValue call( LuaValue arg ) {
@@ -184,24 +185,24 @@ public class PackageLib extends OneArgFunction {
/* else must load it; iterate over available loaders */
LuaTable tbl = PackageLib.this.searchers.checktable();
StringBuffer sb = new StringBuffer();
LuaValue chunk = null;
Varargs loader = null;
for ( int i=1; true; i++ ) {
LuaValue loader = tbl.get(i);
if ( loader.isnil() ) {
LuaValue searcher = tbl.get(i);
if ( searcher.isnil() ) {
error( "module '"+name+"' not found: "+name+sb );
}
/* call loader with module name as argument */
chunk = loader.call(name);
if ( chunk.isfunction() )
loader = searcher.invoke(name);
if ( loader.isfunction(1) )
break;
if ( chunk.isstring() )
sb.append( chunk.tojstring() );
if ( loader.isstring(1) )
sb.append( loader.tojstring(1) );
}
// load the module using the loader
loaded.set(name, _SENTINEL);
result = chunk.call(name);
result = loader.arg1().call(name, loader.arg(2));
if ( ! result.isnil() )
loaded.set( name, result );
else if ( (result = PackageLib.this.loaded.get(name)) == _SENTINEL )
@@ -309,7 +310,7 @@ public class PackageLib extends OneArgFunction {
v = (LuaValue) c.newInstance();
if (v.isfunction())
((LuaFunction)v).initupvalue1(globals);
return v;
return varargsOf(v, globals);
} catch ( ClassNotFoundException cnfe ) {
return valueOf("\n\tno class '"+classname+"'" );
} catch ( Exception e ) {