Tim Starling has submitted this change and it was merged.
Change subject: Make mw.getCurrentFrame() work in console, add frame:newChild()
......................................................................
Make mw.getCurrentFrame() work in console, add frame:newChild()
It would be helpful for debugging if a frame object could be gotten in
the console. To that end, add an empty frame when running in the console
and allow it to be returned by mw.getCurrentFrame().
It would also be helpful to be able to create frames with arbitrary
arguments, again for testing. Fortunately support for creating child
frames with arbitrary arguments already exists in core, so we can just
use it.
And for good measure, be sure to restore the $engine->currentFrames
array even if the Lua code throws an exception.
Change-Id: I1dc8602d63af75424f267c42a3743fabbc1827f7
---
M engines/LuaCommon/LuaCommon.php
M engines/LuaCommon/lualib/mw.lua
M tests/engines/LuaCommon/CommonTest.php
3 files changed, 179 insertions(+), 82 deletions(-)
Approvals:
Tim Starling: Verified; Looks good to me, approved
jenkins-bot: Checked
diff --git a/engines/LuaCommon/LuaCommon.php b/engines/LuaCommon/LuaCommon.php
index e38abf8..fad0e23 100644
--- a/engines/LuaCommon/LuaCommon.php
+++ b/engines/LuaCommon/LuaCommon.php
@@ -28,7 +28,7 @@
protected $loaded = false;
protected $executeModuleFunc, $interpreter;
protected $mw;
- protected $currentFrame = false;
+ protected $currentFrames = array();
protected $expandCache = array();
protected $loadedLibraries = array();
@@ -71,7 +71,8 @@
$funcs = array(
'loadPackage',
- 'parentFrameExists',
+ 'frameExists',
+ 'newChildFrame',
'getExpandedArgument',
'getAllExpandedArguments',
'expandTemplate',
@@ -169,12 +170,20 @@
* Execute a module function chunk
*/
public function executeFunctionChunk( $chunk, $frame ) {
- $oldFrame = $this->currentFrame;
- $this->currentFrame = $frame;
- $result = $this->getInterpreter()->callFunction(
- $this->mw['executeFunction'],
- $chunk );
- $this->currentFrame = $oldFrame;
+ $oldFrames = $this->currentFrames;
+ $this->currentFrames = array(
+ 'current' => $frame,
+ 'parent' => isset( $frame->parent ) ? $frame->parent :
null,
+ );
+ try {
+ $result = $this->getInterpreter()->callFunction(
+ $this->mw['executeFunction'],
+ $chunk );
+ } catch ( Exception $ex ) {
+ $this->currentFrames = $oldFrames;
+ throw $ex;
+ }
+ $this->currentFrames = $oldFrames;
return $result;
}
@@ -203,47 +212,59 @@
}
public function runConsole( $params ) {
- /**
- * 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)\n" .
- "local p = mw.executeModule(__init)\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 .= "return mw.allToString(" . substr(
$params['question'], 1 ) . "), mw.getLogBuffer()\n";
- } else {
- $code .= $params['question'] . "\n" .
- "return nil, mw.getLogBuffer()\n";
- }
- $code .= "end\n";
-
- $contentModule = $this->newModule(
- $params['content'],
$params['title']->getPrefixedDBkey() );
- $contentInit = $contentModule->getInitChunk();
-
- $consoleModule = $this->newModule(
- $code,
- wfMessage( 'scribunto-console-current-src' )->text()
+ $oldFrames = $this->currentFrames;
+ $this->currentFrames = array(
+ 'current' =>
$this->getParser()->getPreprocessor()->newFrame(),
);
- $consoleInit = $consoleModule->getInitChunk();
- $ret = $this->executeModule( $consoleInit );
- $func = $ret[0];
- $ret = $this->getInterpreter()->callFunction( $func,
$contentInit );
+
+ try {
+ /**
+ * 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)\n" .
+ "local p = mw.executeModule(__init)\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 .= "return mw.allToString(" . substr(
$params['question'], 1 ) . "), mw.getLogBuffer()\n";
+ } else {
+ $code .= $params['question'] . "\n" .
+ "return nil, mw.getLogBuffer()\n";
+ }
+ $code .= "end\n";
+
+ $contentModule = $this->newModule(
+ $params['content'],
$params['title']->getPrefixedDBkey() );
+ $contentInit = $contentModule->getInitChunk();
+
+ $consoleModule = $this->newModule(
+ $code,
+ wfMessage( 'scribunto-console-current-src'
)->text()
+ );
+ $consoleInit = $consoleModule->getInitChunk();
+ $ret = $this->executeModule( $consoleInit );
+ $func = $ret[0];
+ $ret = $this->getInterpreter()->callFunction( $func,
$contentInit );
+ } catch ( Exception $ex ) {
+ $this->currentFrames = $oldFrames;
+ throw $ex;
+ }
+
+ $this->currentFrames = $oldFrames;
return array(
'return' => isset( $ret[0] ) ? $ret[0] : null,
'print' => isset( $ret[1] ) ? $ret[1] : '',
@@ -328,28 +349,42 @@
* Helper function for the implementation of frame methods
*/
protected function getFrameById( $frameId ) {
- if ( !$this->currentFrame ) {
- return false;
- }
- if ( $frameId === 'parent' ) {
- if ( !isset( $this->currentFrame->parent ) ) {
- return false;
- } else {
- return $this->currentFrame->parent;
- }
- } elseif ( $frameId === 'current' ) {
- return $this->currentFrame;
+ if ( isset( $this->currentFrames[$frameId] ) ) {
+ return $this->currentFrames[$frameId];
} else {
throw new Scribunto_LuaError( 'invalid frame ID' );
}
}
/**
- * Handler for parentFrameExists()
+ * Handler for frameExists()
*/
- function parentFrameExists() {
- $frame = $this->getFrameById( 'parent' );
- return array( $frame !== false );
+ function frameExists( $frameId ) {
+ return array( isset( $this->currentFrames[$frameId] ) );
+ }
+
+ /**
+ * Handler for newChildFrame()
+ */
+ function newChildFrame( $frameId, $title, $args ) {
+ if ( count( $this->currentFrames ) > 100 ) {
+ throw new Scribunto_LuaError( 'newChild: too many
frames' );
+ }
+
+ $frame = $this->getFrameById( $frameId );
+ if ( $title === false ) {
+ $title = $frame->getTitle();
+ } else {
+ $title = Title::newFromText( $title );
+ if ( !$title ) {
+ throw new Scribunto_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 array( $newFrameId );
}
/**
@@ -360,9 +395,6 @@
$this->checkString( 'getExpandedArgument', $args, 0 );
$frame = $this->getFrameById( $frameId );
- if ( $frame === false ) {
- return array();
- }
$this->getInterpreter()->pauseUsageTimer();
$result = $frame->getArgument( $name );
if ( $result === false ) {
@@ -377,9 +409,6 @@
*/
function getAllExpandedArguments( $frameId ) {
$frame = $this->getFrameById( $frameId );
- if ( $frame === false ) {
- return array();
- }
$this->getInterpreter()->pauseUsageTimer();
return array( $frame->getArguments() );
}
@@ -389,10 +418,6 @@
*/
function expandTemplate( $frameId, $titleText, $args ) {
$frame = $this->getFrameById( $frameId );
- if ( $frame === false ) {
- throw new Scribunto_LuaError( 'attempt to call
mw.expandTemplate with no frame' );
- }
-
$title = Title::newFromText( $titleText, NS_TEMPLATE );
if ( !$title ) {
throw new Scribunto_LuaError( 'expandTemplate: invalid
title' );
diff --git a/engines/LuaCommon/lualib/mw.lua b/engines/LuaCommon/lualib/mw.lua
index c2afeb6..593d836 100644
--- a/engines/LuaCommon/lualib/mw.lua
+++ b/engines/LuaCommon/lualib/mw.lua
@@ -226,8 +226,13 @@
return my_setfenv, my_getfenv
end
-local function newFrame( frameId )
+local function newFrame( frameId, ... )
+ if not php.frameExists( frameId ) then
+ return nil
+ end
+
local frame = {}
+ local parentFrameIds = { ... }
local argCache = {}
local argNames
local args_mt = {}
@@ -328,13 +333,46 @@
function frame:getParent()
checkSelf( self, 'getParent' )
- if frameId == 'parent' then
- return nil
- elseif php.parentFrameExists() then
- return newFrame( 'parent' )
- else
- return nil
+ return newFrame( unpack( parentFrameIds ) )
+ end
+
+ function frame:newChild( opt )
+ checkSelf( self, 'newChild' )
+
+ if type( opt ) ~= 'table' then
+ error( "frame:newChild: the first parameter must be a
table", 2 )
end
+
+ local title, args
+ if opt.title == nil then
+ title = false
+ else
+ title = tostring( opt.title )
+ end
+ if opt.args == nil then
+ args = {}
+ elseif type( opt.args ) ~= 'table' then
+ error( "frame:newChild: args must be a table", 2 )
+ else
+ args = {}
+ for k, v in pairs( opt.args ) do
+ local tp = type( k )
+ if tp ~= 'string' and tp ~= 'number' then
+ error( "frame:newChild: arg keys must
be strings or numbers, " .. tp .. " given", 2 )
+ end
+ local tp = type( v )
+ if tp == 'boolean' then
+ args[k] = v and '1' or ''
+ elseif tp == 'string' or tp == 'number' then
+ args[k] = tostring( v )
+ else
+ error( "frame:newChild: invalid type "
.. tp .. " for arg '" .. k .. "'", 2 )
+ end
+ end
+ end
+
+ local newFrameId = php.newChildFrame( frameId, title, args )
+ return newFrame( newFrameId, frameId, unpack( parentFrameIds ) )
end
function frame:expandTemplate( opt )
@@ -354,7 +392,7 @@
if opt.args == nil then
args = {}
elseif type( opt.args ) ~= 'table' then
- error( "frame:expandTitle: args must be a table" )
+ error( "frame:expandTemplate: args must be a table" )
else
args = opt.args
end
@@ -418,7 +456,7 @@
end
function mw.executeFunction( chunk )
- local frame = newFrame( 'current' )
+ local frame = newFrame( 'current', 'parent' )
local oldFrame = currentFrame
currentFrame = frame
@@ -453,6 +491,9 @@
end
function mw.getCurrentFrame()
+ if not currentFrame then
+ currentFrame = newFrame( 'current', 'parent' )
+ end
return currentFrame
end
diff --git a/tests/engines/LuaCommon/CommonTest.php
b/tests/engines/LuaCommon/CommonTest.php
index 0adbe95..2b8369a 100644
--- a/tests/engines/LuaCommon/CommonTest.php
+++ b/tests/engines/LuaCommon/CommonTest.php
@@ -202,4 +202,35 @@
'data module was stored in top level\'s package.loaded'
);
}
+
+ function testFrames() {
+ $engine = $this->getEngine();
+
+ $ret = $engine->runConsole( array(
+ 'prevQuestions' => array(),
+ 'question' => '=mw.getCurrentFrame()',
+ 'content' => 'return {}',
+ 'title' => Title::makeTitle( NS_MODULE, 'dummy' ),
+ ) );
+ $this->assertSame( 'table', $ret['return'], 'frames can be used
in the console' );
+
+ $ret = $engine->runConsole( array(
+ 'prevQuestions' => array(),
+ 'question' => '=mw.getCurrentFrame():newChild{}',
+ 'content' => 'return {}',
+ 'title' => Title::makeTitle( NS_MODULE, 'dummy' ),
+ ) );
+ $this->assertSame( 'table', $ret['return'], 'child frames can
be created' );
+
+ $ret = $engine->runConsole( array(
+ 'prevQuestions' => array(
+ 'f = mw.getCurrentFrame():newChild{ args = {
"ok" } }',
+ 'f2 = f:newChild{ args = {} }'
+ ),
+ 'question' => '=f2:getParent().args[1],
f2:getParent():getParent()',
+ 'content' => 'return {}',
+ 'title' => Title::makeTitle( NS_MODULE, 'dummy' ),
+ ) );
+ $this->assertSame( "ok\ttable", $ret['return'], 'child frames
have correct parents' );
+ }
}
--
To view, visit https://gerrit.wikimedia.org/r/49801
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I1dc8602d63af75424f267c42a3743fabbc1827f7
Gerrit-PatchSet: 5
Gerrit-Project: mediawiki/extensions/Scribunto
Gerrit-Branch: master
Gerrit-Owner: Anomie <[email protected]>
Gerrit-Reviewer: Aaron Schulz <[email protected]>
Gerrit-Reviewer: Anomie <[email protected]>
Gerrit-Reviewer: Demon <[email protected]>
Gerrit-Reviewer: Tim Starling <[email protected]>
Gerrit-Reviewer: jenkins-bot
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits