diff --git a/engines/LuaStandalone/LuaStandaloneEngine.php b/engines/LuaStandalone/LuaStandaloneEngine.php index f694cc20..ecec1762 100644 --- a/engines/LuaStandalone/LuaStandaloneEngine.php +++ b/engines/LuaStandalone/LuaStandaloneEngine.php @@ -174,6 +174,7 @@ class Scribunto_LuaStandaloneInterpreter extends Scribunto_LuaInterpreter { $result = $this->dispatch( array( 'op' => 'call', 'id' => $func->id, + 'nargs' => count( $args ), 'args' => $args ) ); // Convert return values to zero-based return array_values( $result ); @@ -212,7 +213,23 @@ class Scribunto_LuaStandaloneInterpreter extends Scribunto_LuaInterpreter { return $result[1]; } + /** + * Fill in missing nulls in a list received from Lua + * + * @param $array array List received from Lua + * @param $count integer Number of values that should be in the list + * @return array Non-sparse array + */ + private static function fixNulls( array $array, $count ) { + if ( count( $array ) === $count ) { + return $array; + } else { + return array_replace( array_fill( 1, $count, null ), $array ); + } + } + protected function handleCall( $message ) { + $message['args'] = self::fixNulls( $message['args'], $message['nargs'] ); try { $result = $this->callback( $message['id'], $message['args'] ); } catch ( Scribunto_LuaError $e ) { @@ -230,6 +247,7 @@ class Scribunto_LuaStandaloneInterpreter extends Scribunto_LuaInterpreter { return array( 'op' => 'return', + 'nvalues' => count( $result ), 'values' => $result ); } @@ -258,7 +276,7 @@ class Scribunto_LuaStandaloneInterpreter extends Scribunto_LuaInterpreter { switch ( $msgFromLua['op'] ) { case 'return': - return $msgFromLua['values']; + return self::fixNulls( $msgFromLua['values'], $msgFromLua['nvalues'] ); case 'call': $msgToLua = $this->handleCall( $msgFromLua ); $this->sendMessage( $msgToLua ); diff --git a/engines/LuaStandalone/MWServer.lua b/engines/LuaStandalone/MWServer.lua index d0199c38..962bd36b 100644 --- a/engines/LuaStandalone/MWServer.lua +++ b/engines/LuaStandalone/MWServer.lua @@ -34,21 +34,28 @@ function MWServer:execute() self:debug( 'MWServer:execute: returning' ) end +-- Convert a multiple-return-value or a ... into a count and a table +function MWServer:listToCountAndTable( ... ) + return select( '#', ... ), { ... } +end + --- Call a PHP function -- Raise an error if the PHP handler requests it. May return any number -- of values. -- -- @param id The function ID, specified by a registerLibrary message +-- @param nargs Count of function arguments -- @param args The function arguments -- @return The return values from the PHP function -function MWServer:call( id, args ) +function MWServer:call( id, nargs, args ) local result = self:dispatch( { op = 'call', id = id, + nargs = nargs, args = args } ) if result.op == 'return' then - return unpack( result.values ) + return unpack( result.values, 1, result.nvalues ) elseif result.op == 'error' then -- Raise an error in the actual user code that called the function -- The level is 3 since our immediate caller is a closure @@ -63,19 +70,22 @@ end -- @param message The message from PHP -- @return A response message to send back to PHP function MWServer:handleCall( message ) - local result = { xpcall( + local n, result = self:listToCountAndTable( xpcall( function () - return self.chunks[message.id]( unpack( message.args ) ) + return self.chunks[message.id]( unpack( message.args, 1, message.nargs ) ) end, function ( err ) return MWServer:attachTrace( err ) - end - ) } - + end + ) ) + if result[1] then - table.remove( result, 1 ) + -- table.remove( result, 1 ) renumbers from 2 to #result. But #result + -- is not necessarily "right" if result contains nils. + result = { unpack( result, 2, n ) } return { op = 'return', + nvalues = n - 1, values = result } else @@ -124,6 +134,7 @@ function MWServer:handleLoadString( message ) local id = self:addChunk( chunk ) return { op = 'return', + nvalues = 1, values = {id} } else @@ -161,14 +172,15 @@ function MWServer:handleRegisterLibrary( message ) for name, id in pairs( message.functions ) do t[name] = function( ... ) - return self:call( id, { ... } ) + return self:call( id, self:listToCountAndTable( ... ) ) end -- Protect the function against setfenv() self.protectedFunctions[t[name]] = true end - + return { op = 'return', + nvalues = 0, values = {} } end @@ -181,13 +193,14 @@ end function MWServer:handleWrapPhpFunction( message ) local id = message.id local func = function( ... ) - return self:call( id, { ... } ) + return self:call( id, self:listToCountAndTable( ... ) ) end -- Protect the function against setfenv() self.protectedFunctions[func] = true return { op = 'return', + nvalues = 1, values = { func } } end @@ -199,6 +212,7 @@ end function MWServer:handleGetStatus( message ) local nullRet = { op = 'return', + nvalues = 0, values = {} } local file = io.open( '/proc/self/stat' ) @@ -216,6 +230,7 @@ function MWServer:handleGetStatus( message ) end return { op = 'return', + nvalues = 1, values = {{ pid = tonumber(t[1]), time = tonumber(t[14]) + tonumber(t[15]) + tonumber(t[16]) + tonumber(t[17]), diff --git a/engines/LuaStandalone/protocol.txt b/engines/LuaStandalone/protocol.txt index edaf2249..157383d5 100644 --- a/engines/LuaStandalone/protocol.txt +++ b/engines/LuaStandalone/protocol.txt @@ -22,7 +22,8 @@ message. In this way, a stack of pending requests can be accumulated. This mechanism allows re-entrant and recursive calls. All numerically-indexed arrays should start from index 1 unless otherwise -specified. +specified. Note that the number of values in an array may not match what Lua's +'#' operator returns if the array contains nils. == Request messages sent from PHP to Lua == @@ -38,6 +39,7 @@ Message parameters: On success, the response message is: * op: "return" +* nvalues: 1 * values: An array with a single element with the ID in it On failure, the response message is: @@ -52,11 +54,13 @@ Call a Lua function. Message parameters: * op: "call" * id: The chunk ID +* nargs: Number of arguments, including nils * args: The argument array On success, the response message is: * op: "return" +* nvalues: Number of return values, including nils * values: All return values as an array On failure, the response message is: @@ -81,6 +85,7 @@ Message parameters: On success, the response message is: * op: "return" +* nvalues: 0 * values: An empty array On failure the response message is: @@ -98,6 +103,7 @@ Message parameters: On success, the response message is: * op: "return" +* nvalues: 1 * values: An array with a single element, which is an associative array mapping status key to value. The status keys are: ** pid: The process identifier @@ -108,6 +114,7 @@ On success, the response message is: On failure, the response message is: * op: "return" +* nvalues: 0 * values: An empty array === quit === @@ -128,11 +135,13 @@ Call a PHP function. Message parameters: * op: "call" * id: The function ID given by registerLibrary +* nargs: Number of arguments, including nils * args: An array giving the function arguments On success, the response message is: * op: "return" +* nvalues: Number of return values, including nils * values: All return values as an array On failure the response message is: diff --git a/tests/engines/LuaCommon/LuaInterpreterTest.php b/tests/engines/LuaCommon/LuaInterpreterTest.php index 5c831c79..4a532ba4 100644 --- a/tests/engines/LuaCommon/LuaInterpreterTest.php +++ b/tests/engines/LuaCommon/LuaInterpreterTest.php @@ -108,6 +108,10 @@ abstract class Scribunto_LuaInterpreterTest extends MediaWikiTestCase { array( array( 'x' => 'foo', 'y' => 'bar', 'z' => array() ) ), array( INF ), array( -INF ), + array( 'ok', null, 'ok' ), + array( null, 'ok' ), + array( 'ok', null ), + array( null ), ); }