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 <bjor...@wikimedia.org> Gerrit-Reviewer: Aaron Schulz <asch...@wikimedia.org> Gerrit-Reviewer: Anomie <bjor...@wikimedia.org> Gerrit-Reviewer: Demon <ch...@wikimedia.org> Gerrit-Reviewer: Tim Starling <tstarl...@wikimedia.org> Gerrit-Reviewer: jenkins-bot _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits