mediawiki-extensions-Scribunto/includes/Engines/LuaCommon/LuaEngine.php

1040 lines
29 KiB
PHP
Raw Permalink Normal View History

<?php
namespace MediaWiki\Extension\Scribunto\Engines\LuaCommon;
use Exception;
use MediaWiki\Extension\Scribunto\Engines\LuaSandbox\LuaSandboxInterpreter;
use MediaWiki\Extension\Scribunto\Scribunto;
use MediaWiki\Extension\Scribunto\ScribuntoContent;
use MediaWiki\Extension\Scribunto\ScribuntoEngineBase;
use MediaWiki\Extension\Scribunto\ScribuntoException;
use MediaWiki\Html\Html;
use MediaWiki\Json\FormatJson;
use MediaWiki\MediaWikiServices;
use MediaWiki\Parser\Parser;
use MediaWiki\Parser\PPFrame;
use MediaWiki\Title\Title;
use RuntimeException;
use Wikimedia\ScopedCallback;
abstract class LuaEngine extends ScribuntoEngineBase {
/**
* Libraries to load. See also the 'ScribuntoExternalLibraries' hook.
* @var array<string,class-string<LibraryBase>> Maps module names to PHP classes or definition arrays
*/
protected static $libraryClasses = [
'mw.site' => SiteLibrary::class,
'mw.uri' => UriLibrary::class,
'mw.ustring' => UstringLibrary::class,
'mw.language' => LanguageLibrary::class,
'mw.message' => MessageLibrary::class,
'mw.title' => TitleLibrary::class,
'mw.text' => TextLibrary::class,
'mw.html' => HtmlLibrary::class,
'mw.hash' => HashLibrary::class,
];
/**
* Paths for modules that may be loaded from Lua. See also the
* 'ScribuntoExternalLibraryPaths' hook.
* @var string[] Paths
*/
protected static $libraryPaths = [
'.',
'luabit',
'ustring',
];
/** @var bool */
Added more Lua environment features Package library: * Added a simulation of the Lua 5.1 package library. * Removed mw.import(), replaced it with a package loader. Packages can be retrieved from the wiki, using require('Module:Foo'), or from files distributed with Scribunto, using require('foo'). The "Module:" prefix allows for source compatibility with existing Lua code. * Added a couple of libraries from LuaForge: luabit and stringtools. * Made fetchModuleFromParser() return null on error instead of throwing an exception, to more easily support the desired behaviour of the package loader, which needs to return null on error. * Renamed mw.setupEnvironment() to mw.setup() since it is setting up things other than the environment now. * In MWServer:handleRegisterLibrary(), remove the feature which interprets dots in library names, since LuaSandbox doesn't support this. Improved module isolation and related refactoring: * Expose restricted versions of getfenv() and setfenv() to user Lua code. Requires luasandbox r114952. * Don't cache the export list returned by module execution for later function calls. This breaks isolation of #invoke calls, since the local variables are persistent. * Removed ScribuntoFunctionBase and its children, since it doesn't really have a purpose if it can't cache anything. Instead, invoke functions using a module method called invoke(). * Removed Module::initialize(), replaced it with a validate() function. This is a more elegant interface and works better with the new module caching scheme. * Use a Status object for the return value of Engine::validate() instead of an array. Use the formatting facilities of the Status class. Other: * Removed "too many returns" error, doesn't fit in with Lua conventions. * Use the standalone engine by default, so that the extension will work without configuration for more people. * Added an accessor for $engine->interpreter * Fix mw.clone() to correctly clone metatables * If the standalone interpreter exits due to an error, there are some contexts where the initial error will be caught and ignored, and the user will see the error from checkValid() instead. In this case, rethrow the original error for a more informative message. * Load mw.lua into the initial standalone environment, to reduce code duplication between mw.lua and MWServer.lua. * Fixed a bug in Scribunto_LuaStandaloneInterpreter::handleCall() for functions that return no results. * Fixed a bug in encodeLuaVar() for strings with "\r". Added test case. * In MWServer.lua, don't call error() for internal errors, instead just print the error and exit. This avoids a protocol violation when an error is encountered from within handleCall(). * Added lots of documentation. Lua doc comments are in LuaDoc format. Change-Id: Ie2fd572c362bedf02f45d3fa5352a5280e034740
2012-04-18 03:46:18 +00:00
protected $loaded = false;
/**
* @var LuaInterpreter|null
*/
protected $interpreter;
/**
* @var array
*/
2012-05-22 03:56:07 +00:00
protected $mw;
/**
* @var array<string,?PPFrame>
*/
protected $currentFrames = [];
/**
* @var array<string,string>|null
*/
protected $expandCache = [];
/**
* @var array<string,array|class-string<LibraryBase>>
*/
protected $availableLibraries = [];
2012-05-22 03:56:07 +00:00
private const MAX_EXPAND_CACHE_SIZE = 100;
/**
* If luasandbox is installed and usable then use it,
* otherwise
*
* @param array $options
* @return LuaEngine
*/
public static function newAutodetectEngine( array $options ) {
$engineConf = MediaWikiServices::getInstance()->getMainConfig()->get( 'ScribuntoEngineConf' );
$engine = 'luastandalone';
try {
LuaSandboxInterpreter::checkLuaSandboxVersion();
$engine = 'luasandbox';
} catch ( LuaInterpreterNotFoundError | LuaInterpreterBadVersionError $e ) {
// pass
}
unset( $options['factory'] );
// @phan-suppress-next-line PhanTypeMismatchReturnSuperType
return Scribunto::newEngine( $options + $engineConf[$engine] );
}
Added more Lua environment features Package library: * Added a simulation of the Lua 5.1 package library. * Removed mw.import(), replaced it with a package loader. Packages can be retrieved from the wiki, using require('Module:Foo'), or from files distributed with Scribunto, using require('foo'). The "Module:" prefix allows for source compatibility with existing Lua code. * Added a couple of libraries from LuaForge: luabit and stringtools. * Made fetchModuleFromParser() return null on error instead of throwing an exception, to more easily support the desired behaviour of the package loader, which needs to return null on error. * Renamed mw.setupEnvironment() to mw.setup() since it is setting up things other than the environment now. * In MWServer:handleRegisterLibrary(), remove the feature which interprets dots in library names, since LuaSandbox doesn't support this. Improved module isolation and related refactoring: * Expose restricted versions of getfenv() and setfenv() to user Lua code. Requires luasandbox r114952. * Don't cache the export list returned by module execution for later function calls. This breaks isolation of #invoke calls, since the local variables are persistent. * Removed ScribuntoFunctionBase and its children, since it doesn't really have a purpose if it can't cache anything. Instead, invoke functions using a module method called invoke(). * Removed Module::initialize(), replaced it with a validate() function. This is a more elegant interface and works better with the new module caching scheme. * Use a Status object for the return value of Engine::validate() instead of an array. Use the formatting facilities of the Status class. Other: * Removed "too many returns" error, doesn't fit in with Lua conventions. * Use the standalone engine by default, so that the extension will work without configuration for more people. * Added an accessor for $engine->interpreter * Fix mw.clone() to correctly clone metatables * If the standalone interpreter exits due to an error, there are some contexts where the initial error will be caught and ignored, and the user will see the error from checkValid() instead. In this case, rethrow the original error for a more informative message. * Load mw.lua into the initial standalone environment, to reduce code duplication between mw.lua and MWServer.lua. * Fixed a bug in Scribunto_LuaStandaloneInterpreter::handleCall() for functions that return no results. * Fixed a bug in encodeLuaVar() for strings with "\r". Added test case. * In MWServer.lua, don't call error() for internal errors, instead just print the error and exit. This avoids a protocol violation when an error is encountered from within handleCall(). * Added lots of documentation. Lua doc comments are in LuaDoc format. Change-Id: Ie2fd572c362bedf02f45d3fa5352a5280e034740
2012-04-18 03:46:18 +00:00
/**
* Create a new interpreter object
* @return LuaInterpreter
Added more Lua environment features Package library: * Added a simulation of the Lua 5.1 package library. * Removed mw.import(), replaced it with a package loader. Packages can be retrieved from the wiki, using require('Module:Foo'), or from files distributed with Scribunto, using require('foo'). The "Module:" prefix allows for source compatibility with existing Lua code. * Added a couple of libraries from LuaForge: luabit and stringtools. * Made fetchModuleFromParser() return null on error instead of throwing an exception, to more easily support the desired behaviour of the package loader, which needs to return null on error. * Renamed mw.setupEnvironment() to mw.setup() since it is setting up things other than the environment now. * In MWServer:handleRegisterLibrary(), remove the feature which interprets dots in library names, since LuaSandbox doesn't support this. Improved module isolation and related refactoring: * Expose restricted versions of getfenv() and setfenv() to user Lua code. Requires luasandbox r114952. * Don't cache the export list returned by module execution for later function calls. This breaks isolation of #invoke calls, since the local variables are persistent. * Removed ScribuntoFunctionBase and its children, since it doesn't really have a purpose if it can't cache anything. Instead, invoke functions using a module method called invoke(). * Removed Module::initialize(), replaced it with a validate() function. This is a more elegant interface and works better with the new module caching scheme. * Use a Status object for the return value of Engine::validate() instead of an array. Use the formatting facilities of the Status class. Other: * Removed "too many returns" error, doesn't fit in with Lua conventions. * Use the standalone engine by default, so that the extension will work without configuration for more people. * Added an accessor for $engine->interpreter * Fix mw.clone() to correctly clone metatables * If the standalone interpreter exits due to an error, there are some contexts where the initial error will be caught and ignored, and the user will see the error from checkValid() instead. In this case, rethrow the original error for a more informative message. * Load mw.lua into the initial standalone environment, to reduce code duplication between mw.lua and MWServer.lua. * Fixed a bug in Scribunto_LuaStandaloneInterpreter::handleCall() for functions that return no results. * Fixed a bug in encodeLuaVar() for strings with "\r". Added test case. * In MWServer.lua, don't call error() for internal errors, instead just print the error and exit. This avoids a protocol violation when an error is encountered from within handleCall(). * Added lots of documentation. Lua doc comments are in LuaDoc format. Change-Id: Ie2fd572c362bedf02f45d3fa5352a5280e034740
2012-04-18 03:46:18 +00:00
*/
abstract protected function newInterpreter();
/**
* @param string $text
* @param string|bool $chunkName
* @return LuaModule
*/
protected function newModule( $text, $chunkName ) {
return new LuaModule( $this, $text, $chunkName );
}
/**
* @param string $message
* @param array $params
* @return LuaError
*/
public function newLuaError( $message, $params = [] ) {
return new LuaError( $message, $this->getDefaultExceptionParams() + $params );
}
public function destroy() {
// Break reference cycles
$this->interpreter = null;
$this->mw = [];
$this->expandCache = null;
parent::destroy();
}
Added more Lua environment features Package library: * Added a simulation of the Lua 5.1 package library. * Removed mw.import(), replaced it with a package loader. Packages can be retrieved from the wiki, using require('Module:Foo'), or from files distributed with Scribunto, using require('foo'). The "Module:" prefix allows for source compatibility with existing Lua code. * Added a couple of libraries from LuaForge: luabit and stringtools. * Made fetchModuleFromParser() return null on error instead of throwing an exception, to more easily support the desired behaviour of the package loader, which needs to return null on error. * Renamed mw.setupEnvironment() to mw.setup() since it is setting up things other than the environment now. * In MWServer:handleRegisterLibrary(), remove the feature which interprets dots in library names, since LuaSandbox doesn't support this. Improved module isolation and related refactoring: * Expose restricted versions of getfenv() and setfenv() to user Lua code. Requires luasandbox r114952. * Don't cache the export list returned by module execution for later function calls. This breaks isolation of #invoke calls, since the local variables are persistent. * Removed ScribuntoFunctionBase and its children, since it doesn't really have a purpose if it can't cache anything. Instead, invoke functions using a module method called invoke(). * Removed Module::initialize(), replaced it with a validate() function. This is a more elegant interface and works better with the new module caching scheme. * Use a Status object for the return value of Engine::validate() instead of an array. Use the formatting facilities of the Status class. Other: * Removed "too many returns" error, doesn't fit in with Lua conventions. * Use the standalone engine by default, so that the extension will work without configuration for more people. * Added an accessor for $engine->interpreter * Fix mw.clone() to correctly clone metatables * If the standalone interpreter exits due to an error, there are some contexts where the initial error will be caught and ignored, and the user will see the error from checkValid() instead. In this case, rethrow the original error for a more informative message. * Load mw.lua into the initial standalone environment, to reduce code duplication between mw.lua and MWServer.lua. * Fixed a bug in Scribunto_LuaStandaloneInterpreter::handleCall() for functions that return no results. * Fixed a bug in encodeLuaVar() for strings with "\r". Added test case. * In MWServer.lua, don't call error() for internal errors, instead just print the error and exit. This avoids a protocol violation when an error is encountered from within handleCall(). * Added lots of documentation. Lua doc comments are in LuaDoc format. Change-Id: Ie2fd572c362bedf02f45d3fa5352a5280e034740
2012-04-18 03:46:18 +00:00
/**
* Initialise the interpreter and the base environment
*/
public function load() {
if ( $this->loaded ) {
return;
}
Added more Lua environment features Package library: * Added a simulation of the Lua 5.1 package library. * Removed mw.import(), replaced it with a package loader. Packages can be retrieved from the wiki, using require('Module:Foo'), or from files distributed with Scribunto, using require('foo'). The "Module:" prefix allows for source compatibility with existing Lua code. * Added a couple of libraries from LuaForge: luabit and stringtools. * Made fetchModuleFromParser() return null on error instead of throwing an exception, to more easily support the desired behaviour of the package loader, which needs to return null on error. * Renamed mw.setupEnvironment() to mw.setup() since it is setting up things other than the environment now. * In MWServer:handleRegisterLibrary(), remove the feature which interprets dots in library names, since LuaSandbox doesn't support this. Improved module isolation and related refactoring: * Expose restricted versions of getfenv() and setfenv() to user Lua code. Requires luasandbox r114952. * Don't cache the export list returned by module execution for later function calls. This breaks isolation of #invoke calls, since the local variables are persistent. * Removed ScribuntoFunctionBase and its children, since it doesn't really have a purpose if it can't cache anything. Instead, invoke functions using a module method called invoke(). * Removed Module::initialize(), replaced it with a validate() function. This is a more elegant interface and works better with the new module caching scheme. * Use a Status object for the return value of Engine::validate() instead of an array. Use the formatting facilities of the Status class. Other: * Removed "too many returns" error, doesn't fit in with Lua conventions. * Use the standalone engine by default, so that the extension will work without configuration for more people. * Added an accessor for $engine->interpreter * Fix mw.clone() to correctly clone metatables * If the standalone interpreter exits due to an error, there are some contexts where the initial error will be caught and ignored, and the user will see the error from checkValid() instead. In this case, rethrow the original error for a more informative message. * Load mw.lua into the initial standalone environment, to reduce code duplication between mw.lua and MWServer.lua. * Fixed a bug in Scribunto_LuaStandaloneInterpreter::handleCall() for functions that return no results. * Fixed a bug in encodeLuaVar() for strings with "\r". Added test case. * In MWServer.lua, don't call error() for internal errors, instead just print the error and exit. This avoids a protocol violation when an error is encountered from within handleCall(). * Added lots of documentation. Lua doc comments are in LuaDoc format. Change-Id: Ie2fd572c362bedf02f45d3fa5352a5280e034740
2012-04-18 03:46:18 +00:00
$this->loaded = true;
try {
$this->interpreter = $this->newInterpreter();
Added more Lua environment features Package library: * Added a simulation of the Lua 5.1 package library. * Removed mw.import(), replaced it with a package loader. Packages can be retrieved from the wiki, using require('Module:Foo'), or from files distributed with Scribunto, using require('foo'). The "Module:" prefix allows for source compatibility with existing Lua code. * Added a couple of libraries from LuaForge: luabit and stringtools. * Made fetchModuleFromParser() return null on error instead of throwing an exception, to more easily support the desired behaviour of the package loader, which needs to return null on error. * Renamed mw.setupEnvironment() to mw.setup() since it is setting up things other than the environment now. * In MWServer:handleRegisterLibrary(), remove the feature which interprets dots in library names, since LuaSandbox doesn't support this. Improved module isolation and related refactoring: * Expose restricted versions of getfenv() and setfenv() to user Lua code. Requires luasandbox r114952. * Don't cache the export list returned by module execution for later function calls. This breaks isolation of #invoke calls, since the local variables are persistent. * Removed ScribuntoFunctionBase and its children, since it doesn't really have a purpose if it can't cache anything. Instead, invoke functions using a module method called invoke(). * Removed Module::initialize(), replaced it with a validate() function. This is a more elegant interface and works better with the new module caching scheme. * Use a Status object for the return value of Engine::validate() instead of an array. Use the formatting facilities of the Status class. Other: * Removed "too many returns" error, doesn't fit in with Lua conventions. * Use the standalone engine by default, so that the extension will work without configuration for more people. * Added an accessor for $engine->interpreter * Fix mw.clone() to correctly clone metatables * If the standalone interpreter exits due to an error, there are some contexts where the initial error will be caught and ignored, and the user will see the error from checkValid() instead. In this case, rethrow the original error for a more informative message. * Load mw.lua into the initial standalone environment, to reduce code duplication between mw.lua and MWServer.lua. * Fixed a bug in Scribunto_LuaStandaloneInterpreter::handleCall() for functions that return no results. * Fixed a bug in encodeLuaVar() for strings with "\r". Added test case. * In MWServer.lua, don't call error() for internal errors, instead just print the error and exit. This avoids a protocol violation when an error is encountered from within handleCall(). * Added lots of documentation. Lua doc comments are in LuaDoc format. Change-Id: Ie2fd572c362bedf02f45d3fa5352a5280e034740
2012-04-18 03:46:18 +00:00
$funcs = [
'loadPackage',
'loadPHPLibrary',
'frameExists',
'newChildFrame',
'getExpandedArgument',
'getAllExpandedArguments',
'expandTemplate',
'callParserFunction',
'preprocess',
'incrementExpensiveFunctionCount',
'isSubsting',
'getFrameTitle',
'setTTL',
'addWarning',
'loadJsonData',
];
$lib = [];
foreach ( $funcs as $name ) {
$lib[$name] = [ $this, $name ];
}
2012-05-22 03:56:07 +00:00
$this->registerInterface( 'mwInit.lua', [] );
$this->mw = $this->registerInterface( 'mw.lua', $lib,
[ 'allowEnvFuncs' => $this->options['allowEnvFuncs'] ] );
$this->availableLibraries = $this->getLibraries( 'lua', self::$libraryClasses );
foreach ( $this->availableLibraries as $name => $def ) {
$this->instantiatePHPLibrary( $name, $def, false );
}
} catch ( Exception $ex ) {
$this->loaded = false;
$this->interpreter = null;
throw $ex;
}
}
/**
* Register a Lua Library
*
* This should be called from the library's PHP module's register() method.
*
* The value for $interfaceFuncs is used to populate the mw_interface
* global that is defined when the library's Lua module is loaded. Values
* must be PHP callables, which will be seen in Lua as functions.
*
* @param string $moduleFileName The path to the Lua portion of the library
* (absolute, or relative to $this->getLuaLibDir())
* @param array<string,callable> $interfaceFuncs Populates mw_interface
* @param array $setupOptions Passed to the modules setupInterface() method.
* @return array Lua package
*/
public function registerInterface( $moduleFileName, $interfaceFuncs, $setupOptions = [] ) {
$this->interpreter->registerLibrary( 'mw_interface', $interfaceFuncs );
$moduleFileName = $this->normalizeModuleFileName( $moduleFileName );
$package = $this->loadLibraryFromFile( $moduleFileName );
if ( !empty( $package['setupInterface'] ) ) {
$this->interpreter->callFunction( $package['setupInterface'], $setupOptions );
}
return $package;
}
/**
* Return the base path for Lua modules.
* @return string
*/
public function getLuaLibDir() {
return __DIR__ . '/lualib';
}
/**
* Normalize a lua module to its full path. If path does not look like an
* absolute path (i.e. begins with DIRECTORY_SEPARATOR or "X:"), prepend
* getLuaLibDir()
*
* @param string $fileName name of the lua module file
* @return string
*/
protected function normalizeModuleFileName( $fileName ) {
if ( !preg_match( '<^(?:[a-zA-Z]:)?' . preg_quote( DIRECTORY_SEPARATOR ) . '>', $fileName ) ) {
$fileName = "{$this->getLuaLibDir()}/{$fileName}";
}
return $fileName;
}
/**
* Get performance characteristics of the Lua engine/interpreter
*
* phpCallsRequireSerialization: boolean
* whether calls between PHP and Lua functions require (slow)
* serialization of parameters and return values
*
* @return array
*/
abstract public function getPerformanceCharacteristics();
Added more Lua environment features Package library: * Added a simulation of the Lua 5.1 package library. * Removed mw.import(), replaced it with a package loader. Packages can be retrieved from the wiki, using require('Module:Foo'), or from files distributed with Scribunto, using require('foo'). The "Module:" prefix allows for source compatibility with existing Lua code. * Added a couple of libraries from LuaForge: luabit and stringtools. * Made fetchModuleFromParser() return null on error instead of throwing an exception, to more easily support the desired behaviour of the package loader, which needs to return null on error. * Renamed mw.setupEnvironment() to mw.setup() since it is setting up things other than the environment now. * In MWServer:handleRegisterLibrary(), remove the feature which interprets dots in library names, since LuaSandbox doesn't support this. Improved module isolation and related refactoring: * Expose restricted versions of getfenv() and setfenv() to user Lua code. Requires luasandbox r114952. * Don't cache the export list returned by module execution for later function calls. This breaks isolation of #invoke calls, since the local variables are persistent. * Removed ScribuntoFunctionBase and its children, since it doesn't really have a purpose if it can't cache anything. Instead, invoke functions using a module method called invoke(). * Removed Module::initialize(), replaced it with a validate() function. This is a more elegant interface and works better with the new module caching scheme. * Use a Status object for the return value of Engine::validate() instead of an array. Use the formatting facilities of the Status class. Other: * Removed "too many returns" error, doesn't fit in with Lua conventions. * Use the standalone engine by default, so that the extension will work without configuration for more people. * Added an accessor for $engine->interpreter * Fix mw.clone() to correctly clone metatables * If the standalone interpreter exits due to an error, there are some contexts where the initial error will be caught and ignored, and the user will see the error from checkValid() instead. In this case, rethrow the original error for a more informative message. * Load mw.lua into the initial standalone environment, to reduce code duplication between mw.lua and MWServer.lua. * Fixed a bug in Scribunto_LuaStandaloneInterpreter::handleCall() for functions that return no results. * Fixed a bug in encodeLuaVar() for strings with "\r". Added test case. * In MWServer.lua, don't call error() for internal errors, instead just print the error and exit. This avoids a protocol violation when an error is encountered from within handleCall(). * Added lots of documentation. Lua doc comments are in LuaDoc format. Change-Id: Ie2fd572c362bedf02f45d3fa5352a5280e034740
2012-04-18 03:46:18 +00:00
/**
* Get the current interpreter object
* @return LuaInterpreter
Added more Lua environment features Package library: * Added a simulation of the Lua 5.1 package library. * Removed mw.import(), replaced it with a package loader. Packages can be retrieved from the wiki, using require('Module:Foo'), or from files distributed with Scribunto, using require('foo'). The "Module:" prefix allows for source compatibility with existing Lua code. * Added a couple of libraries from LuaForge: luabit and stringtools. * Made fetchModuleFromParser() return null on error instead of throwing an exception, to more easily support the desired behaviour of the package loader, which needs to return null on error. * Renamed mw.setupEnvironment() to mw.setup() since it is setting up things other than the environment now. * In MWServer:handleRegisterLibrary(), remove the feature which interprets dots in library names, since LuaSandbox doesn't support this. Improved module isolation and related refactoring: * Expose restricted versions of getfenv() and setfenv() to user Lua code. Requires luasandbox r114952. * Don't cache the export list returned by module execution for later function calls. This breaks isolation of #invoke calls, since the local variables are persistent. * Removed ScribuntoFunctionBase and its children, since it doesn't really have a purpose if it can't cache anything. Instead, invoke functions using a module method called invoke(). * Removed Module::initialize(), replaced it with a validate() function. This is a more elegant interface and works better with the new module caching scheme. * Use a Status object for the return value of Engine::validate() instead of an array. Use the formatting facilities of the Status class. Other: * Removed "too many returns" error, doesn't fit in with Lua conventions. * Use the standalone engine by default, so that the extension will work without configuration for more people. * Added an accessor for $engine->interpreter * Fix mw.clone() to correctly clone metatables * If the standalone interpreter exits due to an error, there are some contexts where the initial error will be caught and ignored, and the user will see the error from checkValid() instead. In this case, rethrow the original error for a more informative message. * Load mw.lua into the initial standalone environment, to reduce code duplication between mw.lua and MWServer.lua. * Fixed a bug in Scribunto_LuaStandaloneInterpreter::handleCall() for functions that return no results. * Fixed a bug in encodeLuaVar() for strings with "\r". Added test case. * In MWServer.lua, don't call error() for internal errors, instead just print the error and exit. This avoids a protocol violation when an error is encountered from within handleCall(). * Added lots of documentation. Lua doc comments are in LuaDoc format. Change-Id: Ie2fd572c362bedf02f45d3fa5352a5280e034740
2012-04-18 03:46:18 +00:00
*/
public function getInterpreter() {
$this->load();
return $this->interpreter;
}
/**
* Replaces the list of current frames, and return a ScopedCallback that
* will reset them when it goes out of scope.
*
* @param PPFrame|null $frame If null, an empty frame with no parent will be used
* @return ScopedCallback
*/
private function setupCurrentFrames( ?PPFrame $frame = null ) {
if ( !$frame ) {
$frame = $this->getParser()->getPreprocessor()->newFrame();
}
$oldFrames = $this->currentFrames;
$oldExpandCache = $this->expandCache;
$this->currentFrames = [
'current' => $frame,
'parent' => $frame->parent ?? null,
];
$this->expandCache = [];
return new ScopedCallback( function () use ( $oldFrames, $oldExpandCache ) {
$this->currentFrames = $oldFrames;
$this->expandCache = $oldExpandCache;
} );
}
Added more Lua environment features Package library: * Added a simulation of the Lua 5.1 package library. * Removed mw.import(), replaced it with a package loader. Packages can be retrieved from the wiki, using require('Module:Foo'), or from files distributed with Scribunto, using require('foo'). The "Module:" prefix allows for source compatibility with existing Lua code. * Added a couple of libraries from LuaForge: luabit and stringtools. * Made fetchModuleFromParser() return null on error instead of throwing an exception, to more easily support the desired behaviour of the package loader, which needs to return null on error. * Renamed mw.setupEnvironment() to mw.setup() since it is setting up things other than the environment now. * In MWServer:handleRegisterLibrary(), remove the feature which interprets dots in library names, since LuaSandbox doesn't support this. Improved module isolation and related refactoring: * Expose restricted versions of getfenv() and setfenv() to user Lua code. Requires luasandbox r114952. * Don't cache the export list returned by module execution for later function calls. This breaks isolation of #invoke calls, since the local variables are persistent. * Removed ScribuntoFunctionBase and its children, since it doesn't really have a purpose if it can't cache anything. Instead, invoke functions using a module method called invoke(). * Removed Module::initialize(), replaced it with a validate() function. This is a more elegant interface and works better with the new module caching scheme. * Use a Status object for the return value of Engine::validate() instead of an array. Use the formatting facilities of the Status class. Other: * Removed "too many returns" error, doesn't fit in with Lua conventions. * Use the standalone engine by default, so that the extension will work without configuration for more people. * Added an accessor for $engine->interpreter * Fix mw.clone() to correctly clone metatables * If the standalone interpreter exits due to an error, there are some contexts where the initial error will be caught and ignored, and the user will see the error from checkValid() instead. In this case, rethrow the original error for a more informative message. * Load mw.lua into the initial standalone environment, to reduce code duplication between mw.lua and MWServer.lua. * Fixed a bug in Scribunto_LuaStandaloneInterpreter::handleCall() for functions that return no results. * Fixed a bug in encodeLuaVar() for strings with "\r". Added test case. * In MWServer.lua, don't call error() for internal errors, instead just print the error and exit. This avoids a protocol violation when an error is encountered from within handleCall(). * Added lots of documentation. Lua doc comments are in LuaDoc format. Change-Id: Ie2fd572c362bedf02f45d3fa5352a5280e034740
2012-04-18 03:46:18 +00:00
/**
* Execute a module chunk in a new isolated environment, and return the specified function
* @param mixed $chunk As accepted by LuaInterpreter::callFunction()
* @param string $functionName
* @param PPFrame|null $frame
* @return mixed
* @throws ScribuntoException
Added more Lua environment features Package library: * Added a simulation of the Lua 5.1 package library. * Removed mw.import(), replaced it with a package loader. Packages can be retrieved from the wiki, using require('Module:Foo'), or from files distributed with Scribunto, using require('foo'). The "Module:" prefix allows for source compatibility with existing Lua code. * Added a couple of libraries from LuaForge: luabit and stringtools. * Made fetchModuleFromParser() return null on error instead of throwing an exception, to more easily support the desired behaviour of the package loader, which needs to return null on error. * Renamed mw.setupEnvironment() to mw.setup() since it is setting up things other than the environment now. * In MWServer:handleRegisterLibrary(), remove the feature which interprets dots in library names, since LuaSandbox doesn't support this. Improved module isolation and related refactoring: * Expose restricted versions of getfenv() and setfenv() to user Lua code. Requires luasandbox r114952. * Don't cache the export list returned by module execution for later function calls. This breaks isolation of #invoke calls, since the local variables are persistent. * Removed ScribuntoFunctionBase and its children, since it doesn't really have a purpose if it can't cache anything. Instead, invoke functions using a module method called invoke(). * Removed Module::initialize(), replaced it with a validate() function. This is a more elegant interface and works better with the new module caching scheme. * Use a Status object for the return value of Engine::validate() instead of an array. Use the formatting facilities of the Status class. Other: * Removed "too many returns" error, doesn't fit in with Lua conventions. * Use the standalone engine by default, so that the extension will work without configuration for more people. * Added an accessor for $engine->interpreter * Fix mw.clone() to correctly clone metatables * If the standalone interpreter exits due to an error, there are some contexts where the initial error will be caught and ignored, and the user will see the error from checkValid() instead. In this case, rethrow the original error for a more informative message. * Load mw.lua into the initial standalone environment, to reduce code duplication between mw.lua and MWServer.lua. * Fixed a bug in Scribunto_LuaStandaloneInterpreter::handleCall() for functions that return no results. * Fixed a bug in encodeLuaVar() for strings with "\r". Added test case. * In MWServer.lua, don't call error() for internal errors, instead just print the error and exit. This avoids a protocol violation when an error is encountered from within handleCall(). * Added lots of documentation. Lua doc comments are in LuaDoc format. Change-Id: Ie2fd572c362bedf02f45d3fa5352a5280e034740
2012-04-18 03:46:18 +00:00
*/
public function executeModule( $chunk, $functionName, $frame ) {
// $resetFrames is a ScopedCallback, so it has a purpose even though it appears unused.
$resetFrames = $this->setupCurrentFrames( $frame );
$retval = $this->getInterpreter()->callFunction(
$this->mw['executeModule'], $chunk, $functionName
);
if ( !$retval[0] ) {
// If we get here, it means we asked for an element from the table the module returned,
// but it returned something other than a table. In this case, $retval[1] contains the type
// of what it did returned, instead of the value we asked for.
throw $this->newException(
'scribunto-lua-notarrayreturn', [ 'args' => [ $retval[1] ] ]
);
}
return $retval[1];
2012-05-22 03:56:07 +00:00
}
/**
* Execute a module function chunk
* @param mixed $chunk As accepted by LuaInterpreter::callFunction()
* @param PPFrame|null $frame
* @return array
2012-05-22 03:56:07 +00:00
*/
public function executeFunctionChunk( $chunk, $frame ) {
// $resetFrames is a ScopedCallback, so it has a purpose even though it appears unused.
$resetFrames = $this->setupCurrentFrames( $frame );
return $this->getInterpreter()->callFunction(
$this->mw['executeFunction'],
$chunk );
}
/**
* Get data logged by modules
* @return string Logged data
*/
protected function getLogBuffer() {
if ( !$this->loaded ) {
return '';
}
try {
$log = $this->getInterpreter()->callFunction( $this->mw['getLogBuffer'] );
return $log[0];
} catch ( ScribuntoException $ex ) {
// Probably time expired, ignore it.
return '';
}
}
/**
* Format the logged data for HTML output
* @param string $logs Logged data
* @param bool $localize Whether to localize the message key
* @return string HTML
*/
protected function formatHtmlLogs( $logs, $localize ) {
$keyMsg = wfMessage( 'scribunto-limitreport-logs' );
if ( !$localize ) {
$keyMsg->inLanguage( 'en' )->useDatabase( false );
}
return Html::openElement( 'tr' ) .
Html::rawElement( 'th', [ 'colspan' => 2 ], $keyMsg->parse() ) .
Html::closeElement( 'tr' ) .
Html::openElement( 'tr' ) .
Html::openElement( 'td', [ 'colspan' => 2 ] ) .
Html::openElement( 'div', [ 'class' => 'mw-collapsible mw-collapsed' ] ) .
Html::element( 'pre', [ 'class' => 'scribunto-limitreport-logs' ], $logs ) .
Html::closeElement( 'div' ) .
Html::closeElement( 'td' ) .
Html::closeElement( 'tr' );
}
Added more Lua environment features Package library: * Added a simulation of the Lua 5.1 package library. * Removed mw.import(), replaced it with a package loader. Packages can be retrieved from the wiki, using require('Module:Foo'), or from files distributed with Scribunto, using require('foo'). The "Module:" prefix allows for source compatibility with existing Lua code. * Added a couple of libraries from LuaForge: luabit and stringtools. * Made fetchModuleFromParser() return null on error instead of throwing an exception, to more easily support the desired behaviour of the package loader, which needs to return null on error. * Renamed mw.setupEnvironment() to mw.setup() since it is setting up things other than the environment now. * In MWServer:handleRegisterLibrary(), remove the feature which interprets dots in library names, since LuaSandbox doesn't support this. Improved module isolation and related refactoring: * Expose restricted versions of getfenv() and setfenv() to user Lua code. Requires luasandbox r114952. * Don't cache the export list returned by module execution for later function calls. This breaks isolation of #invoke calls, since the local variables are persistent. * Removed ScribuntoFunctionBase and its children, since it doesn't really have a purpose if it can't cache anything. Instead, invoke functions using a module method called invoke(). * Removed Module::initialize(), replaced it with a validate() function. This is a more elegant interface and works better with the new module caching scheme. * Use a Status object for the return value of Engine::validate() instead of an array. Use the formatting facilities of the Status class. Other: * Removed "too many returns" error, doesn't fit in with Lua conventions. * Use the standalone engine by default, so that the extension will work without configuration for more people. * Added an accessor for $engine->interpreter * Fix mw.clone() to correctly clone metatables * If the standalone interpreter exits due to an error, there are some contexts where the initial error will be caught and ignored, and the user will see the error from checkValid() instead. In this case, rethrow the original error for a more informative message. * Load mw.lua into the initial standalone environment, to reduce code duplication between mw.lua and MWServer.lua. * Fixed a bug in Scribunto_LuaStandaloneInterpreter::handleCall() for functions that return no results. * Fixed a bug in encodeLuaVar() for strings with "\r". Added test case. * In MWServer.lua, don't call error() for internal errors, instead just print the error and exit. This avoids a protocol violation when an error is encountered from within handleCall(). * Added lots of documentation. Lua doc comments are in LuaDoc format. Change-Id: Ie2fd572c362bedf02f45d3fa5352a5280e034740
2012-04-18 03:46:18 +00:00
/**
* Load a library from the given file and execute it in the base environment.
* @param string $fileName File name/path to load
* @return array|null the export list, or null if there isn't one.
Added more Lua environment features Package library: * Added a simulation of the Lua 5.1 package library. * Removed mw.import(), replaced it with a package loader. Packages can be retrieved from the wiki, using require('Module:Foo'), or from files distributed with Scribunto, using require('foo'). The "Module:" prefix allows for source compatibility with existing Lua code. * Added a couple of libraries from LuaForge: luabit and stringtools. * Made fetchModuleFromParser() return null on error instead of throwing an exception, to more easily support the desired behaviour of the package loader, which needs to return null on error. * Renamed mw.setupEnvironment() to mw.setup() since it is setting up things other than the environment now. * In MWServer:handleRegisterLibrary(), remove the feature which interprets dots in library names, since LuaSandbox doesn't support this. Improved module isolation and related refactoring: * Expose restricted versions of getfenv() and setfenv() to user Lua code. Requires luasandbox r114952. * Don't cache the export list returned by module execution for later function calls. This breaks isolation of #invoke calls, since the local variables are persistent. * Removed ScribuntoFunctionBase and its children, since it doesn't really have a purpose if it can't cache anything. Instead, invoke functions using a module method called invoke(). * Removed Module::initialize(), replaced it with a validate() function. This is a more elegant interface and works better with the new module caching scheme. * Use a Status object for the return value of Engine::validate() instead of an array. Use the formatting facilities of the Status class. Other: * Removed "too many returns" error, doesn't fit in with Lua conventions. * Use the standalone engine by default, so that the extension will work without configuration for more people. * Added an accessor for $engine->interpreter * Fix mw.clone() to correctly clone metatables * If the standalone interpreter exits due to an error, there are some contexts where the initial error will be caught and ignored, and the user will see the error from checkValid() instead. In this case, rethrow the original error for a more informative message. * Load mw.lua into the initial standalone environment, to reduce code duplication between mw.lua and MWServer.lua. * Fixed a bug in Scribunto_LuaStandaloneInterpreter::handleCall() for functions that return no results. * Fixed a bug in encodeLuaVar() for strings with "\r". Added test case. * In MWServer.lua, don't call error() for internal errors, instead just print the error and exit. This avoids a protocol violation when an error is encountered from within handleCall(). * Added lots of documentation. Lua doc comments are in LuaDoc format. Change-Id: Ie2fd572c362bedf02f45d3fa5352a5280e034740
2012-04-18 03:46:18 +00:00
*/
protected function loadLibraryFromFile( $fileName ) {
static $cache = null;
$objectcachefactory = MediaWikiServices::getInstance()->getObjectCacheFactory();
if ( !$cache ) {
$cache = $objectcachefactory->getLocalServerInstance( CACHE_HASH );
}
$mtime = filemtime( $fileName );
if ( $mtime === false ) {
throw new RuntimeException( 'Lua file does not exist: ' . $fileName );
}
$cacheKey = $cache->makeGlobalKey( __CLASS__, $fileName );
$fileData = $cache->get( $cacheKey );
$code = false;
if ( $fileData ) {
[ $code, $cachedMtime ] = $fileData;
if ( $cachedMtime < $mtime ) {
$code = false;
}
}
if ( !$code ) {
$code = file_get_contents( $fileName );
if ( $code === false ) {
throw new RuntimeException( 'Lua file does not exist: ' . $fileName );
}
$cache->set( $cacheKey, [ $code, $mtime ], 60 * 5 );
}
# Prepending an "@" to the chunk name makes Lua think it is a filename
$module = $this->getInterpreter()->loadString( $code, '@' . basename( $fileName ) );
$ret = $this->getInterpreter()->callFunction( $module );
return $ret[0] ?? null;
}
/** @inheritDoc */
public function getGeSHiLanguage() {
return 'lua';
}
2012-05-22 03:56:07 +00:00
/** @inheritDoc */
public function getCodeEditorLanguage() {
return 'lua';
}
/** @inheritDoc */
public function runConsole( array $params ) {
// $resetFrames is a ScopedCallback, so it has a purpose even though it appears unused.
$resetFrames = $this->setupCurrentFrames();
/**
* TODO: provide some means for giving correct line numbers for errors
* in console input, and for producing an informative error message
* if there is an error in prevQuestions.
*
* Maybe each console line could be evaluated as a different chunk,
* apparently that's what lua.c does.
*/
$code = "return function (__init, exe)\n" .
"if not exe then exe = function(...) return true, ... end end\n" .
"local p = select(2, exe(__init) )\n" .
"__init, exe = nil, nil\n" .
"local print = mw.log\n";
foreach ( $params['prevQuestions'] as $q ) {
if ( substr( $q, 0, 1 ) === '=' ) {
$code .= "print(" . substr( $q, 1 ) . ")";
} else {
$code .= $q;
}
$code .= "\n";
}
$code .= "mw.clearLogBuffer()\n";
if ( substr( $params['question'], 0, 1 ) === '=' ) {
// Treat a statement starting with "=" as a return statement, like in lua.c
$code .= "local ret = mw.allToString(" . substr( $params['question'], 1 ) . ")\n" .
"return ret, mw.getLogBuffer()\n";
} else {
$code .= $params['question'] . "\n" .
"return nil, mw.getLogBuffer()\n";
}
$code .= "end\n";
if ( $params['title']->hasContentModel( CONTENT_MODEL_SCRIBUNTO ) ) {
$contentModule = $this->newModule(
$params['content'], $params['title']->getPrefixedDBkey() );
$contentInit = $contentModule->getInitChunk();
$contentExe = $this->mw['executeModule'];
} else {
$contentInit = $params['content'];
$contentExe = null;
}
$consoleModule = $this->newModule(
$code,
wfMessage( 'scribunto-console-current-src' )->text()
);
$consoleInit = $consoleModule->getInitChunk();
$ret = $this->getInterpreter()->callFunction( $this->mw['executeModule'], $consoleInit, false );
$func = $ret[1];
$ret = $this->getInterpreter()->callFunction( $func, $contentInit, $contentExe );
return [
'return' => $ret[0] ?? null,
'print' => $ret[1] ?? '',
];
}
/**
* Workalike for luaL_checktype()
*
* @param string $funcName The Lua function name, for use in error messages
* @param array $args The argument array
* @param int $index0 The zero-based argument index
* @param string|string[] $type The allowed type names as given by gettype()
* @param string $msgType The type name used in the error message
* @throws LuaError
*/
public function checkType( $funcName, $args, $index0, $type, $msgType ) {
if ( !is_array( $type ) ) {
$type = [ $type ];
}
if ( !isset( $args[$index0] ) || !in_array( gettype( $args[$index0] ), $type, true ) ) {
$index1 = $index0 + 1;
throw new LuaError( "bad argument #$index1 to '$funcName' ($msgType expected)" );
}
}
/**
* Workalike for luaL_checkstring()
*
* @param string $funcName The Lua function name, for use in error messages
* @param array $args The argument array
* @param int $index0 The zero-based argument index
*/
public function checkString( $funcName, $args, $index0 ) {
$this->checkType( $funcName, $args, $index0, 'string', 'string' );
}
/**
* Workalike for luaL_checknumber()
*
* @param string $funcName The Lua function name, for use in error messages
* @param array $args The argument array
* @param int $index0 The zero-based argument index
*/
public function checkNumber( $funcName, $args, $index0 ) {
$this->checkType( $funcName, $args, $index0, [ 'integer', 'double' ], 'number' );
}
/**
* Instantiate and register a library.
* @param string $name
* @param array|class-string<LibraryBase> $def
* @param bool $loadDeferred
* @return array|null
*/
private function instantiatePHPLibrary( $name, $def, $loadDeferred ) {
$def = $this->availableLibraries[$name];
if ( is_string( $def ) ) {
/** @var LibraryBase $class */
$class = new $def( $this );
} else {
if ( !$loadDeferred && !empty( $def['deferLoad'] ) ) {
return null;
}
if ( isset( $def['class'] ) ) {
/** @var LibraryBase $class */
$class = new $def['class']( $this );
} else {
throw new RuntimeException( "No class for library \"$name\"" );
}
}
return $class->register();
}
/**
* Handler for the loadPHPLibrary() callback. Register the specified
* library and return its function table. It's not necessary to cache the
* function table in the object instance, since there is caching in a
* wrapper on the Lua side.
* @internal
* @param string $name
* @return array
*/
public function loadPHPLibrary( $name ) {
$args = func_get_args();
$this->checkString( 'loadPHPLibrary', $args, 0 );
$ret = null;
if ( isset( $this->availableLibraries[$name] ) ) {
$ret = $this->instantiatePHPLibrary( $name, $this->availableLibraries[$name], true );
}
return [ $ret ];
}
Added more Lua environment features Package library: * Added a simulation of the Lua 5.1 package library. * Removed mw.import(), replaced it with a package loader. Packages can be retrieved from the wiki, using require('Module:Foo'), or from files distributed with Scribunto, using require('foo'). The "Module:" prefix allows for source compatibility with existing Lua code. * Added a couple of libraries from LuaForge: luabit and stringtools. * Made fetchModuleFromParser() return null on error instead of throwing an exception, to more easily support the desired behaviour of the package loader, which needs to return null on error. * Renamed mw.setupEnvironment() to mw.setup() since it is setting up things other than the environment now. * In MWServer:handleRegisterLibrary(), remove the feature which interprets dots in library names, since LuaSandbox doesn't support this. Improved module isolation and related refactoring: * Expose restricted versions of getfenv() and setfenv() to user Lua code. Requires luasandbox r114952. * Don't cache the export list returned by module execution for later function calls. This breaks isolation of #invoke calls, since the local variables are persistent. * Removed ScribuntoFunctionBase and its children, since it doesn't really have a purpose if it can't cache anything. Instead, invoke functions using a module method called invoke(). * Removed Module::initialize(), replaced it with a validate() function. This is a more elegant interface and works better with the new module caching scheme. * Use a Status object for the return value of Engine::validate() instead of an array. Use the formatting facilities of the Status class. Other: * Removed "too many returns" error, doesn't fit in with Lua conventions. * Use the standalone engine by default, so that the extension will work without configuration for more people. * Added an accessor for $engine->interpreter * Fix mw.clone() to correctly clone metatables * If the standalone interpreter exits due to an error, there are some contexts where the initial error will be caught and ignored, and the user will see the error from checkValid() instead. In this case, rethrow the original error for a more informative message. * Load mw.lua into the initial standalone environment, to reduce code duplication between mw.lua and MWServer.lua. * Fixed a bug in Scribunto_LuaStandaloneInterpreter::handleCall() for functions that return no results. * Fixed a bug in encodeLuaVar() for strings with "\r". Added test case. * In MWServer.lua, don't call error() for internal errors, instead just print the error and exit. This avoids a protocol violation when an error is encountered from within handleCall(). * Added lots of documentation. Lua doc comments are in LuaDoc format. Change-Id: Ie2fd572c362bedf02f45d3fa5352a5280e034740
2012-04-18 03:46:18 +00:00
/**
* Handler for the loadPackage() callback. Load the specified
Added more Lua environment features Package library: * Added a simulation of the Lua 5.1 package library. * Removed mw.import(), replaced it with a package loader. Packages can be retrieved from the wiki, using require('Module:Foo'), or from files distributed with Scribunto, using require('foo'). The "Module:" prefix allows for source compatibility with existing Lua code. * Added a couple of libraries from LuaForge: luabit and stringtools. * Made fetchModuleFromParser() return null on error instead of throwing an exception, to more easily support the desired behaviour of the package loader, which needs to return null on error. * Renamed mw.setupEnvironment() to mw.setup() since it is setting up things other than the environment now. * In MWServer:handleRegisterLibrary(), remove the feature which interprets dots in library names, since LuaSandbox doesn't support this. Improved module isolation and related refactoring: * Expose restricted versions of getfenv() and setfenv() to user Lua code. Requires luasandbox r114952. * Don't cache the export list returned by module execution for later function calls. This breaks isolation of #invoke calls, since the local variables are persistent. * Removed ScribuntoFunctionBase and its children, since it doesn't really have a purpose if it can't cache anything. Instead, invoke functions using a module method called invoke(). * Removed Module::initialize(), replaced it with a validate() function. This is a more elegant interface and works better with the new module caching scheme. * Use a Status object for the return value of Engine::validate() instead of an array. Use the formatting facilities of the Status class. Other: * Removed "too many returns" error, doesn't fit in with Lua conventions. * Use the standalone engine by default, so that the extension will work without configuration for more people. * Added an accessor for $engine->interpreter * Fix mw.clone() to correctly clone metatables * If the standalone interpreter exits due to an error, there are some contexts where the initial error will be caught and ignored, and the user will see the error from checkValid() instead. In this case, rethrow the original error for a more informative message. * Load mw.lua into the initial standalone environment, to reduce code duplication between mw.lua and MWServer.lua. * Fixed a bug in Scribunto_LuaStandaloneInterpreter::handleCall() for functions that return no results. * Fixed a bug in encodeLuaVar() for strings with "\r". Added test case. * In MWServer.lua, don't call error() for internal errors, instead just print the error and exit. This avoids a protocol violation when an error is encountered from within handleCall(). * Added lots of documentation. Lua doc comments are in LuaDoc format. Change-Id: Ie2fd572c362bedf02f45d3fa5352a5280e034740
2012-04-18 03:46:18 +00:00
* module and return its chunk. It's not necessary to cache the resulting
* chunk in the object instance, since there is caching in a wrapper on the
* Lua side.
* @internal
* @param string $name
* @return array
Added more Lua environment features Package library: * Added a simulation of the Lua 5.1 package library. * Removed mw.import(), replaced it with a package loader. Packages can be retrieved from the wiki, using require('Module:Foo'), or from files distributed with Scribunto, using require('foo'). The "Module:" prefix allows for source compatibility with existing Lua code. * Added a couple of libraries from LuaForge: luabit and stringtools. * Made fetchModuleFromParser() return null on error instead of throwing an exception, to more easily support the desired behaviour of the package loader, which needs to return null on error. * Renamed mw.setupEnvironment() to mw.setup() since it is setting up things other than the environment now. * In MWServer:handleRegisterLibrary(), remove the feature which interprets dots in library names, since LuaSandbox doesn't support this. Improved module isolation and related refactoring: * Expose restricted versions of getfenv() and setfenv() to user Lua code. Requires luasandbox r114952. * Don't cache the export list returned by module execution for later function calls. This breaks isolation of #invoke calls, since the local variables are persistent. * Removed ScribuntoFunctionBase and its children, since it doesn't really have a purpose if it can't cache anything. Instead, invoke functions using a module method called invoke(). * Removed Module::initialize(), replaced it with a validate() function. This is a more elegant interface and works better with the new module caching scheme. * Use a Status object for the return value of Engine::validate() instead of an array. Use the formatting facilities of the Status class. Other: * Removed "too many returns" error, doesn't fit in with Lua conventions. * Use the standalone engine by default, so that the extension will work without configuration for more people. * Added an accessor for $engine->interpreter * Fix mw.clone() to correctly clone metatables * If the standalone interpreter exits due to an error, there are some contexts where the initial error will be caught and ignored, and the user will see the error from checkValid() instead. In this case, rethrow the original error for a more informative message. * Load mw.lua into the initial standalone environment, to reduce code duplication between mw.lua and MWServer.lua. * Fixed a bug in Scribunto_LuaStandaloneInterpreter::handleCall() for functions that return no results. * Fixed a bug in encodeLuaVar() for strings with "\r". Added test case. * In MWServer.lua, don't call error() for internal errors, instead just print the error and exit. This avoids a protocol violation when an error is encountered from within handleCall(). * Added lots of documentation. Lua doc comments are in LuaDoc format. Change-Id: Ie2fd572c362bedf02f45d3fa5352a5280e034740
2012-04-18 03:46:18 +00:00
*/
public function loadPackage( $name ) {
$args = func_get_args();
Added more Lua environment features Package library: * Added a simulation of the Lua 5.1 package library. * Removed mw.import(), replaced it with a package loader. Packages can be retrieved from the wiki, using require('Module:Foo'), or from files distributed with Scribunto, using require('foo'). The "Module:" prefix allows for source compatibility with existing Lua code. * Added a couple of libraries from LuaForge: luabit and stringtools. * Made fetchModuleFromParser() return null on error instead of throwing an exception, to more easily support the desired behaviour of the package loader, which needs to return null on error. * Renamed mw.setupEnvironment() to mw.setup() since it is setting up things other than the environment now. * In MWServer:handleRegisterLibrary(), remove the feature which interprets dots in library names, since LuaSandbox doesn't support this. Improved module isolation and related refactoring: * Expose restricted versions of getfenv() and setfenv() to user Lua code. Requires luasandbox r114952. * Don't cache the export list returned by module execution for later function calls. This breaks isolation of #invoke calls, since the local variables are persistent. * Removed ScribuntoFunctionBase and its children, since it doesn't really have a purpose if it can't cache anything. Instead, invoke functions using a module method called invoke(). * Removed Module::initialize(), replaced it with a validate() function. This is a more elegant interface and works better with the new module caching scheme. * Use a Status object for the return value of Engine::validate() instead of an array. Use the formatting facilities of the Status class. Other: * Removed "too many returns" error, doesn't fit in with Lua conventions. * Use the standalone engine by default, so that the extension will work without configuration for more people. * Added an accessor for $engine->interpreter * Fix mw.clone() to correctly clone metatables * If the standalone interpreter exits due to an error, there are some contexts where the initial error will be caught and ignored, and the user will see the error from checkValid() instead. In this case, rethrow the original error for a more informative message. * Load mw.lua into the initial standalone environment, to reduce code duplication between mw.lua and MWServer.lua. * Fixed a bug in Scribunto_LuaStandaloneInterpreter::handleCall() for functions that return no results. * Fixed a bug in encodeLuaVar() for strings with "\r". Added test case. * In MWServer.lua, don't call error() for internal errors, instead just print the error and exit. This avoids a protocol violation when an error is encountered from within handleCall(). * Added lots of documentation. Lua doc comments are in LuaDoc format. Change-Id: Ie2fd572c362bedf02f45d3fa5352a5280e034740
2012-04-18 03:46:18 +00:00
$this->checkString( 'loadPackage', $args, 0 );
# This is what Lua does for its built-in loaders
$luaName = str_replace( '.', '/', $name ) . '.lua';
$paths = $this->getLibraryPaths( 'lua', self::$libraryPaths );
foreach ( $paths as $path ) {
$fileName = $this->normalizeModuleFileName( "$path/$luaName" );
Added more Lua environment features Package library: * Added a simulation of the Lua 5.1 package library. * Removed mw.import(), replaced it with a package loader. Packages can be retrieved from the wiki, using require('Module:Foo'), or from files distributed with Scribunto, using require('foo'). The "Module:" prefix allows for source compatibility with existing Lua code. * Added a couple of libraries from LuaForge: luabit and stringtools. * Made fetchModuleFromParser() return null on error instead of throwing an exception, to more easily support the desired behaviour of the package loader, which needs to return null on error. * Renamed mw.setupEnvironment() to mw.setup() since it is setting up things other than the environment now. * In MWServer:handleRegisterLibrary(), remove the feature which interprets dots in library names, since LuaSandbox doesn't support this. Improved module isolation and related refactoring: * Expose restricted versions of getfenv() and setfenv() to user Lua code. Requires luasandbox r114952. * Don't cache the export list returned by module execution for later function calls. This breaks isolation of #invoke calls, since the local variables are persistent. * Removed ScribuntoFunctionBase and its children, since it doesn't really have a purpose if it can't cache anything. Instead, invoke functions using a module method called invoke(). * Removed Module::initialize(), replaced it with a validate() function. This is a more elegant interface and works better with the new module caching scheme. * Use a Status object for the return value of Engine::validate() instead of an array. Use the formatting facilities of the Status class. Other: * Removed "too many returns" error, doesn't fit in with Lua conventions. * Use the standalone engine by default, so that the extension will work without configuration for more people. * Added an accessor for $engine->interpreter * Fix mw.clone() to correctly clone metatables * If the standalone interpreter exits due to an error, there are some contexts where the initial error will be caught and ignored, and the user will see the error from checkValid() instead. In this case, rethrow the original error for a more informative message. * Load mw.lua into the initial standalone environment, to reduce code duplication between mw.lua and MWServer.lua. * Fixed a bug in Scribunto_LuaStandaloneInterpreter::handleCall() for functions that return no results. * Fixed a bug in encodeLuaVar() for strings with "\r". Added test case. * In MWServer.lua, don't call error() for internal errors, instead just print the error and exit. This avoids a protocol violation when an error is encountered from within handleCall(). * Added lots of documentation. Lua doc comments are in LuaDoc format. Change-Id: Ie2fd572c362bedf02f45d3fa5352a5280e034740
2012-04-18 03:46:18 +00:00
if ( !file_exists( $fileName ) ) {
continue;
}
$code = file_get_contents( $fileName );
$init = $this->interpreter->loadString( $code, "@$luaName" );
return [ $init ];
}
Added more Lua environment features Package library: * Added a simulation of the Lua 5.1 package library. * Removed mw.import(), replaced it with a package loader. Packages can be retrieved from the wiki, using require('Module:Foo'), or from files distributed with Scribunto, using require('foo'). The "Module:" prefix allows for source compatibility with existing Lua code. * Added a couple of libraries from LuaForge: luabit and stringtools. * Made fetchModuleFromParser() return null on error instead of throwing an exception, to more easily support the desired behaviour of the package loader, which needs to return null on error. * Renamed mw.setupEnvironment() to mw.setup() since it is setting up things other than the environment now. * In MWServer:handleRegisterLibrary(), remove the feature which interprets dots in library names, since LuaSandbox doesn't support this. Improved module isolation and related refactoring: * Expose restricted versions of getfenv() and setfenv() to user Lua code. Requires luasandbox r114952. * Don't cache the export list returned by module execution for later function calls. This breaks isolation of #invoke calls, since the local variables are persistent. * Removed ScribuntoFunctionBase and its children, since it doesn't really have a purpose if it can't cache anything. Instead, invoke functions using a module method called invoke(). * Removed Module::initialize(), replaced it with a validate() function. This is a more elegant interface and works better with the new module caching scheme. * Use a Status object for the return value of Engine::validate() instead of an array. Use the formatting facilities of the Status class. Other: * Removed "too many returns" error, doesn't fit in with Lua conventions. * Use the standalone engine by default, so that the extension will work without configuration for more people. * Added an accessor for $engine->interpreter * Fix mw.clone() to correctly clone metatables * If the standalone interpreter exits due to an error, there are some contexts where the initial error will be caught and ignored, and the user will see the error from checkValid() instead. In this case, rethrow the original error for a more informative message. * Load mw.lua into the initial standalone environment, to reduce code duplication between mw.lua and MWServer.lua. * Fixed a bug in Scribunto_LuaStandaloneInterpreter::handleCall() for functions that return no results. * Fixed a bug in encodeLuaVar() for strings with "\r". Added test case. * In MWServer.lua, don't call error() for internal errors, instead just print the error and exit. This avoids a protocol violation when an error is encountered from within handleCall(). * Added lots of documentation. Lua doc comments are in LuaDoc format. Change-Id: Ie2fd572c362bedf02f45d3fa5352a5280e034740
2012-04-18 03:46:18 +00:00
$title = Title::newFromText( $name );
if ( !$title || !$title->hasContentModel( CONTENT_MODEL_SCRIBUNTO ) ) {
return [];
Added more Lua environment features Package library: * Added a simulation of the Lua 5.1 package library. * Removed mw.import(), replaced it with a package loader. Packages can be retrieved from the wiki, using require('Module:Foo'), or from files distributed with Scribunto, using require('foo'). The "Module:" prefix allows for source compatibility with existing Lua code. * Added a couple of libraries from LuaForge: luabit and stringtools. * Made fetchModuleFromParser() return null on error instead of throwing an exception, to more easily support the desired behaviour of the package loader, which needs to return null on error. * Renamed mw.setupEnvironment() to mw.setup() since it is setting up things other than the environment now. * In MWServer:handleRegisterLibrary(), remove the feature which interprets dots in library names, since LuaSandbox doesn't support this. Improved module isolation and related refactoring: * Expose restricted versions of getfenv() and setfenv() to user Lua code. Requires luasandbox r114952. * Don't cache the export list returned by module execution for later function calls. This breaks isolation of #invoke calls, since the local variables are persistent. * Removed ScribuntoFunctionBase and its children, since it doesn't really have a purpose if it can't cache anything. Instead, invoke functions using a module method called invoke(). * Removed Module::initialize(), replaced it with a validate() function. This is a more elegant interface and works better with the new module caching scheme. * Use a Status object for the return value of Engine::validate() instead of an array. Use the formatting facilities of the Status class. Other: * Removed "too many returns" error, doesn't fit in with Lua conventions. * Use the standalone engine by default, so that the extension will work without configuration for more people. * Added an accessor for $engine->interpreter * Fix mw.clone() to correctly clone metatables * If the standalone interpreter exits due to an error, there are some contexts where the initial error will be caught and ignored, and the user will see the error from checkValid() instead. In this case, rethrow the original error for a more informative message. * Load mw.lua into the initial standalone environment, to reduce code duplication between mw.lua and MWServer.lua. * Fixed a bug in Scribunto_LuaStandaloneInterpreter::handleCall() for functions that return no results. * Fixed a bug in encodeLuaVar() for strings with "\r". Added test case. * In MWServer.lua, don't call error() for internal errors, instead just print the error and exit. This avoids a protocol violation when an error is encountered from within handleCall(). * Added lots of documentation. Lua doc comments are in LuaDoc format. Change-Id: Ie2fd572c362bedf02f45d3fa5352a5280e034740
2012-04-18 03:46:18 +00:00
}
$module = $this->fetchModuleFromParser( $title );
Added more Lua environment features Package library: * Added a simulation of the Lua 5.1 package library. * Removed mw.import(), replaced it with a package loader. Packages can be retrieved from the wiki, using require('Module:Foo'), or from files distributed with Scribunto, using require('foo'). The "Module:" prefix allows for source compatibility with existing Lua code. * Added a couple of libraries from LuaForge: luabit and stringtools. * Made fetchModuleFromParser() return null on error instead of throwing an exception, to more easily support the desired behaviour of the package loader, which needs to return null on error. * Renamed mw.setupEnvironment() to mw.setup() since it is setting up things other than the environment now. * In MWServer:handleRegisterLibrary(), remove the feature which interprets dots in library names, since LuaSandbox doesn't support this. Improved module isolation and related refactoring: * Expose restricted versions of getfenv() and setfenv() to user Lua code. Requires luasandbox r114952. * Don't cache the export list returned by module execution for later function calls. This breaks isolation of #invoke calls, since the local variables are persistent. * Removed ScribuntoFunctionBase and its children, since it doesn't really have a purpose if it can't cache anything. Instead, invoke functions using a module method called invoke(). * Removed Module::initialize(), replaced it with a validate() function. This is a more elegant interface and works better with the new module caching scheme. * Use a Status object for the return value of Engine::validate() instead of an array. Use the formatting facilities of the Status class. Other: * Removed "too many returns" error, doesn't fit in with Lua conventions. * Use the standalone engine by default, so that the extension will work without configuration for more people. * Added an accessor for $engine->interpreter * Fix mw.clone() to correctly clone metatables * If the standalone interpreter exits due to an error, there are some contexts where the initial error will be caught and ignored, and the user will see the error from checkValid() instead. In this case, rethrow the original error for a more informative message. * Load mw.lua into the initial standalone environment, to reduce code duplication between mw.lua and MWServer.lua. * Fixed a bug in Scribunto_LuaStandaloneInterpreter::handleCall() for functions that return no results. * Fixed a bug in encodeLuaVar() for strings with "\r". Added test case. * In MWServer.lua, don't call error() for internal errors, instead just print the error and exit. This avoids a protocol violation when an error is encountered from within handleCall(). * Added lots of documentation. Lua doc comments are in LuaDoc format. Change-Id: Ie2fd572c362bedf02f45d3fa5352a5280e034740
2012-04-18 03:46:18 +00:00
if ( $module ) {
// @phan-suppress-next-line PhanUndeclaredMethod
return [ $module->getInitChunk() ];
Added more Lua environment features Package library: * Added a simulation of the Lua 5.1 package library. * Removed mw.import(), replaced it with a package loader. Packages can be retrieved from the wiki, using require('Module:Foo'), or from files distributed with Scribunto, using require('foo'). The "Module:" prefix allows for source compatibility with existing Lua code. * Added a couple of libraries from LuaForge: luabit and stringtools. * Made fetchModuleFromParser() return null on error instead of throwing an exception, to more easily support the desired behaviour of the package loader, which needs to return null on error. * Renamed mw.setupEnvironment() to mw.setup() since it is setting up things other than the environment now. * In MWServer:handleRegisterLibrary(), remove the feature which interprets dots in library names, since LuaSandbox doesn't support this. Improved module isolation and related refactoring: * Expose restricted versions of getfenv() and setfenv() to user Lua code. Requires luasandbox r114952. * Don't cache the export list returned by module execution for later function calls. This breaks isolation of #invoke calls, since the local variables are persistent. * Removed ScribuntoFunctionBase and its children, since it doesn't really have a purpose if it can't cache anything. Instead, invoke functions using a module method called invoke(). * Removed Module::initialize(), replaced it with a validate() function. This is a more elegant interface and works better with the new module caching scheme. * Use a Status object for the return value of Engine::validate() instead of an array. Use the formatting facilities of the Status class. Other: * Removed "too many returns" error, doesn't fit in with Lua conventions. * Use the standalone engine by default, so that the extension will work without configuration for more people. * Added an accessor for $engine->interpreter * Fix mw.clone() to correctly clone metatables * If the standalone interpreter exits due to an error, there are some contexts where the initial error will be caught and ignored, and the user will see the error from checkValid() instead. In this case, rethrow the original error for a more informative message. * Load mw.lua into the initial standalone environment, to reduce code duplication between mw.lua and MWServer.lua. * Fixed a bug in Scribunto_LuaStandaloneInterpreter::handleCall() for functions that return no results. * Fixed a bug in encodeLuaVar() for strings with "\r". Added test case. * In MWServer.lua, don't call error() for internal errors, instead just print the error and exit. This avoids a protocol violation when an error is encountered from within handleCall(). * Added lots of documentation. Lua doc comments are in LuaDoc format. Change-Id: Ie2fd572c362bedf02f45d3fa5352a5280e034740
2012-04-18 03:46:18 +00:00
} else {
return [];
Added more Lua environment features Package library: * Added a simulation of the Lua 5.1 package library. * Removed mw.import(), replaced it with a package loader. Packages can be retrieved from the wiki, using require('Module:Foo'), or from files distributed with Scribunto, using require('foo'). The "Module:" prefix allows for source compatibility with existing Lua code. * Added a couple of libraries from LuaForge: luabit and stringtools. * Made fetchModuleFromParser() return null on error instead of throwing an exception, to more easily support the desired behaviour of the package loader, which needs to return null on error. * Renamed mw.setupEnvironment() to mw.setup() since it is setting up things other than the environment now. * In MWServer:handleRegisterLibrary(), remove the feature which interprets dots in library names, since LuaSandbox doesn't support this. Improved module isolation and related refactoring: * Expose restricted versions of getfenv() and setfenv() to user Lua code. Requires luasandbox r114952. * Don't cache the export list returned by module execution for later function calls. This breaks isolation of #invoke calls, since the local variables are persistent. * Removed ScribuntoFunctionBase and its children, since it doesn't really have a purpose if it can't cache anything. Instead, invoke functions using a module method called invoke(). * Removed Module::initialize(), replaced it with a validate() function. This is a more elegant interface and works better with the new module caching scheme. * Use a Status object for the return value of Engine::validate() instead of an array. Use the formatting facilities of the Status class. Other: * Removed "too many returns" error, doesn't fit in with Lua conventions. * Use the standalone engine by default, so that the extension will work without configuration for more people. * Added an accessor for $engine->interpreter * Fix mw.clone() to correctly clone metatables * If the standalone interpreter exits due to an error, there are some contexts where the initial error will be caught and ignored, and the user will see the error from checkValid() instead. In this case, rethrow the original error for a more informative message. * Load mw.lua into the initial standalone environment, to reduce code duplication between mw.lua and MWServer.lua. * Fixed a bug in Scribunto_LuaStandaloneInterpreter::handleCall() for functions that return no results. * Fixed a bug in encodeLuaVar() for strings with "\r". Added test case. * In MWServer.lua, don't call error() for internal errors, instead just print the error and exit. This avoids a protocol violation when an error is encountered from within handleCall(). * Added lots of documentation. Lua doc comments are in LuaDoc format. Change-Id: Ie2fd572c362bedf02f45d3fa5352a5280e034740
2012-04-18 03:46:18 +00:00
}
}
2012-05-22 03:56:07 +00:00
/**
* Helper function for the implementation of frame methods
*
* @param string $frameId
* @return PPFrame
*
* @throws LuaError
2012-05-22 03:56:07 +00:00
*/
protected function getFrameById( $frameId ) {
if ( $frameId === 'empty' ) {
return $this->getParser()->getPreprocessor()->newFrame();
} elseif ( isset( $this->currentFrames[$frameId] ) ) {
// @phan-suppress-next-line PhanTypeMismatchReturnNullable False positive
return $this->currentFrames[$frameId];
2012-05-22 03:56:07 +00:00
} else {
throw new LuaError( 'invalid frame ID' );
2012-05-22 03:56:07 +00:00
}
}
/**
* Handler for frameExists()
*
* @internal
* @param string $frameId
* @return array
*/
public function frameExists( $frameId ) {
return [ $frameId === 'empty' || isset( $this->currentFrames[$frameId] ) ];
}
/**
* Handler for newChildFrame()
*
* @internal
* @param string $frameId
* @param string $title
* @param array $args
* @return array
* @throws LuaError
2012-05-22 03:56:07 +00:00
*/
public function newChildFrame( $frameId, $title, array $args ) {
if ( count( $this->currentFrames ) > 100 ) {
throw new LuaError( 'newChild: too many frames' );
}
$frame = $this->getFrameById( $frameId );
if ( $title === false ) {
$title = $frame->getTitle();
} else {
$title = Title::newFromText( $title );
if ( !$title ) {
throw new LuaError( 'newChild: invalid title' );
}
}
$args = $this->getParser()->getPreprocessor()->newPartNodeArray( $args );
$newFrame = $frame->newChild( $args, $title );
$newFrameId = 'frame' . count( $this->currentFrames );
$this->currentFrames[$newFrameId] = $newFrame;
return [ $newFrameId ];
2012-05-22 03:56:07 +00:00
}
/**
* Handler for getTitle()
*
* @internal
* @param string $frameId
*
* @return array
*/
public function getFrameTitle( $frameId ) {
$frame = $this->getFrameById( $frameId );
return [ $frame->getTitle()->getPrefixedText() ];
}
/**
* Handler for setTTL()
* @internal
* @param int $ttl
*/
public function setTTL( $ttl ) {
$args = func_get_args();
$this->checkNumber( 'setTTL', $args, 0 );
$frame = $this->getFrameById( 'current' );
$frame->setTTL( $ttl );
}
2012-05-22 03:56:07 +00:00
/**
* Handler for getExpandedArgument()
* @internal
* @param string $frameId
* @param string $name
* @return array
2012-05-22 03:56:07 +00:00
*/
public function getExpandedArgument( $frameId, $name ) {
2012-05-22 03:56:07 +00:00
$args = func_get_args();
$this->checkString( 'getExpandedArgument', $args, 0 );
$frame = $this->getFrameById( $frameId );
$this->getInterpreter()->pauseUsageTimer();
2012-05-22 03:56:07 +00:00
$result = $frame->getArgument( $name );
if ( $result === false ) {
return [];
2012-05-22 03:56:07 +00:00
} else {
return [ $result ];
2012-05-22 03:56:07 +00:00
}
}
/**
* Handler for getAllExpandedArguments()
* @internal
* @param string $frameId
* @return array
2012-05-22 03:56:07 +00:00
*/
public function getAllExpandedArguments( $frameId ) {
2012-05-22 03:56:07 +00:00
$frame = $this->getFrameById( $frameId );
$this->getInterpreter()->pauseUsageTimer();
return [ $frame->getArguments() ];
2012-05-22 03:56:07 +00:00
}
/**
* Handler for expandTemplate()
* @internal
* @param string $frameId
* @param string $titleText
* @param array $args
* @return array
* @throws LuaError
2012-05-22 03:56:07 +00:00
*/
public function expandTemplate( $frameId, $titleText, $args ) {
2012-05-22 03:56:07 +00:00
$frame = $this->getFrameById( $frameId );
$title = Title::newFromText( $titleText, NS_TEMPLATE );
if ( !$title ) {
throw new LuaError( "expandTemplate: invalid title \"$titleText\"" );
2012-05-22 03:56:07 +00:00
}
if ( $frame->depth >= $this->parser->getOptions()->getMaxTemplateDepth() ) {
throw new LuaError( 'expandTemplate: template depth limit exceeded' );
2012-05-22 03:56:07 +00:00
}
if ( MediaWikiServices::getInstance()->getNamespaceInfo()->isNonincludable( $title->getNamespace() ) ) {
throw new LuaError( 'expandTemplate: template inclusion denied' );
2012-05-22 03:56:07 +00:00
}
[ $dom, $finalTitle ] = $this->parser->getTemplateDom( $title );
2012-05-22 03:56:07 +00:00
if ( $dom === false ) {
throw new LuaError( "expandTemplate: template \"$titleText\" does not exist" );
2012-05-22 03:56:07 +00:00
}
if ( !$frame->loopCheck( $finalTitle ) ) {
throw new LuaError( 'expandTemplate: template loop detected' );
2012-05-22 03:56:07 +00:00
}
$fargs = $this->getParser()->getPreprocessor()->newPartNodeArray( $args );
$newFrame = $frame->newChild( $fargs, $finalTitle );
$text = $this->doCachedExpansion( $newFrame, $dom,
[
'frameId' => $frameId,
2012-05-22 03:56:07 +00:00
'template' => $finalTitle->getPrefixedDBkey(),
'args' => $args
] );
return [ $text ];
2012-05-22 03:56:07 +00:00
}
/**
* Handler for callParserFunction()
* @internal
* @param string $frameId
* @param string $function
* @param array $args
* @throws LuaError
* @return array
* @suppress PhanImpossibleCondition
*/
public function callParserFunction( $frameId, $function, $args ) {
$frame = $this->getFrameById( $frameId );
# Make zero-based, without screwing up named args
$args = array_merge( [], $args );
# Sort, since we can't rely on the order coming in from Lua
uksort( $args, static function ( $a, $b ) {
if ( is_int( $a ) !== is_int( $b ) ) {
return is_int( $a ) ? -1 : 1;
}
if ( is_int( $a ) ) {
return $a - $b;
}
return strcmp( $a, $b );
} );
# Be user-friendly
$colonPos = strpos( $function, ':' );
if ( $colonPos !== false ) {
array_unshift( $args, trim( substr( $function, $colonPos + 1 ) ) );
$function = substr( $function, 0, $colonPos );
}
if ( !isset( $args[0] ) ) {
# It's impossible to call a parser function from wikitext without
# supplying an arg 0. Insist that one be provided via Lua, too.
throw new LuaError( 'callParserFunction: At least one unnamed parameter ' .
'(the parameter that comes after the colon in wikitext) ' .
'must be provided'
);
}
$result = $this->parser->callParserFunction( $frame, $function, $args );
if ( !$result['found'] ) {
throw new LuaError( "callParserFunction: function \"$function\" was not found" );
}
# Set defaults for various flags
$result += [
'nowiki' => false,
'isChildObj' => false,
'isLocalObj' => false,
'isHTML' => false,
'title' => false,
];
$text = $result['text'];
if ( $result['isChildObj'] ) {
$fargs = $this->getParser()->getPreprocessor()->newPartNodeArray( $args );
$newFrame = $frame->newChild( $fargs, $result['title'] );
if ( $result['nowiki'] ) {
$text = $newFrame->expand( $text, PPFrame::RECOVER_ORIG );
} else {
$text = $newFrame->expand( $text );
}
}
if ( $result['isLocalObj'] && $result['nowiki'] ) {
$text = $frame->expand( $text, PPFrame::RECOVER_ORIG );
$result['isLocalObj'] = false;
}
# Replace raw HTML by a placeholder
if ( $result['isHTML'] ) {
$text = $this->parser->insertStripItem( $text );
} elseif ( $result['nowiki'] ) {
# Escape nowiki-style return values
$text = wfEscapeWikiText( $text );
}
if ( $result['isLocalObj'] ) {
$text = $frame->expand( $text );
}
return [ "$text" ];
}
2012-05-22 03:56:07 +00:00
/**
* Handler for preprocess()
* @internal
* @param string $frameId
* @param string $text
* @return array
* @throws LuaError
2012-05-22 03:56:07 +00:00
*/
public function preprocess( $frameId, $text ) {
2012-05-22 03:56:07 +00:00
$args = func_get_args();
$this->checkString( 'preprocess', $args, 0 );
$frame = $this->getFrameById( $frameId );
if ( !$frame ) {
throw new LuaError( 'attempt to call mw.preprocess with no frame' );
2012-05-22 03:56:07 +00:00
}
// Don't count the time for expanding all the frame arguments against
// the Lua time limit.
$this->getInterpreter()->pauseUsageTimer();
$frame->getArguments();
$this->getInterpreter()->unpauseUsageTimer();
$text = $this->doCachedExpansion( $frame, $text,
[
'frameId' => $frameId,
'inputText' => $text
] );
return [ $text ];
2012-05-22 03:56:07 +00:00
}
/**
* Increment the expensive function count, and throw if limit exceeded
*
* @internal
* @throws LuaError
* @return null
*/
public function incrementExpensiveFunctionCount() {
if ( !$this->getParser()->incrementExpensiveFunctionCount() ) {
throw new LuaError( "too many expensive function calls" );
}
return null;
}
/**
* Adds a warning to be displayed upon preview
*
* @internal
* @param string $text wikitext
*/
public function addWarning( $text ) {
$args = func_get_args();
$this->checkString( 'addWarning', $args, 0 );
// Message localization has to happen on the Lua side
$this->getParser()->getOutput()->addWarningMsg(
'scribunto-lua-warning',
$text
);
}
/**
* Return whether the parser is currently substing
*
* @internal
* @return array
*/
public function isSubsting() {
// See Parser::braceSubstitution, OT_WIKI is the switch
return [ $this->getParser()->getOutputType() === Parser::OT_WIKI ];
}
/**
* @param PPFrame $frame
* @param string|array $input
* @param mixed $cacheKey
* @return string
*/
private function doCachedExpansion( $frame, $input, $cacheKey ) {
2012-05-22 03:56:07 +00:00
$hash = md5( serialize( $cacheKey ) );
if ( isset( $this->expandCache[$hash] ) ) {
return $this->expandCache[$hash];
}
if ( is_scalar( $input ) ) {
$input = str_replace( [ "\r\n", "\r" ], "\n", $input );
$dom = $this->parser->getPreprocessor()->preprocessToObj(
$input, $frame->depth ? Parser::PTD_FOR_INCLUSION : 0 );
} else {
$dom = $input;
}
$ret = $frame->expand( $dom );
if ( !$frame->isVolatile() ) {
2012-05-22 03:56:07 +00:00
if ( count( $this->expandCache ) > self::MAX_EXPAND_CACHE_SIZE ) {
reset( $this->expandCache );
$oldHash = key( $this->expandCache );
unset( $this->expandCache[$oldHash] );
}
$this->expandCache[$hash] = $ret;
2012-05-22 03:56:07 +00:00
}
return $ret;
2012-05-22 03:56:07 +00:00
}
/**
* Implements mw.loadJsonData()
*
* @param string $title Title text, type-checked in Lua
* @return string[]
*/
public function loadJsonData( $title ) {
$this->incrementExpensiveFunctionCount();
$titleObj = Title::newFromText( $title );
if ( !$titleObj || !$titleObj->exists() || !$titleObj->hasContentModel( CONTENT_MODEL_JSON ) ) {
throw new LuaError(
"bad argument #1 to 'mw.loadJsonData' ('$title' is not a valid JSON page)"
);
}
$parser = $this->getParser();
[ $text, $finalTitle ] = $parser->fetchTemplateAndTitle( $titleObj );
$json = FormatJson::decode( $text, true );
if ( is_array( $json ) ) {
$json = TextLibrary::reindexArrays( $json, false );
}
// We'll throw an error for non-tables on the Lua side
return [ $json ];
}
/**
* @see Content::updateRedirect
*
* @param ScribuntoContent $content
* @param Title $target
* @return ScribuntoContent
*/
public function updateRedirect( ScribuntoContent $content, Title $target ): ScribuntoContent {
if ( !$content->isRedirect() ) {
return $content;
}
return $this->makeRedirectContent( $target );
}
/**
* @see Content::getRedirectTarget
*
* @param ScribuntoContent $content
* @return Title|null
*/
public function getRedirectTarget( ScribuntoContent $content ) {
$text = $content->getText();
preg_match( '/^return require \[\[(.*?)\]\]/', $text, $matches );
if ( isset( $matches[1] ) ) {
$title = Title::newFromText( $matches[1] );
// Can only redirect to other Scribunto pages
if ( $title && $title->hasContentModel( CONTENT_MODEL_SCRIBUNTO ) ) {
// Have a title, check that the current content equals what
// the redirect content should be
if ( $content->equals( $this->makeRedirectContent( $title ) ) ) {
return $title;
}
}
}
return null;
}
/**
* @see ContentHandler::makeRedirectContent
* @param Title $destination
* @param string $text
* @return ScribuntoContent
*/
public function makeRedirectContent( Title $destination, $text = '' ) {
$targetPage = $destination->getPrefixedText();
$redirectText = "return require [[$targetPage]]";
return new ScribuntoContent( $redirectText );
}
/**
* @see ContentHandler::supportsRedirects
* @return bool true
*/
public function supportsRedirects(): bool {
return true;
}
}
class_alias( LuaEngine::class, 'Scribunto_LuaEngine' );