jenkins-bot has submitted this change and it was merged.
Change subject: Add while loops (version 0.5.0)
......................................................................
Add while loops (version 0.5.0)
* Add Parser Function Foxway ( {{#foxway: echo | 'hello' }} )
* Add variable $wgFoxway_max_execution_time
* Add variable $wgFoxway_max_execution_time_for_scope
* Add Hook 'ParserLimitReport'
* Add Array function key_exists (Alias of array_key_exists)
* Fix not enabled by default function strncasecmp()
* Fix boolean AND & OR operators
Change-Id: I426f3b2048205a920fd5e11596b09b4cc640f240
---
M Foxway.body.php
M Foxway.i18n.php
M Foxway.php
M Settings.php
M includes/Debug.php
M includes/Interpreter.php
M includes/Runtime.php
M includes/RuntimeDebug.php
M includes/functions/FArray.php
M resources/ext.foxway.debug.css
A resources/ext.foxway.debugloops.js
M tests/phpunit/includes/InterpreterTest.php
12 files changed, 559 insertions(+), 66 deletions(-)
Approvals:
Pastakhov: Looks good to me, approved
jenkins-bot: Verified
diff --git a/Foxway.body.php b/Foxway.body.php
index f707185..5bb13a9 100644
--- a/Foxway.body.php
+++ b/Foxway.body.php
@@ -9,16 +9,62 @@
*/
class Foxway {
+ static $DebugLoops = false;
+ static $time = false;
+ static $startTime = false;
+
static $frames=array();
+ /**
+ *
+ * @param Parser $parser
+ * @param PPFrame $frame
+ * @param array $args
+ */
+ public static function renderFunction( $parser, $frame, $args ) {
+ self::$startTime = microtime(true);
+
+ $is_banned = self::isBanned($frame);
+ if( $is_banned ) {
+ return $is_banned;
+ }
+
+ $command = array_shift($args);
+ if( count($args) > 0 ) {
+ foreach ($args as &$value) {
+ $value = $frame->expand( $value );
+ }
+ $command = "echo $command (" . implode(',', $args) .
');';
+ }
+ //MWDebug::log($command);
+
+ $result = Foxway\Interpreter::run(
+ $command,
+ array($frame->getTitle()->getPrefixedText()),
+ self::getScope($frame)
+ );
+
+ foreach ($result as &$value) {
+ if( $value instanceof Foxway\iRawOutput ) {
+ $value = '(object)';
+ }
+ }
+ $return = implode($result);
+
+ self::$time += microtime(true) - self::$startTime;
+ return \UtfNormal::cleanUp($return);
+ }
+
public static function render($input, array $args, Parser $parser,
PPFrame $frame) {
- global $wgNamespacesWithFoxway;
- if( $wgNamespacesWithFoxway !== true &&
empty($wgNamespacesWithFoxway[$frame->getTitle()->getNamespace()]) ) {
- return Html::element( 'span', array('class'=>'error'),
wfMessage('foxway-disabled-for-namespace',
$frame->getTitle()->getNsText())->escaped() );
+ self::$startTime = microtime(true);
+
+ $is_banned = self::isBanned($frame);
+ if( $is_banned ) {
+ return $is_banned;
}
$is_debug = isset($args['debug']);
- $return = '';
+ $return = false;
$result = Foxway\Interpreter::run(
$input,
@@ -27,18 +73,32 @@
$is_debug
);
- foreach ($result as &$value) {
- if( $value instanceof Foxway\iRawOutput ) {
- $value = (string)$value;
- }
- }
-
if( $is_debug ) {
$parser->getOutput()->addModules('ext.Foxway.Debug');
+ if( self::$DebugLoops ) {
+
$parser->getOutput()->addModules('ext.Foxway.DebugLoops');
+ }
$return .= self::insertNoWiki( $parser,
array_shift($result) ) . "\n";
}
- return $return . self::insertGeneral( $parser,
$parser->recursiveTagParse(implode($result),$frame) );
+ if( count($result) > 0 ) {
+ //$return .=
Sanitizer::removeHTMLtags(implode($result));
+ $return .= self::insertGeneral( $parser,
$parser->recursiveTagParse(implode($result),$frame) );
+ }
+
+ self::$time += microtime(true) - self::$startTime;
+ return \UtfNormal::cleanUp($return);
+ }
+
+ public static function isBanned(PPFrame $frame) {
+ global $wgNamespacesWithFoxway, $wgFoxway_max_execution_time;
+ if( $wgNamespacesWithFoxway !== true &&
empty($wgNamespacesWithFoxway[$frame->getTitle()->getNamespace()]) ) {
+ return Html::element( 'span', array('class'=>'error'),
wfMessage('foxway-disabled-for-namespace',
$frame->getTitle()->getNsText())->escaped() );
+ }
+ if( $wgFoxway_max_execution_time !== false && self::$time >=
$wgFoxway_max_execution_time) {
+ return Html::element( 'span', array('class'=>'error'),
wfMessage('foxway-php-fatal-error-max-execution-time',
$wgFoxway_max_execution_time, $frame->getTitle()->getPrefixedText())->escaped()
);
+ }
+ return false;
}
/**
@@ -75,4 +135,5 @@
self::$frames[] = array($frame, $scope);
return $scope;
}
-}
\ No newline at end of file
+
+}
diff --git a/Foxway.i18n.php b/Foxway.i18n.php
index 670e1b8..d21358a 100644
--- a/Foxway.i18n.php
+++ b/Foxway.i18n.php
@@ -18,6 +18,9 @@
'foxway-error-bad-delimiter' => 'Delimiter must not be alphanumeric or
backslash',
'foxway-error-no-ending-matching-delimiter' => 'No ending matching
delimiter "$1" found',
'foxway-error-unknown-modifier' => 'Unknown modifier "$1"',
+ 'foxway-php-fatal-error-cannot-break-continue' => 'PHP fatal error:
Cannot break/continue $1 levels on page $2 line $3.',
+ 'foxway-php-fatal-error-max-execution-time' => 'PHP fatal error:
Maximum execution time of $1 second exceeded on page $2.',
+ 'foxway-php-fatal-error-max-execution-time-scope' => 'PHP fatal error:
Maximum execution time of $1 second exceeded on page $2 line $3.',
'foxway-php-fatal-error-undefined-function' => 'PHP fatal error: Call
to undefined function $1() on page $2 line $3.',
'foxway-php-not-variable-passed-by-reference' => 'PHP fatal error: Only
variables can be passed by reference, function $1() on page $2 line $3.',
'foxway-php-syntax-error-unexpected' => 'PHP parse error: Syntax error,
unexpected $1 in command line code on line $2.',
@@ -38,6 +41,17 @@
'foxway-error-no-ending-matching-delimiter' => 'Error message.
Parameters:
* $1 - delimiter',
'foxway-error-unknown-modifier' => 'Error message, parameter $1 is
modifier',
+ 'foxway-php-fatal-error-max-execution-time' => 'Error message,
parameters:
+* $1 - the number of seconds
+* $2 - the name of the page on which the error occurred',
+ 'foxway-php-fatal-error-cannot-break-continue' => 'Error message,
parameters:
+* $1 - the number of user defined level
+* $2 - the name of the page on which the error occurred
+* $3 - the line number where the error occurred',
+ 'foxway-php-fatal-error-max-execution-time-scope' => 'Error message,
parameters:
+* $1 - the number of seconds
+* $2 - the name of the page on which the error occurred
+* $3 - the line number where the error occurred',
'foxway-php-fatal-error-undefined-function' => 'Error message,
parameters:
* $1 - user-specified function name
* $2 - the name of the page on which the error occurred
diff --git a/Foxway.php b/Foxway.php
index 4295eb8..bf013fb 100644
--- a/Foxway.php
+++ b/Foxway.php
@@ -15,7 +15,7 @@
die( 'This file is an extension to MediaWiki and thus not a valid entry
point.' );
}
-define( 'Foxway_VERSION' , '0.4.4' );
+define( 'Foxway_VERSION' , '0.5.0' );
// Register this extension on Special:Version
$wgExtensionCredits['parserhook'][] = array(
@@ -23,7 +23,7 @@
'name' => 'Foxway',
'version' => Foxway_VERSION,
'url' =>
'https://www.mediawiki.org/wiki/Extension:Foxway',
- 'author' => array( '[[mw:User:Pastakhov|Pavel
Astakhov]]' ),
+ 'author' =>
'[https://www.mediawiki.org/wiki/User:Pastakhov Pavel Astakhov]',
'descriptionmsg' => 'foxway-desc'
);
@@ -42,9 +42,20 @@
* @codeCoverageIgnore
*/
$wgHooks['ParserFirstCallInit'][] = function( Parser &$parser ) {
- //$parser->setFunctionHook( 'foxway', 'Foxway::renderParserFunction' );
+ $parser->setFunctionHook( 'foxway', 'Foxway::renderFunction',
SFH_OBJECT_ARGS );
$parser->setHook( 'foxway', 'Foxway::render' );
return true;
+};
+
+/**
+ * @codeCoverageIgnore
+ */
+$wgHooks['ParserLimitReport'][] = function( $parser, &$limitReport ) {
+ if( Foxway::$time !== false ) {
+ $limitReport .= sprintf( "Foxway time usage: %.3f secs\n",
Foxway::$time );
+ # print_r(Foxway\Runtime::getTime(),true);
+ }
+ return true;
};
// Preparing classes for autoloading
@@ -78,6 +89,13 @@
'remoteExtPath' => 'Foxway'
);
+$wgResourceModules['ext.Foxway.DebugLoops'] = array(
+ 'scripts' => 'resources/ext.foxway.debugloops.js',
+ 'dependencies' => 'jquery',
+ 'localBasePath' => __DIR__,
+ 'remoteExtPath' => 'Foxway'
+);
+
/**
* Add files to phpunit test
* @codeCoverageIgnore
diff --git a/Settings.php b/Settings.php
index 69ba5ca..57345a6 100644
--- a/Settings.php
+++ b/Settings.php
@@ -18,7 +18,8 @@
}
// Default settings
-
+$wgFoxway_max_execution_time = 2;
+$wgFoxway_max_execution_time_for_scope = 0.5;
/**
* You can specify the namespaces in which is allowed to use the extension
Foxway.
@@ -117,6 +118,7 @@
'each',
'end',
'in_array',
+ 'key_exists',
'key',
'krsort',
'ksort',
@@ -189,6 +191,7 @@
'strlen',
'strnatcasecmp',
'strnatcmp',
+ 'strncasecmp',
'strncmp',
'strpbrk',
'strpos',
@@ -218,10 +221,9 @@
'preg_grep',
'preg_last_error',
'preg_match_all',
- 'preg_match_all',
'preg_match',
'preg_quote',
- //'preg_replace', not tested
+ //'preg_replace', not fully tested
'preg_split',
),
'FMath' => array( // Math Functions @see
http://www.php.net/manual/en/ref.math.php
diff --git a/includes/Debug.php b/includes/Debug.php
index 437bfca..03634f6 100644
--- a/includes/Debug.php
+++ b/includes/Debug.php
@@ -11,6 +11,8 @@
class Debug implements \ArrayAccess, iRawOutput {
protected $_container = array();
+ private $loops = array();
+ private $closeTags = array();
public function offsetExists($offset)
{
@@ -78,6 +80,9 @@
case T_UNSET_CAST: // (unset)
case T_ECHO:
case T_PRINT:
+ case T_CONTINUE:
+ case T_BREAK:
+ case T_WHILE:
case T_IF:
case T_ELSE:
case T_ELSEIF:
@@ -131,8 +136,35 @@
public function __toString() {
return \Html::rawElement( 'table',
array('class'=>'foxway_debug'),
\Html::rawElement( 'tr', array(),
\Html::element( 'th', array(), 'Debug view' ) ) .
- \Html::rawElement( 'tr', array(),
\Html::rawElement( 'td', array(), implode('', $this->_container) ) )
+ \Html::rawElement( 'tr', array(),
\Html::rawElement( 'td', array(), implode('', $this->_container).implode('',
$this->closeTags) ) )
);
}
-}
+ public function setLoopState($state, $index = 0) {
+ switch ($state) {
+ case FOXWAY_STATE_HEAD:
+ if( !isset($this->loops[$index]) ) {
+ \Foxway::$DebugLoops = true;
+ $this->_container[] =
\Html::openElement( 'table', array('class'=>'foxway_debug_loop') );
+ $this->closeTags[] =
\Html::closeElement('table');
+ $this->loops[$index] = 0;
+ }
+ $this->loops[$index]++;
+ $this->_container[] = \Html::openElement('tr',
array('class'=>'foxway_debug_loophead')) . \Html::element('td', array(),
$this->loops[$index]) . \Html::openElement('td');
+ $this->closeTags[] = \Html::closeElement('td')
. \Html::closeElement('tr');
+ break;
+ case FOXWAY_STATE_BODY:
+ $this->_container[] = array_pop(
$this->closeTags ) . \Html::openElement('tr',
array('class'=>'foxway_debug_loopbody')) . \Html::element('td') .
\Html::openElement('td');
+ $this->closeTags[] = \Html::closeElement('td')
. \Html::closeElement('tr');
+ break;
+ case FOXWAY_STATE_ENDBODY:
+ $this->_container[] = array_pop(
$this->closeTags );
+ break;
+ case FOXWAY_STATE_ENDLOOP:
+ $this->_container[] = array_pop(
$this->closeTags );
+ array_pop($this->loops);
+ break;
+ }
+ }
+
+}
diff --git a/includes/Interpreter.php b/includes/Interpreter.php
index 25e71aa..113c89e 100644
--- a/includes/Interpreter.php
+++ b/includes/Interpreter.php
@@ -5,6 +5,8 @@
define( 'FOXWAY_ENDIF', 1 );
define( 'FOXWAY_ELSE' , 2 );
define( 'FOXWAY_VALUE', 3 );
+define( 'FOXWAY_START_LOOP', 4 );
+define( 'FOXWAY_PARENT_LOOP', 5);
define( 'FOXWAY_ALLOW_PARENTHES_WITH_VOID_PARAMS', 1 << 0 );
define( 'FOXWAY_EXPECT_CURLY_CLOSE', 1 << 1 );
@@ -25,6 +27,11 @@
define( 'FOXWAY_EXPECT_GLOBAL_VARIABLE', 1 << 16 );
define( 'FOXWAY_EXPECT_PARENTHES_WITH_VARIABLE_ONLY', 1 << 17 );
define( 'FOXWAY_ALLOW_PARENTHES_WITH_VARIABLE_ONLY', 1 << 18 );
+
+define( 'FOXWAY_STATE_HEAD', 1 );
+define( 'FOXWAY_STATE_BODY', 2 );
+define( 'FOXWAY_STATE_ENDBODY', 3 );
+define( 'FOXWAY_STATE_ENDLOOP', 4 );
/**
* Interpreter class of Foxway extension.
@@ -52,6 +59,8 @@
'%',
'&',
'|',
+ T_BOOLEAN_AND, // &&
+ T_BOOLEAN_OR, // ||
'^',
T_SL, // <<
T_SR, // >>
@@ -118,25 +127,30 @@
public static function run($source, array $args=array(), $scope='',
$is_debug=false) {
global $wgFoxwayAllowedPHPConstants;
- $tokens = self::getTokens($source);
-
- $return = array();
$debug = $is_debug ? new Debug() : false;
- $blocks = array();
- $expected = false;
- $parentheses = array();
- $parenthesFlags = FOXWAY_EXPECT_SEMICOLON;
- $curlyLever = 0;
- $IfIndex = false;
- $incrementVariable = false;
- $commandResult = null;
- $tokenLine = 1;
-
if( $debug ) {
$runtime = new RuntimeDebug( $args, $scope );
} else {
$runtime = new Runtime( $args, $scope );
}
+ $r = $runtime->startTime($scope);
+ if( $r !== null ) {
+ return array($r);
+ }
+
+ $return = array();
+ $tokens = self::getTokens($source); // @todo cache
+ $blocks = array(); // @todo cache
+ $expected = false;
+ $parentheses = array();
+ $parenthesFlags = FOXWAY_EXPECT_SEMICOLON;
+ $curlyLever = 0;
+ $ifIndex = false;
+ $incrementVariable = false;
+ $commandResult = null;
+ $tokenLine = 1;
+ $breakNumber = 0;
+ $loopContinue = false;
$operators = $runtime->getOperators();
@@ -157,7 +171,6 @@
switch ($id) {
case ';':
- $parenthesFlags =
FOXWAY_EXPECT_SEMICOLON;
if ( !($parenthesFlags &
FOXWAY_EXPECT_SEMICOLON) ) {
$return[] = new
ErrorMessage(__LINE__, $tokenLine, E_PARSE, $id);
break 2;
@@ -283,10 +296,13 @@
FOXWAY_EXPECT_FUNCTION_PARENTHESES |
FOXWAY_EXPECT_PARENTHES_WITH_DOUBLE_ARROW;
break;
+ case T_WHILE:
+ if( $debug ) {
$debug->setLoopState(FOXWAY_STATE_HEAD, $index); }
+ // break is not necessary here
case T_IF:
$parentheses[] = $parenthesFlags;
//TODO check &= ~FOXWAY_EXPECT_SEMICOLON;
$parenthesFlags =
FOXWAY_EXPECT_FUNCTION_PARENTHESES;
- $IfIndex = $index;
+ $ifIndex = $index;
$expected = array('(');
$runtime->addCommand($id);
break;
@@ -294,6 +310,10 @@
$runtime->addCommand($id);
$parenthesFlags =
FOXWAY_ALLOW_LIST_PARAMS | FOXWAY_EXPECT_SEMICOLON;
break;
+ case T_CONTINUE:
+ case T_BREAK:
+ $ifIndex = $index;
+ // break is not necessary here
case T_PRINT:
$runtime->addCommand($id);
$parenthesFlags =
FOXWAY_EXPECT_SEMICOLON;
@@ -454,14 +474,53 @@
case T_PRINT:
$return =
array_merge($return, $result);
break;
- case T_IF:
- if(
!isset($blocks[$IfIndex]) ) {
- if(
self::findIfElseIndexes($tokens, $blocks, $IfIndex, $index) !== true ) {
+ case T_CONTINUE:
+ case T_BREAK:
+ $result =
isset($result[0]) ? intval($result[0]) : 0 ;
+ if( $result > 1 ) {
+ $result--;
+ }
+ if( $command == T_BREAK
) { // For 'break N' command
+ $breakNumber =
$result+1; // $breakNumber = N;
+ $loopContinue =
false;
+ } elseif( $result > 0 )
{ // For 'continue N > 1' command
+ $breakNumber =
$result; // $breakNumber = N-1;
+ $loopContinue =
true; // and mark loop continue
+ } // nothing for
command 'continue 1'
+ if(
!isset($blocks[$ifIndex][FOXWAY_ENDBLOCK]) ) {
+ $return[] = new
ErrorMessage( __LINE__, $tokenLine, E_ERROR,
array('foxway-php-fatal-error-cannot-break-continue', $text,
isset($args[0])?$args[0]:'n\a') );
+ break 2;
+ }
+ $index =
$blocks[$ifIndex][FOXWAY_ENDBLOCK]-1;
+ break;
+ case T_WHILE:
+ if(
!isset($blocks[$ifIndex]) ) {
+ if(
self::findLoopIndexes($tokens, $blocks, $ifIndex, $index) !== true ) {
$return[] = new ErrorMessage(__LINE__, $tokenLine, E_PARSE, '$end');
break 2;
}
}
- $curBlock =
$blocks[$IfIndex];
+ $expected = false;
+ if( $result == false )
{ // if true - just go next
+ $curBlock =
$blocks[$ifIndex];
+ $index =
$curBlock[FOXWAY_ENDBLOCK];
+ $commandResult
= null;
+ if( $debug ) {
+
$debug[] = 'skip';
+
$debug->setLoopState(FOXWAY_STATE_ENDLOOP, $ifIndex);
+ }
+ continue 2;
+ }
+ if( $debug ) {
$debug->setLoopState(FOXWAY_STATE_BODY, $ifIndex); }
+ break;
+ case T_IF:
+ if(
!isset($blocks[$ifIndex]) ) {
+ if(
self::findIfElseIndexes($tokens, $blocks, $ifIndex, $index) !== true ) {
+
$return[] = new ErrorMessage(__LINE__, $tokenLine, E_PARSE, '$end');
+ break 2;
+ }
+ }
+ $curBlock =
$blocks[$ifIndex];
$elseIndex =
$curBlock[FOXWAY_ELSE];
if( $result ) {
if( $elseIndex
) {
@@ -490,7 +549,7 @@
case T_ELSEIF:
if( $result ) { // chek
for IF
// this code
from previus swith( $id ) case T_IF: & case T_ECHO:
- $IfIndex =
$index;
+ $ifIndex =
$index;
$expected =
array('(');
$runtime->addCommand(T_IF);
break;
@@ -514,6 +573,9 @@
}
continue 2;
}
+ break;
+ default :
+ // @todo error message ?
break;
}
}elseif( $commandResult instanceof ErrorMessage
) {
@@ -564,6 +626,8 @@
// break is not necessary here
case T_ECHO:
case T_PRINT:
+// case T_CONTINUE:
+// case T_BREAK:
case ',':
case T_CONCAT_EQUAL: // .=
case T_PLUS_EQUAL: // +=
@@ -585,6 +649,8 @@
case '%':
case '&':
case '|':
+ case T_BOOLEAN_AND:
+ case T_BOOLEAN_OR:
case '^':
case T_SL: // <<
case T_SR: // >>
@@ -623,7 +689,7 @@
}
break;
case '}':
- if( $parenthesFlags &
FOXWAY_EXPECT_CURLY_CLOSE ) {
+ if( $parenthesFlags &
FOXWAY_EXPECT_CURLY_CLOSE ) { // This is '}' (CURLY CLOSE) for T_CURLY_OPEN
$parenthesFlags =
array_pop($parentheses);
$expected = array(
T_CONSTANT_ENCAPSED_STRING,
@@ -634,9 +700,29 @@
T_CURLY_OPEN,
'"',
);
- } elseif( $curlyLever ) {
+ } elseif( $curlyLever ) { // This is
mark of end block
$curlyLever--;
- }else {
+ if( isset($blocks[$index]) &&
isset($blocks[$index][FOXWAY_START_LOOP]) ) { // This is end of loop
+ if( $debug ) {
$debug->setLoopState(FOXWAY_STATE_ENDBODY); }
+ if( $breakNumber > 1 ||
($breakNumber==1 && $loopContinue) ) { // This is 'break > 1' or 'continue > 1'
- go to end of parent loop
+ $breakNumber--;
+ if(
!isset($blocks[$index][FOXWAY_PARENT_LOOP][FOXWAY_ENDBLOCK]) ) {
+
$return[] = new ErrorMessage( __LINE__, $tokenLine, E_ERROR,
array('foxway-php-fatal-error-cannot-break-continue', $text,
isset($args[0])?$args[0]:'n\a') );
+ break 2;
+ }
+ $index =
$blocks[$index][FOXWAY_PARENT_LOOP][FOXWAY_ENDBLOCK]-1;
+ if( $debug ) {
$debug->setLoopState(FOXWAY_STATE_ENDLOOP); }
+ continue 2;
+ }
+ if( $breakNumber != 1 )
{ // This is not 'break 1' (possibly 'continue 1') - go to start loop
+ $index =
$blocks[$index][FOXWAY_START_LOOP]-1;
+ continue 2;
+ }
+ //This is 'break 1' -
just go next, out of this loop
+ $breakNumber--;
+ if( $debug ) {
$debug->setLoopState(FOXWAY_STATE_ENDLOOP); }
+ }
+ } else { // This is mark of end block
without mark of start block
$return[] = new
ErrorMessage(__LINE__, $tokenLine, E_PARSE, $id);
break 2;
}
@@ -663,6 +749,11 @@
/***************** EXPECT PHASE TWO
**************************/
switch ($id) {
+ case ';':
+ if(
isset($blocks[$index][FOXWAY_START_LOOP]) ) {
+ $index =
$blocks[$index][FOXWAY_START_LOOP]-1;
+ }
+ break;
case T_ARRAY:
$parenthesFlags |=
FOXWAY_ALLOW_PARENTHES_WITH_VOID_PARAMS;
break;
@@ -705,6 +796,7 @@
if( $debug ) {
array_unshift($return, $debug);
}
+ $runtime->stopTime($scope);
return $return;
}
@@ -730,7 +822,6 @@
static $replacement = array('$1"', "$1\n", "$1\r", "$1\t",
"$1\v", '$1$', '\\');
return preg_replace($pattern, $replacement, $string);
}
-
private static function findTernaryIndexes( &$tokens, &$blocks, $index
) {
$embedded = 0;
@@ -786,7 +877,61 @@
}
}
- private static function findIfElseIndexes(&$tokens, &$blocks, $ifIndex,
$index) {
+ public static function findLoopIndexes( &$tokens, &$blocks, $ifIndex,
$index, &$parentLoop = false ) {
+ $count = count($tokens);
+ $nestedBlocks = 0;
+ for( $i = $index; $i < $count; $i++ ) { // find end of block
+ $token = $tokens[$i];
+ if ( is_string($token) ) {
+ $id = $token;
+ } else {
+ list($id, , $tokenLine) = $token;
+ }
+ switch ($id) {
+ case '{':
+ case T_CURLY_OPEN:
+ $nestedBlocks++;
+ break;
+ case '}':
+ $nestedBlocks--;
+ // break is not necessary here
+ case ';':
+ if($nestedBlocks == 0 ) {
+ break 2;
+ }
+ break;
+ case T_WHILE:
+ if(
/*!isset($blocks[$i][FOXWAY_ENDBLOCK]) &&*/ self::findLoopIndexes($tokens,
$blocks, $i, self::findLastParenthesis($tokens, $i), $blocks[$ifIndex]) !==
true ) {
+ return false;
+ }
+ $i = $blocks[$i][FOXWAY_ENDBLOCK];
+ if( $nestedBlocks == 0 ) { break 2;
}
+ break;
+ case T_IF:
+ if( /*!isset($blocks[$i][FOXWAY_ENDIF])
&&*/ self::findIfElseIndexes($tokens, $blocks, $i,
self::findLastParenthesis($tokens, $i), $blocks[$ifIndex]) !== true ) {
+ return false;
+ }
+ $i = $blocks[$i][FOXWAY_ENDIF];
+ if( $nestedBlocks == 0 ) { break 2;
}
+ break;
+ case T_CONTINUE:
+ case T_BREAK:
+ $blocks[$i] = &$blocks[$ifIndex];
+ break;
+ }
+ }
+ if( $i >= $count ) {
+ return false;
+ }
+ $blocks[$ifIndex][FOXWAY_ENDBLOCK] = $i;
+ $blocks[$i][FOXWAY_START_LOOP] = $ifIndex;
+ if( $parentLoop !== false ) {
+ $blocks[$i][FOXWAY_PARENT_LOOP] = &$parentLoop;
+ }
+ return true;
+ }
+
+ private static function findIfElseIndexes( &$tokens, &$blocks,
$ifIndex, $index, &$parentLoop = false) {
$count = count($tokens);
$nestedBlocks = 0;
@@ -805,22 +950,29 @@
$nestedBlocks--;
// break is not necessary here
case ';':
- if($nestedBlocks == 0 ) {
- break 2;
- }
+ if($nestedBlocks == 0 ) { break 2; }
break;
case T_IF:
- if( $nestedBlocks == 0 ) {
- if(
!isset($blocks[$i][FOXWAY_ENDIF]) && self::findIfElseIndexes($tokens, $blocks,
$i, self::findLastParenthesis($tokens, $i)) !== true ) {
- return false;
- }
- $i = $blocks[$i][FOXWAY_ENDIF];
- break 2;
+ if( /*!isset($blocks[$i][FOXWAY_ENDIF])
&&*/ self::findIfElseIndexes($tokens, $blocks, $i,
self::findLastParenthesis($tokens, $i), $parentLoop) !== true ) {
+ return false;
}
+ $i = $blocks[$i][FOXWAY_ENDIF];
+ if( $nestedBlocks == 0 ) { break 2;
}
+ break;
+ case T_WHILE:
+ if(
/*!isset($blocks[$i][FOXWAY_ENDBLOCK]) &&*/ self::findLoopIndexes($tokens,
$blocks, $i, self::findLastParenthesis($tokens, $i), $parentLoop) !== true ) {
+ return false;
+ }
+ $i = $blocks[$i][FOXWAY_ENDBLOCK];
+ if( $nestedBlocks == 0 ) { break 2;
}
+ break;
+ case T_CONTINUE:
+ case T_BREAK:
+ $blocks[$i] = &$parentLoop;
break;
}
}
- if( $i == $count ) {
+ if( $i >= $count ) {
return false; // end of block not find
}
$blocks[$ifIndex][FOXWAY_ENDBLOCK] = $i;
@@ -839,7 +991,7 @@
case T_WHITESPACE:
break; // ignore it
case T_ELSEIF:
- if( !isset($blocks[$i]) &&
self::findIfElseIndexes($tokens, $blocks, $i,
self::findLastParenthesis($tokens, $i)) !== true ) {
+ if( !isset($blocks[$i]) &&
self::findIfElseIndexes($tokens, $blocks, $i,
self::findLastParenthesis($tokens, $i), $parentLoop) !== true ) {
return false;
}
$blocks[$ifIndex][FOXWAY_ELSE] = $i; //
We fount T_ELSE or T_ELSEIF
@@ -850,7 +1002,7 @@
$i = $blocks[$i][FOXWAY_ENDBLOCK];
break;
case T_ELSE:
- if( !isset($blocks[$i]) &&
self::findIfElseIndexes($tokens, $blocks, $i, $i+1) !== true ) {
+ if( !isset($blocks[$i]) &&
self::findIfElseIndexes($tokens, $blocks, $i, $i+1, $parentLoop) !== true ) {
return false;
}
$blocks[$ifIndex][FOXWAY_ELSE] = $i; //
We fount T_ELSE or T_ELSEIF
diff --git a/includes/Runtime.php b/includes/Runtime.php
index 5860ed3..4c4fac6 100644
--- a/includes/Runtime.php
+++ b/includes/Runtime.php
@@ -32,6 +32,8 @@
protected static $variables = array();
protected static $staticVariables = array();
protected static $globalVariables = array();
+ protected static $time = array();
+ protected static $startTime = array();
protected $thisVariables;
protected $args;
protected $scope;
@@ -53,8 +55,8 @@
array('&'),
array('^'),
array('|'),
- array('&&'),
- array('||'),
+ array(T_BOOLEAN_AND), // &&
+ array(T_BOOLEAN_OR), // ||
array('?', ':'),
// += -=
*= /= .=
%= &=
|= ^= <<= >>=
=>
array('=', T_PLUS_EQUAL, T_MINUS_EQUAL, T_MUL_EQUAL,
T_DIV_EQUAL, T_CONCAT_EQUAL, T_MOD_EQUAL, T_AND_EQUAL, T_OR_EQUAL, T_XOR_EQUAL,
T_SL_EQUAL, T_SR_EQUAL, T_DOUBLE_ARROW),
@@ -70,6 +72,7 @@
if( !isset(self::$variables[$scope]) ) {
self::$variables[$scope] = array();
}
+ $this->scope = $scope;
$this->thisVariables = &self::$variables[$scope];
$this->thisVariables['argv'] = $args;
$this->thisVariables['argc'] = count($args);
@@ -251,16 +254,23 @@
}
// break is not necessary here
case ')':
+ $return = $this->checkExceedsTime();
+ if( $return !== null ) {
+ $this->lastCommand = false;
+ }
$this->parenthesesClose();
- $return = null;
switch ($this->lastCommand) {
case false:
case T_ECHO:
case T_PRINT:
+ case T_CONTINUE:
+ case T_BREAK:
break 2;
+ case T_WHILE:
case T_IF:
- $this->lastCommand = false;
- return array( T_IF,
$this->lastParam->getValue() );
+ $return = array(
$this->lastCommand, $this->lastParam->getValue() );
+ //$this->lastCommand = false;
+ //return $return;
break;
case T_ARRAY:
$this->lastParam = new RValue(
(array)$this->lastParam );
@@ -415,6 +425,12 @@
case '^':
$this->lastParam = new RValue(
$param->getValue() ^ $lastParam );
break;
+ case T_BOOLEAN_AND: // &&
+ $this->lastParam = new RValue(
$param->getValue() && $lastParam );
+ break;
+ case T_BOOLEAN_OR: // ||
+ $this->lastParam = new RValue(
$param->getValue() || $lastParam );
+ break;
case T_SL: // <<
$this->lastParam = new RValue(
$param->getValue() << $lastParam );
break;
@@ -474,16 +490,21 @@
// Remember the child class RuntimeDebug
public function getCommandResult( ) {
+ $return = $this->checkExceedsTime();
+ if( $return !== null ) {
+ return $return;
+ }
if( $this->lastParam !== null ) {
$this->addOperator(',');
}
- $return = null;
// Remember the child class RuntimeDebug
switch ($this->lastCommand) {
case T_ECHO:
case T_PRINT:
- $return = array( T_ECHO, $this->listParams );
+ case T_CONTINUE:
+ case T_BREAK:
+ $return = array( $this->lastCommand,
$this->listParams );
break;
case false:
break; // exsample: $foo = 'foobar';
@@ -542,4 +563,29 @@
return $return;
}
+ public function startTime($scope) {
+ self::$startTime[$scope] = microtime(true);
+ if( isset(self::$time[$scope]) ) {
+ return $this->checkExceedsTime();
+ }else{
+ self::$time[$scope] = 0;
+ }
+ }
+
+ public function stopTime($scope) {
+ self::$time[$scope] += microtime(true) -
self::$startTime[$scope];
+ }
+
+ public static function getTime() {
+ return self::$time;
+ }
+
+ public function checkExceedsTime() {
+ global $wgFoxway_max_execution_time_for_scope;
+ if( microtime(true) - self::$startTime[$this->scope] +
self::$time[$this->scope] > $wgFoxway_max_execution_time_for_scope ) {
+ return new ErrorMessage( __LINE__, null, E_ERROR,
array( 'foxway-php-fatal-error-max-execution-time-scope',
$wgFoxway_max_execution_time_for_scope,
isset($this->args[0])?$this->args[0]:'n\a' ) );
+ }
+ return null;
+ }
+
}
diff --git a/includes/RuntimeDebug.php b/includes/RuntimeDebug.php
index 75cb095..670f90f 100644
--- a/includes/RuntimeDebug.php
+++ b/includes/RuntimeDebug.php
@@ -103,9 +103,12 @@
case T_ARRAY:
case T_ECHO:
case T_PRINT:
+ case T_CONTINUE:
+ case T_BREAK:
break;
+ case T_WHILE:
case T_IF:
- $this->debug[] =
self::getHTMLForCommand(T_IF) .
+ $this->debug[] =
self::getHTMLForCommand( $this->lastCommandDebug ) .
"( " .
self::getHTMLForValue( $this->lastParam ) .
" ) <b>=></b> " .
self::getHTMLForValue(
new RValue($this->lastParam->getValue() ? true : false) );
@@ -119,12 +122,19 @@
$lastCommand = $this->lastCommand;
$return = parent::getCommandResult();
+ if( $return instanceof ErrorMessage ){
+ $lastCommand = false;
+ }
switch ($lastCommand) {
case T_ECHO:
case T_PRINT:
$this->debug[] =
self::getHTMLForCommand($lastCommand) . ' ' . implode(', ',
$this->savedListParams) . ';';
$this->debug[] = implode('', $return[1]);
+ break;
+ case T_CONTINUE:
+ case T_BREAK:
+ $this->debug[] =
self::getHTMLForCommand($lastCommand) . ' ' . implode(', ',
$this->savedListParams) . ';';
break;
default :
$this->debug[] = is_array($return) ? $return[1]
: $return ;
@@ -203,6 +213,15 @@
break;
case T_PRINT:
$return = \Html::element('span',
array('class'=>'foxway_construct'), 'print');
+ break;
+ case T_CONTINUE:
+ $return = \Html::element('span',
array('class'=>'foxway_construct'), 'continue');
+ break;
+ case T_BREAK:
+ $return = \Html::element('span',
array('class'=>'foxway_construct'), 'break');
+ break;
+ case T_WHILE:
+ $return = \Html::element('span',
array('class'=>'foxway_construct'), 'while');
break;
case T_IF:
$return = \Html::element('span',
array('class'=>'foxway_construct'), 'if');
@@ -284,6 +303,27 @@
case T_IS_NOT_IDENTICAL: // !==
$operator = '!==';
break;
+ case T_SL: // <<
+ $operator = '<<';
+ break;
+ case T_SR: // >>
+ $operator= '>>';
+ break;
+ case T_BOOLEAN_AND: // &&
+ $operator = '&&';
+ break;
+ case T_BOOLEAN_OR: // ||
+ $operator = '||';
+ break;
+ case T_LOGICAL_AND:
+ $operator = 'and';
+ break;
+ case T_LOGICAL_XOR:
+ $operator = 'xor';
+ break;
+ case T_LOGICAL_OR:
+ $operator = 'or';
+ break;
case T_INT_CAST:
$operator = \Html::element('span',
array('class'=>'foxway_construct'), '(integer)');
break;
@@ -312,7 +352,7 @@
"(" . implode( ', ', $listParams ) .
") <b>=></b> " .
self::getHTMLForValue( $this->lastParam );
- return $return;
+ return $return;
}
}
diff --git a/includes/functions/FArray.php b/includes/functions/FArray.php
index 289dfb5..09057c9 100644
--- a/includes/functions/FArray.php
+++ b/includes/functions/FArray.php
@@ -69,6 +69,7 @@
'f_end' => array('end', 1, 1),
// @todo extract (if there is a need in this)
'f_in_array' => array('in_array', 2, 3),
+ 'f_key_exists' => array('array_key_exists', 2, 2), // Alias of
array_key_exists()
'f_key' => array('key', 1, 1),
'f_krsort' => array('krsort', 1, 2),
'f_ksort' => array('ksort', 1, 2),
diff --git a/resources/ext.foxway.debug.css b/resources/ext.foxway.debug.css
index 15abc91..ab59200 100644
--- a/resources/ext.foxway.debug.css
+++ b/resources/ext.foxway.debug.css
@@ -10,6 +10,23 @@
background-color: #f9f9f9;
}
+.foxway_debug_loop {
+ width: auto;
+ border: 1px solid gray;
+ border-spacing: 0;
+}
+
+.foxway_debug_loop td {
+ padding: 0px;
+ border: 0px;
+ background-color: transparent;
+}
+
+.foxway_debug_loophead {
+ background-color: antiquewhite;
+ cursor: pointer;
+}
+
.foxway_comment,
.foxway_skipped {
color:#969696;
diff --git a/resources/ext.foxway.debugloops.js
b/resources/ext.foxway.debugloops.js
new file mode 100644
index 0000000..cd1ea97
--- /dev/null
+++ b/resources/ext.foxway.debugloops.js
@@ -0,0 +1,10 @@
+(function ($) {
+
+ $().ready(function () {
+ $('.foxway_debug_loopbody').hide();
+ $('.foxway_debug_loophead').click(function () {
+ $(this).next().toggle();
+ });
+ });
+
+})(window.jQuery);
diff --git a/tests/phpunit/includes/InterpreterTest.php
b/tests/phpunit/includes/InterpreterTest.php
index d443795..2d5c4b1 100644
--- a/tests/phpunit/includes/InterpreterTest.php
+++ b/tests/phpunit/includes/InterpreterTest.php
@@ -2,6 +2,9 @@
namespace Foxway;
+$wgFoxway_max_execution_time = 20;
+$wgFoxway_max_execution_time_for_scope = 20;
+
/**
* Generated by PHPUnit_SkeletonGenerator 1.2.0 on 2013-03-28 at 05:28:38.
*/
@@ -2014,6 +2017,103 @@
);
}
+ public function testRun_while_1() {
+ $this->assertEquals(
+ Interpreter::run('$i=1; while( $i <= 3 ) { echo
$i++; }'),
+ array('1', '2', '3')
+ );
+ }
+ public function testRun_while_2() {
+ $this->assertEquals(
+ Interpreter::run('$i=1; while( $i <= 3 ) echo
$i++;'),
+ array('1', '2', '3')
+ );
+ }
+ public function testRun_while_continue_1() {
+ $this->assertEquals(
+ Interpreter::run('$i=1; while( $i <= 3 ) { echo
$i++; continue; $i++ }'),
+ array('1', '2', '3')
+ );
+ }
+ public function testRun_while_break_1() {
+ $this->assertEquals(
+ Interpreter::run('$i=1; while( $i <= 33 ) {
echo $i++; break; $i++ }'),
+ array('1')
+ );
+ }
+ public function testRun_while_if_continue_1() {
+ $this->assertEquals(
+ Interpreter::run('$i=0; while( $i <= 2 ) {
$i++; if($i == 2) continue; echo $i; }'),
+ array('1', '3')
+ );
+ }
+ public function testRun_while_if_continue_2() {
+ $this->assertEquals(
+ Interpreter::run('$i=0; while( $i <= 2 ) {
$i++; if($i == 2) { echo "Two"; continue; } echo $i; }'),
+ array('1', 'Two', '3')
+ );
+ }
+ public function testRun_while_if_break_1() {
+ $this->assertEquals(
+ Interpreter::run('$i=1; while( $i <= 33 ) {
echo $i++; if($i == 3) break; }'),
+ array('1', '2')
+ );
+ }
+ public function testRun_while_if_break_2() {
+ $this->assertEquals(
+ Interpreter::run('$i=1; while( $i <= 33 ) {
echo $i++; if($i == 3){echo "The end"; break; echo "anything";} }'),
+ array('1', '2', 'The end')
+ );
+ }
+ public function testRun_while_while_1() {
+ $this->assertEquals(
+ implode( Interpreter::run('$i=1; while( $i<=3
){ echo "|$i|"; $y=1; while( $y<=$i ){ echo "($y)"; $y++; } $i++; }') ),
+ '|1|(1)|2|(1)(2)|3|(1)(2)(3)'
+ );
+ }
+ public function testRun_while_while_continue_1() {
+ $this->assertEquals(
+ implode( Interpreter::run('$i=1; while( $i<=3
){ echo "|$i|"; $y=0; while( $y<3 ){ $y++; if( $y==2) continue; echo "($y)"; }
$i++; }') ),
+ '|1|(1)(3)|2|(1)(3)|3|(1)(3)'
+ );
+ }
+ public function testRun_while_while_continue_2() {
+ $this->assertEquals(
+ implode( Interpreter::run('$i=0; while( $i<5 ){
$i++; echo "|$i|"; $y=0; while( $y<3 ){ $y++; if( $y==$i ){ continue 2; } echo
"($y)"; } }') ),
+ '|1||2|(1)|3|(1)(2)|4|(1)(2)(3)|5|(1)(2)(3)'
+ );
+ }
+ public function testRun_while_while_break_1() {
+ $this->assertEquals(
+ implode( Interpreter::run('$i=1; while( $i<=5
){ echo "|$i|"; $y=0; while( $y<3 ){ $y++; if( $y==2) break; echo "($y)"; }
$i++; }') ),
+ '|1|(1)|2|(1)|3|(1)|4|(1)|5|(1)'
+ );
+ }
+ public function testRun_while_while_break_2() {
+ $this->assertEquals(
+ implode( Interpreter::run('$i=1; while( $i<=5
){ echo "|$i|"; $y=0; while( $y<4 && $y<$i ){ $y++; if( $y==3) { break 2; echo
"hohoho"; } echo "($y)"; } $i++; }') ),
+ '|1|(1)|2|(1)(2)|3|(1)(2)'
+ );
+ }
+ public function testRun_while_if_while_1() {
+ $this->assertEquals(
+ implode( Interpreter::run('$i=1; while( $i<=5
){ echo "|$i|"; $y=0; if( $i==2 || $i == 4 ) while( $y<$i ){ $y++; echo "($y)";
} $i++; } ') ),
+ '|1||2|(1)(2)|3||4|(1)(2)(3)(4)|5|'
+ );
+ }
+ public function testRun_while_if_while_2() {
+ $this->assertEquals(
+ implode( Interpreter::run('$i=1; while( $i<=5
){ echo "|$i|"; $y=0; if( $i==2 || $i == 4 ) { echo "."; while( $y<$i ){ $y++;
echo "($y)"; } } $i++; } ') ),
+ '|1||2|.(1)(2)|3||4|.(1)(2)(3)(4)|5|'
+ );
+ }
+ public function testRun_while_if_while_if_1() {
+ $this->assertEquals(
+ implode( Interpreter::run('$i=1; while( $i<=5
){ echo "|$i|"; $y=0; if( $i==2 || $i == 4 ) { echo "."; while( $y<$i ){ $y++;
if($y < 3) echo "($y)"; else break 2; } } $i++; } ') ),
+ '|1||2|.(1)(2)|3||4|.(1)(2)'
+ );
+ }
+
}
// @todo echo is_scalar(array("foo","bar") ? "true" : "false"; // most return
error
--
To view, visit https://gerrit.wikimedia.org/r/69836
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I426f3b2048205a920fd5e11596b09b4cc640f240
Gerrit-PatchSet: 12
Gerrit-Project: mediawiki/extensions/Foxway
Gerrit-Branch: master
Gerrit-Owner: Pastakhov <[email protected]>
Gerrit-Reviewer: Pastakhov <[email protected]>
Gerrit-Reviewer: jenkins-bot
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits