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

Reply via email to