commonMain.org.luaj.vm2.lib.PackageLib.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of luak-jvm Show documentation
Show all versions of luak-jvm Show documentation
Multiplatform Kotlin LuaJ port (LUA interpreter)
/*******************************************************************************
* Copyright (c) 2010-2011 Luaj.org. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.luaj.vm2.lib
import org.luaj.vm2.*
import org.luaj.vm2.internal.*
import org.luaj.vm2.io.*
/**
* Subclass of [LibFunction] which implements the lua standard package and module
* library functions.
*
* Lua Environment Variables
* The following variables are available to lua scrips when this library has been loaded:
*
* * `"package.loaded"` Lua table of loaded modules.
* * `"package.path"` Search path for lua scripts.
* * `"package.preload"` Lua table of uninitialized preload functions.
* * `"package.searchers"` Lua table of functions that search for object to load.
*
*
* Java Environment Variables
* These Java environment variables affect the library behavior:
*
* * `"luaj.package.path"` Initial value for `"package.path"`. Default value is `"?.lua"`
*
*
* Loading
* Typically, this library is included as part of a call to either
* [org.luaj.vm2.lib.jse.JsePlatform.standardGlobals] or [org.luaj.vm2.lib.jme.JmePlatform.standardGlobals]
* `Globals globals = JsePlatform.standardGlobals();
* System.out.println( globals.get("require").call"foo") );
` *
*
*
* To instantiate and use it directly,
* link it into your globals table via [LuaValue.load] using code such as:
* `Globals globals = new Globals();
* globals.load(new JseBaseLib());
* globals.load(new PackageLib());
* System.out.println( globals.get("require").call("foo") );
` *
* Limitations
* This library has been implemented to match as closely as possible the behavior in the corresponding library in C.
* However, the default filesystem search semantics are different and delegated to the bas library
* as outlined in the [BaseLib] and [org.luaj.vm2.lib.jse.JseBaseLib] documentation.
*
*
* @see LibFunction
*
* @see BaseLib
*
* @see org.luaj.vm2.lib.jse.JseBaseLib
*
* @see org.luaj.vm2.lib.jse.JsePlatform
*
* @see org.luaj.vm2.lib.jme.JmePlatform
*
* @see [Lua 5.2 Package Lib Reference](http://www.lua.org/manual/5.2/manual.html.6.3)
*/
class PackageLib : TwoArgFunction() {
/** The globals that were used to load this library. */
@kotlin.jvm.JvmField
internal var globals: Globals? = null
/** The table for this package. */
@kotlin.jvm.JvmField
internal var package_: LuaTable? = null
/** Loader that loads from `preload` table if found there */
@kotlin.jvm.JvmField
var preload_searcher: Preload_searcher? = null
/** Loader that loads as a lua script using the lua path currently in [path] */
@kotlin.jvm.JvmField
var lua_searcher: Lua_searcher? = null
/** Loader that loads as a Java class. Class must have public constructor and be a LuaValue. */
@kotlin.jvm.JvmField
var java_searcher: Java_searcher? = null
/** Perform one-time initialization on the library by adding package functions
* to the supplied environment, and returning it as the return value.
* It also creates the package.preload and package.loaded tables for use by
* other libraries.
* @param modname the module name supplied if this is loaded via 'require'.
* @param env the environment to load into, typically a Globals instance.
*/
override fun call(modname: LuaValue, env: LuaValue): LuaValue {
globals = env.checkglobals()
globals!!["require"] = require()
package_ = LuaTable()
val package_ = package_!!
package_[_LOADED] = LuaTable()
package_[_PRELOAD] = LuaTable()
package_[_PATH] = valueOf(DEFAULT_LUA_PATH!!)
package_[_LOADLIB] = loadlib()
package_[_SEARCHPATH] = searchpath()
val searchers = LuaTable()
searchers.set(1, run { preload_searcher = Preload_searcher(); preload_searcher!! })
searchers.set(2, run { lua_searcher = Lua_searcher(); lua_searcher!! })
searchers.set(3, run { java_searcher = Java_searcher(); java_searcher!! })
package_[_SEARCHERS] = searchers
package_[_LOADED]["package"] = package_
env["package"] = package_
globals!!.package_ = this
return env
}
/** Allow packages to mark themselves as loaded */
fun setIsLoaded(name: String, value: LuaTable) {
package_!![_LOADED][name] = value
}
/** Set the lua path used by this library instance to a new value.
* Merely sets the value of [path] to be used in subsequent searches. */
fun setLuaPath(newLuaPath: String) {
package_!![_PATH] = valueOf(newLuaPath)
}
override fun tojstring(): String {
return "package"
}
// ======================== Package loading =============================
/**
* 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.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 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 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.
*/
inner class require : OneArgFunction() {
override fun call(arg: LuaValue): LuaValue {
val name = arg.checkstring()
val loaded = package_!![_LOADED]
var result = loaded[name!!]
if (result.toboolean()) {
if (result === _SENTINEL)
LuaValue.error("loop or previous error loading module '$name'")
return result
}
/* else must load it; iterate over available loaders */
val tbl = package_!![_SEARCHERS].checktable()
val sb = StringBuilder()
var loader: Varargs? = null
var i = 1
while (true) {
val searcher = tbl!![i]
if (searcher.isnil()) {
LuaValue.error("module '$name' not found: $name$sb")
}
/* call loader with module name as argument */
loader = searcher.invoke(name)
if (loader.isfunction(1))
break
if (loader.isstring(1))
sb.append(loader.tojstring(1))
i++
}
// load the module using the loader
loaded[name] = _SENTINEL
result = loader!!.arg1().call(name, loader.arg(2))
if (!result.isnil())
loaded[name] = result
else if ((run { result = loaded[name]; result }) === _SENTINEL)
loaded.set(name, run { result = LuaValue.TRUE; result!! })
return result
}
}
class loadlib : VarArgFunction() {
override fun invoke(args: Varargs): Varargs {
args.checkstring(1)
return varargsOf(
NIL,
valueOf("dynamic libraries not enabled"),
valueOf("absent")
)
}
}
inner class Preload_searcher : VarArgFunction() {
override fun invoke(args: Varargs): Varargs {
val name = args.checkstring(1)
val `val` = package_!![_PRELOAD]!![name!!]
return if (`val`.isnil())
valueOf("\n\tno field package.preload['$name']")
else
`val`
}
}
inner class Lua_searcher : VarArgFunction() {
override fun invoke(args: Varargs): Varargs {
val name = args.checkstring(1)
val `is`: LuaBinInput? = null
// get package path
val path = package_!![_PATH]!!
if (!path.isstring())
return valueOf("package.path is not a string")
// get the searchpath function.
var v = package_!![_SEARCHPATH]!!.invoke(varargsOf(name!!, path))
// Did we get a result?
if (!v.isstring(1))
return v.arg(2).tostring()
val filename = v.arg1().strvalue()
// Try to load the file.
v = globals!!.loadfile(filename!!.tojstring())
return if (v.arg1().isfunction()) varargsOf(v.arg1(), filename) else varargsOf(
NIL,
valueOf("'" + filename + "': " + v.arg(2).tojstring())
)
// report error
}
}
inner class searchpath : VarArgFunction() {
override fun invoke(args: Varargs): Varargs {
var name = args.checkjstring(1)
val path = args.checkjstring(2)
val sep = args.optjstring(3, ".")
val rep = args.optjstring(4, FILE_SEP!!)
// check the path elements
var e = -1
val n = path!!.length
var sb: StringBuilder? = null
name = name!!.replace(sep!![0], rep!![0])
while (e < n) {
// find next template
val b = e + 1
e = path.indexOf(';', b)
if (e < 0)
e = path.length
val template = path.substring(b, e)
// create filename
val q = template.indexOf('?')
var filename = template
if (q >= 0) {
filename = template.substring(0, q) + name + template.substring(q + 1)
}
// try opening the file
val `is` = globals!!.finder!!.findResource(filename)
if (`is` != null) {
try {
`is`.close()
} catch (ioe: IOException) {
}
return valueOf(filename)
}
// report error
if (sb == null)
sb = StringBuilder()
sb.append("\n\t" + filename)
}
return varargsOf(NIL, valueOf(sb!!.toString()))
}
}
inner class Java_searcher : VarArgFunction() {
override fun invoke(args: Varargs): Varargs {
val name = args.checkjstring(1)
val classname = toClassname(name!!)
try {
val v = JSystem.InstantiateClassByName(classname) as? LuaValue ?: return valueOf("\n\tno class '$classname'")
if (v.isfunction()) (v as LuaFunction).initupvalue1(globals!!)
return varargsOf(v, globals!!)
} catch (e: Exception) {
return valueOf("\n\tjava load failed on '$classname', $e")
}
}
}
companion object {
/** The default value to use for package.path. This can be set with the system property
* `"luaj.package.path"`, and is `"?.lua"` by default. */
var DEFAULT_LUA_PATH: String? = null
init {
try {
DEFAULT_LUA_PATH = JSystem.getProperty("luaj.package.path")
} catch (e: Exception) {
println(e.toString())
}
if (DEFAULT_LUA_PATH == null)
DEFAULT_LUA_PATH = "?.lua"
}
private val _LOADED = valueOf("loaded")
private val _LOADLIB = valueOf("loadlib")
private val _PRELOAD = valueOf("preload")
private val _PATH = valueOf("path")
private val _SEARCHPATH = valueOf("searchpath")
private val _SEARCHERS = valueOf("searchers")
private val _SENTINEL = valueOf("\u0001")
private val FILE_SEP = JSystem.getProperty("file.separator")
/** Convert lua filename to valid class name */
fun toClassname(filename: String): String {
val n = filename.length
var j = n
if (filename.endsWith(".lua"))
j -= 4
for (k in 0 until j) {
var c = filename[k]
if (!isClassnamePart(c) || c == '/' || c == '\\') {
val sb = StringBuilder(j)
for (i in 0 until j) {
c = filename[i]
sb.append(
if (isClassnamePart(c))
c
else if (c == '/' || c == '\\') '.' else '_'
)
}
return sb.toString()
}
}
return if (n == j) filename else filename.substring(0, j)
}
private fun isClassnamePart(c: Char): Boolean {
if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9')
return true
when (c) {
'.', '$', '_' -> return true
else -> return false
}
}
}
}