r53110 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r53109‎ | r53110 | r53111 >
Date:13:52, 11 July 2009
Author:vasilievvv
Status:deferred
Tags:
Comment:
InlineScripts: added exception handling, break/continue, isset/unset, testing suite, and some other things
Modified paths:
  • /trunk/extensions/InlineScripts/InlineScripts.i18n.php (modified) (history)
  • /trunk/extensions/InlineScripts/InlineScripts.php (modified) (history)
  • /trunk/extensions/InlineScripts/interpreter/Interpreter.php (modified) (history)
  • /trunk/extensions/InlineScripts/interpreter/ParserShuntingYard.php (modified) (history)
  • /trunk/extensions/InlineScripts/interpreter/Utils.php (modified) (history)
  • /trunk/extensions/InlineScripts/interpreterTests.txt (added) (history)

Diff [purge]

Index: trunk/extensions/InlineScripts/interpreter/ParserShuntingYard.php
@@ -38,8 +38,8 @@
3939
4040 // Comments
4141 if ( substr($this->mCode, $this->mPos, 2) == '/*' ) {
42 - $end = strpos( $this->mCode, '*/', $this->mPos );
43 - return self::nextToken( $this->mCode, $end + 2 );
 42+ $this->mPos = strpos( $this->mCode, '*/', $this->mPos ) + 2;
 43+ return self::nextToken();
4444 }
4545
4646 // Braces
@@ -222,7 +222,7 @@
223223 if( $type != ISToken::TNone ) {
224224 $this->mTokensCount++;
225225 if( !$this->mInterpreter->increaseTokensCount() )
226 - throw new ISUserVisibleException( 'toomanytokens', $ast->getPos() );
 226+ throw new ISUserVisibleException( 'toomanytokens', $this->mPos );
227227 }
228228
229229 $token = new ISToken( $type, $val, $this->mPos );
@@ -295,8 +295,21 @@
296296 break;
297297 }
298298 }
299 - array_push( $opStack, $op1 );
300 - $expecting = self::ExpectingData;
 299+
 300+ if( $this->mCur->isOp( 'catch' ) ) {
 301+ $this->move();
 302+ if( $this->mCur->type != ISToken::TID )
 303+ throw new ISUserVisibleException( 'cantchangeconst', $pos );
 304+ $op1->setData( $this->mCur->value );
 305+ }
 306+
 307+ if( $op1->getArgsNumber() ) {
 308+ array_push( $opStack, $op1 );
 309+ $expecting = self::ExpectingData;
 310+ } else {
 311+ $outputQueue[] = $op1;
 312+ $expecting = self::ExpectingOperator;
 313+ }
301314 }
302315
303316 /* Functions */
@@ -316,7 +329,7 @@
317330 $this->pushOp( $outputQueue, $opStack );
318331 } else {
319332 $outputQueue[] = new ISDataNode(
320 - new ISData( ISData::DList, array() ) );
 333+ new ISData( ISData::DList, array() ), $this->mPos );
321334 }
322335 $expecting = self::ExpectingOperator;
323336 continue;
Index: trunk/extensions/InlineScripts/interpreter/Utils.php
@@ -60,6 +60,12 @@
6161 const OElse = 'else';
6262 const ODo = 'do';
6363 const OForeach = 'foreach';
 64+ const OTry = 'try';
 65+ const OCatch = 'catch';
 66+ const OBreak = 'break';
 67+ const OContinue = 'continue';
 68+ const OIsset = 'isset';
 69+ const OUnset = 'unset';
6470 const OIn = 'in';
6571 const OInvert = '!';
6672 const OPow = '**';
@@ -82,13 +88,17 @@
8389 const OTrinary = '?';
8490 const OColon = ':';
8591 const OSet = '=';
 92+ const OSetAdd = '+=';
 93+ const OSetSub = '-=';
 94+ const OSetMul = '*=';
 95+ const OSetDiv = '/=';
8696 const OComma = ',';
8797 const OStatementSeperator = ';';
8898 const OLeftBrace = '(';
8999 const OLeftCurly = '{';
90100
91101 static $precedence = array(
92 - self::OFunction => 20,
 102+ self::OFunction => 20, self::OIsset => 20, self::OUnset => 20,
93103 self::OArrayElement => 19, self::OPositive => 19, self::ONegative => 19,
94104 self::OIn => 18,
95105 self::OInvert => 17, self::OPow => 17,
@@ -98,27 +108,30 @@
99109 self::ONotEqualsToStrict => 13, self::OGreater => 13, self::OLess => 13,
100110 self::OGreaterOrEq => 13, self::OLessOrEq => 13,
101111 self::OAnd => 12, self::OOr => 12, self::OXor => 12,
102 - self::OColon => 11, self::OTrinary => 10, self::OSet => 9,
 112+ self::OColon => 11, self::OTrinary => 10,
 113+ self::OSet => 9, self::OSetAdd => 9, self::OSetSub => 9,
 114+ self::OSetMul => 9, self::OSetDiv => 9,
103115 self::OIf => 6, self::OThen => 7, self::OElse => 8,
104116 self::OForeach => 6, self::ODo => 7,
 117+ self::OTry => 6, self::OCatch => 7,
105118 self::OComma => 8, self::OStatementSeperator => 0,
106119 );
107120
108121 static $unaryOperators = array(
109 - self::OPositive, self::ONegative, self::OFunction, self::OArray,
 122+ self::OPositive, self::ONegative, self::OFunction, self::OArray, self::OTry,
110123 self::OIf, self::OForeach, self::OInvert, self::OArrayElementSingle,
 124+ self::OIsset, self::OUnset
111125 );
112126
113127 static function parseOperator( $op, $expecting, $pos ) {
114128 if( $expecting == ISCodeParserShuntingYard::ExpectingData ) {
 129+ $ops = array( '(', '{', 'if', '!', 'try', 'break', 'continue',
 130+ 'isset', 'unset' );
115131 if( $op == '+' ) return new self( self::OPositive, $pos );
116132 if( $op == '-' ) return new self( self::ONegative, $pos );
117 - if( $op == self::OLeftBrace ||
118 - $op == self::OLeftCurly ||
119 - $op == self::OIf ||
120 - $op == self::OInvert )
 133+ if( $op == '[' ) return new self( self::OArray, $pos );
 134+ if( in_array( $op, $ops ) )
121135 return new self( $op, $pos );
122 - if( $op == '[' ) return new self( self::OArray, $pos );
123136 return null;
124137 } else {
125138 if( $op == '+' ) return new self( self::OSum, $pos );
@@ -155,6 +168,9 @@
156169 return $this->mNumArgs;
157170 if( in_array( $this->mOperator, self::$unaryOperators ) )
158171 return 1;
 172+ elseif( $this->mOperator == self::OBreak ||
 173+ $this->mOperator == self::OContinue )
 174+ return 0;
159175 else
160176 return 2;
161177 }
@@ -163,6 +179,10 @@
164180 $this->mNumArgs = $num;
165181 }
166182
 183+ public function setData( $data ) {
 184+ $this->mData = $data;
 185+ }
 186+
167187 public function isRightAssociative() {
168188 return $this->mOperator == self::OPow ||
169189 $this->mOperator == self::OSet;
@@ -224,16 +244,16 @@
225245
226246 class ISData {
227247 // Data types
228 - const DInt = 'int';
 248+ const DInt = 'int';
229249 const DString = 'string';
230250 const DNull = 'null';
231251 const DBool = 'bool';
232252 const DFloat = 'float';
233253 const DList = 'list';
234 -
 254+
235255 var $type;
236256 var $data;
237 -
 257+
238258 public function __construct( $type = self::DNull, $val = null ) {
239259 $this->type = $type;
240260 $this->data = $val;
@@ -350,7 +370,7 @@
351371
352372 public static function equals( $d1, $d2 ) {
353373 return $d1->type != self::DList && $d2->type != self::DList &&
354 - $d1->toString() === $d2->toString();
 374+ $d1->data == $d2->data;
355375 }
356376
357377 public static function unaryMinus( $data ) {
@@ -436,6 +456,37 @@
437457 return new ISData( self::DFloat, $a->toFloat() - $b->toFloat() );
438458 }
439459
 460+ public function setValueByIndices( $val, $indices ) {
 461+ if( $this->type == self::DNull && $indices[0] === null ) {
 462+ $this->type = self::DList;
 463+ $this->value = array();
 464+ $this->setValueByIndices( $val, $indices );
 465+ } elseif( $this->type == self::DList ) {
 466+ if( $indices[0] === null ) {
 467+ $this->data[] = $val;
 468+ } else {
 469+ $idx = $indices[0]->toInt();
 470+ if( $idx < 0 || $idx >= count( $this->data ) )
 471+ throw new ISUserVisibleException( 'outofbounds', 0, array( count( $this->data ), $index ) );
 472+ if( count( $indices ) > 1 )
 473+ $this->data[$idx]->setValueByIndices( $val, array_slice( $indices, 1 ) );
 474+ else
 475+ $this->data[$idx] = $val;
 476+ }
 477+ }
 478+ }
 479+
 480+ public function checkIssetByIndices( $indices ) {
 481+ if( $indices ) {
 482+ $idx = array_shift( $indices );
 483+ if( $this->type != self::DList || $idx >= count( $this->data ) )
 484+ return false;
 485+ return $this->checkIssetByIndices( $indices );
 486+ } else {
 487+ return true;
 488+ }
 489+ }
 490+
440491 /** Convert shorteners */
441492 public function toBool() {
442493 return self::castTypes( $this, self::DBool )->data;
@@ -476,9 +527,10 @@
477528 }
478529
479530 public function appendTokenCount( &$interpr ) {
 531+ global $wgInlineScriptsParserParams;
480532 $interpr->mParser->is_tokensCount += $this->mTokensCount;
481 - if( $interpr->mParser->is_tokensCount > $interpr->mLimits['tokens'] )
482 - throw new ISUserVisibleException( 'toomanytokens', $ast->getPos() );
 533+ if( $interpr->mParser->is_tokensCount > $wgInlineScriptsParserParams['limits']['tokens'] )
 534+ throw new ISUserVisibleException( 'toomanytokens', 0 );
483535 }
484536 }
485537
@@ -495,4 +547,8 @@
496548 $this->mPosition = $position;
497549 $this->mParams = $params;
498550 }
 551+
 552+ public function getExceptionID() {
 553+ return $this->mExceptionID;
 554+ }
499555 }
Index: trunk/extensions/InlineScripts/interpreter/Interpreter.php
@@ -14,15 +14,40 @@
1515 */
1616 const ParserVersion = 1;
1717
18 - var $mVars, $mOut, $mParser, $mFrame, $mCodeParser, $mLimits;
 18+ var $mVars, $mOut, $mParser, $mFrame, $mCodeParser;
1919
2020 // length,lcase,ccnorm,rmdoubles,specialratio,rmspecials,norm,count
2121 static $mFunctions = array(
 22+ 'out' => 'funcOut',
 23+
 24+ /* String functions */
2225 'lc' => 'funcLc',
23 - 'out' => 'funcOut',
 26+ 'uc' => 'funcUc',
 27+ 'ucfirst' => 'funcUcFirst',
 28+ 'urlencode' => 'funcUrlencode',
 29+ 'grammar' => 'funcGrammar',
 30+ 'plural' => 'funcPlural',
 31+ 'anchorencode' => 'funcAnchorEncode',
 32+ 'strlen' => 'funcStrlen',
 33+ 'substr' => 'funcSubstr',
 34+ 'strreplace' => 'funcStrreplace',
 35+ 'split' => 'funcSplit',
 36+
 37+ /* Array functions */
 38+ 'join' => 'funcJoin',
 39+ 'count' => 'funcCount',
 40+
 41+ /* Parser interaction functions */
2442 'arg' => 'funcArg',
2543 'args' => 'funcArgs',
2644 'istranscluded' => 'funcIsTranscluded',
 45+ 'parse' => 'funcParse',
 46+
 47+ /* Cast functions */
 48+ 'string' => 'castString',
 49+ 'int' => 'castInt',
 50+ 'float' => 'castFloat',
 51+ 'bool' => 'castBool',
2752 );
2853
2954 // Order is important. The punctuation-matching regex requires that
@@ -30,6 +55,8 @@
3156 // such errors.
3257 static $mOps = array(
3358 '!==', '!=', '!', // Inequality
 59+ '+=', '-=', // Setting 1
 60+ '*=', '/=', // Setting 2
3461 '**', '*', // Multiplication/exponentiation
3562 '/', '+', '-', '%', // Other arithmetic
3663 '&', '|', '^', // Logic
@@ -41,41 +68,45 @@
4269 '(', '[', '{', // Braces
4370 );
4471 static $mKeywords = array(
45 - 'in', 'true', 'false', 'null', 'contains', 'matches',
46 - 'if', 'then', 'else', 'foreach', 'do',
 72+ 'in', 'true', 'false', 'null', 'contains', 'break',
 73+ 'if', 'then', 'else', 'foreach', 'do', 'try', 'catch',
 74+ 'continue', 'isset', 'unset',
4775 );
4876
49 - public function __construct( $params ) {
 77+ public function __construct() {
 78+ global $wgInlineScriptsParserParams;
5079 $this->resetState();
51 - $this->mCodeParser = new $params['parserClass']( $this );
52 - $this->mLimits = $params['limits'];
 80+ $this->mCodeParser = new $wgInlineScriptsParserParams['parserClass']( $this );
5381 }
5482
5583 public function resetState() {
5684 $this->mVars = array();
57 - $this->mCode = '';
5885 $this->mOut = '';
5986 }
6087
6188 protected function checkRecursionLimit( $rec ) {
 89+ global $wgInlineScriptsParserParams;
6290 if( $rec > $this->mParser->is_maxDepth )
6391 $this->mParser->is_maxDepth = $rec;
64 - return $rec <= $this->mLimits['depth'];
 92+ return $rec <= $wgInlineScriptsParserParams['limits']['depth'];
6593 }
6694
6795 protected function increaseEvaluationsCount() {
 96+ global $wgInlineScriptsParserParams;
6897 $this->mParser->is_evalsCount++;
69 - return $this->mParser->is_evalsCount <= $this->mLimits['evaluations'];
 98+ return $this->mParser->is_evalsCount <= $wgInlineScriptsParserParams['limits']['evaluations'];
7099 }
71100
72101 public function increaseTokensCount() {
 102+ global $wgInlineScriptsParserParams;
73103 $this->mParser->is_tokensCount++;
74 - return $this->mParser->is_tokensCount <= $this->mLimits['tokens'];
 104+ return $this->mParser->is_tokensCount <= $wgInlineScriptsParserParams['limits']['tokens'];
75105 }
76106
77 - public function evaluateForOutput( $code, $parser, $frame ) {
 107+ public function evaluateForOutput( $code, $parser, $frame, $resetState = true ) {
78108 wfProfileIn( __METHOD__ );
79 - $this->resetState();
 109+ if( $resetState )
 110+ $this->resetState();
80111 $this->mParser = $parser;
81112 $this->mFrame = $frame;
82113
@@ -85,9 +116,10 @@
86117 return $this->mOut;
87118 }
88119
89 - public function evaluate( $code, $parser, $frame ) {
 120+ public function evaluate( $code, $parser, $frame, $resetState = true ) {
90121 wfProfileIn( __METHOD__ );
91 - $this->resetState();
 122+ if( $resetState )
 123+ $this->resetState();
92124 $this->mParser = $parser;
93125 $this->mFrame = $frame;
94126
@@ -97,18 +129,30 @@
98130 }
99131
100132 public function getCodeAST( $code ) {
101 - global $wgMemc;
 133+ global $parserMemc;
 134+ static $ASTCache;
 135+
 136+ wfProfileIn( __METHOD__ );
102137 $code = trim( $code );
103138
104139 $memcKey = 'isparser:ast:' . md5( $code );
105 - $cached = $wgMemc->get( $memcKey );
106 - if( $cached instanceof ISParserOutput && !$cached->isOutOfDate() ) {
 140+ if( isset( $ASTCache[$memcKey] ) ) {
 141+ wfProfileOut( __METHOD__ );
 142+ return $ASTCache[$memcKey];
 143+ }
 144+
 145+ $cached = $parserMemc->get( $memcKey );
 146+ if( @$cached instanceof ISParserOutput && !$cached->isOutOfDate() ) {
107147 $cached->appendTokenCount( $this );
 148+ $ASTCache[$memcKey] = $cached->getAST();
 149+ wfProfileOut( __METHOD__ );
108150 return $cached->getAST();
109151 }
110152
111153 $out = $this->mCodeParser->parse( $code );
112 - $wgMemc->set( $memcKey, $out );
 154+ $parserMemc->set( $memcKey, $out );
 155+ $ASTCache[$memcKey] = $out->getAST();
 156+ wfProfileOut( __METHOD__ );
113157 return $out->getAST();
114158 }
115159
@@ -204,15 +248,34 @@
205249
206250 /* Variable assignment */
207251 case ISOperatorNode::OSet:
208 - $data = $this->evaluateASTNode( $r, $rec + 1 );
209 - if( $l->getType() != ISASTNode::NodeData || $l->getType() == ISDataNode::DNData )
210 - throw new ISUserVisibleException( 'cantchangeconst', $pos );
211 - switch( $l->getDataType() ) {
212 - case ISDataNode::DNVariable:
213 - $this->mVars[$l->getVar()] = $data;
214 - break;
 252+ case ISOperatorNode::OSetAdd:
 253+ case ISOperatorNode::OSetSub:
 254+ case ISOperatorNode::OSetMul:
 255+ case ISOperatorNode::OSetDiv:
 256+ if( $l->isOp( ISOperatorNode::OArrayElement ) || $l->isOp( ISOperatorNode::OArrayElementSingle ) ) {
 257+ $datanode = $r;
 258+ $keys = array();
 259+ while( $l->isOp( ISOperatorNode::OArrayElement ) || $l->isOp( ISOperatorNode::OArrayElementSingle ) ) {
 260+ @list( $l, $r ) = $l->getChildren();
 261+ array_unshift( $keys, $r ? $r : null );
 262+ }
 263+ if( $l->getType() != ISASTNode::NodeData || $l->getType() == ISDataNode::DNData )
 264+ throw new ISUserVisibleException( 'cantchangeconst', $pos );
 265+ $array = $this->getDataNodeValue( new ISDataNode( $l->getVar(), 0 ) );
 266+ foreach( $keys as &$key )
 267+ if( $key )
 268+ $key = $this->evaluateASTNode( $key, $rec + 1 );
 269+ $val = $this->evaluateASTNode( $datanode, $rec + 1 );
 270+ $array->setValueByIndices( $val, $keys );
 271+ $this->mVars[$l->getVar()] = $array;
 272+ return $val;
 273+ } else {
 274+ if( $l->getType() != ISASTNode::NodeData || $l->getType() == ISDataNode::DNData )
 275+ throw new ISUserVisibleException( 'cantchangeconst', $pos );
 276+ $val = $this->getValueForSetting( @$this->mVars[$l->getVar()],
 277+ $this->evaluateASTNode( $r, $rec + 1 ), $op );
 278+ return $this->mVars[$l->getVar()] = $val;
215279 }
216 - return $data;
217280
218281 /* Arrays */
219282 case ISOperatorNode::OArray:
@@ -232,7 +295,7 @@
233296 if( $array->type != ISData::DList )
234297 throw new ISUserVisibleException( 'notanarray', $ast->getPos(), array( $array->type ) );
235298 if( count( $array->data ) <= $index )
236 - throw new ISUserVisibleException( 'outofbounds', $ast->getPos(), array( count( $array->data, $index ) ) );
 299+ throw new ISUserVisibleException( 'outofbounds', $ast->getPos(), array( count( $array->data ), $index ) );
237300 return $array->data[$index];
238301
239302 /* Flow control (if, foreach, etc) */
@@ -251,10 +314,13 @@
252315 list( $l, $r ) = $l->getChildren();
253316 if( $r->isOp( ISOperatorNode::OElse ) ) {
254317 list( $onTrue, $onFalse ) = $r->getChildren();
255 - if( $this->evaluateASTNode( $l )->toBool() )
256 - $this->evaluateASTNode( $onTrue );
 318+ if( $this->evaluateASTNode( $l, $rec + 1 )->toBool() )
 319+ $this->evaluateASTNode( $onTrue, $rec + 1 );
257320 else
258 - $this->evaluateASTNode( $onFalse );
 321+ $this->evaluateASTNode( $onFalse, $rec + 1 );
 322+ } else {
 323+ if( $this->evaluateASTNode( $l, $rec + 1 )->toBool() )
 324+ $this->evaluateASTNode( $r, $rec + 1 );
259325 }
260326 return new ISData();
261327 case ISOperatorNode::OForeach:
@@ -265,11 +331,79 @@
266332 if( $array->type != ISData::DList )
267333 throw new ISUserVisibleException( 'invalidforeach', $ast->getPos(), array( $array->type ) );
268334 foreach( $array->data as $element ) {
269 - $this->mVars[$ast->getData()] = $element;
270 - $this->evaluateASTNode( $r, $rec + 1 );
 335+ try {
 336+ $this->mVars[$ast->getData()] = $element;
 337+ $this->evaluateASTNode( $r, $rec + 1 );
 338+ } catch( ISUserVisibleException $e ) {
 339+ if( $e->getExceptionID() == 'break' )
 340+ break;
 341+ elseif( $e->getExceptionID() == 'continue' )
 342+ continue;
 343+ else
 344+ throw $e;
 345+ }
271346 }
272347 return new ISData();
273 -
 348+ case ISOperatorNode::OTry:
 349+ if( $l->isOp( ISOperatorNode::OCatch ) ) {
 350+ list( $code, $errorHandler ) = $l->getChildren();
 351+ try {
 352+ $val = $this->evaluateASTNode( $code, $rec + 1 );
 353+ } catch( ISUserVisibleException $e ) {
 354+ if( in_array( $e->getExceptionID(), array( 'break', 'continue' ) ) )
 355+ throw $e;
 356+ $varname = $l->getData();
 357+ $old = wfSetVar( $this->mVars[$varname],
 358+ new ISData( ISData::DString, $e->getExceptionID() ) );
 359+ $val = $this->evaluateASTNode( $errorHandler, $rec + 1 );
 360+ $this->mVars[$varname] = $old;
 361+ }
 362+ return $val;
 363+ } else {
 364+ try {
 365+ return $this->evaluateASTNode( $l, $rec + 1 );
 366+ } catch( ISUserVisibleException $e ) {
 367+ return new ISData();
 368+ }
 369+ }
 370+
 371+ /* break/continue */
 372+ case ISOperatorNode::OBreak:
 373+ throw new ISUserVisibleException( 'break', $ast->getPos() );
 374+ case ISOperatorNode::OContinue:
 375+ throw new ISUserVisibleException( 'continue', $ast->getPos() );
 376+
 377+ /* isset/unset */
 378+ case ISOperatorNode::OUnset:
 379+ if( $l->getType() == ISASTNode::NodeData && $l->getDataType() == ISDataNode::DNVariable ) {
 380+ if( isset( $this->mVars[$l->getVar()] ) )
 381+ unset( $this->mVars[$l->getVar()] );
 382+ break;
 383+ } else {
 384+ throw new ISUserVisibleException( 'cantchangeconst', $ast->getPos() );
 385+ }
 386+ case ISOperatorNode::OIsset:
 387+ if( $l->getType() == ISASTNode::NodeData && $l->getDataType() == ISDataNode::DNVariable ) {
 388+ return new ISData( ISData::DBool, isset( $this->mVars[$l->getVar()] ) );
 389+ } elseif( $l->isOp( ISOperatorNode::OArrayElement ) ) {
 390+ $indices = array();
 391+ while( $l->isOp( ISOperatorNode::OArrayElement ) ) {
 392+ list( $l, $r ) = $l->getChildren();
 393+ array_unshift( $indices, $r );
 394+ }
 395+ if( !($l->getType() == ISASTNode::NodeData && $l->getDataType() == ISDataNode::DNVariable) )
 396+ throw new ISUserVisibleException( 'cantchangeconst', $ast->getPos() );
 397+ foreach( $indices as &$idx )
 398+ $idx = $this->evaluateASTNode( $idx )->toInt();
 399+ $var = $l->getVar();
 400+
 401+ if( !isset( $this->mVars[$var] ) )
 402+ return new ISData( ISData::DBool, false );
 403+ return new ISData( ISData::DBool, $this->mVars[$var]->checkIssetByIndices( $indices ) );
 404+ } else {
 405+ throw new ISUserVisibleException( 'cantchangeconst', $ast->getPos() );
 406+ }
 407+
274408 /* Functions */
275409 case ISOperatorNode::OFunction:
276410 $args = array();
@@ -281,7 +415,7 @@
282416 array_unshift( $args, $l );
283417 }
284418 foreach( $args as &$arg )
285 - $arg = $this->evaluateASTNode( $arg );
 419+ $arg = $this->evaluateASTNode( $arg, $rec + 1 );
286420 $funcName = self::$mFunctions[$ast->getData()];
287421 $result = $this->$funcName( $args, $ast->getPos() );
288422 return $result;
@@ -290,7 +424,7 @@
291425 }
292426 }
293427
294 - function getDataNodeValue( $node ) {
 428+ protected function getDataNodeValue( $node ) {
295429 switch( $node->getDataType() ) {
296430 case ISDataNode::DNData:
297431 return $node->getData();
@@ -303,19 +437,30 @@
304438 }
305439 }
306440
307 - /** Functions */
308 - function funcLc( $args, $pos ) {
309 - global $wgContLang;
310 - if( !$args )
311 - throw new ISUserVisibleException( 'notenoughargs', $pos );
312 -
313 - return new ISData( ISData::DString, $wgContLang->lc( $args[0]->toString() ) );
 441+ protected function getValueForSetting( $old, $new, $set ) {
 442+ switch( $set ) {
 443+ case ISOperatorNode::OSetAdd:
 444+ return ISData::sum( $old, $new );
 445+ case ISOperatorNode::OSetSub:
 446+ return ISData::sub( $old, $new );
 447+ case ISOperatorNode::OSetMul:
 448+ return ISData::mulRel( $old, $new, '*', 0 );
 449+ case ISOperatorNode::OSetDiv:
 450+ return ISData::mulRel( $old, $new, '/', 0 );
 451+ default:
 452+ return $new;
 453+ }
314454 }
315455
316 - function funcOut( $args, $pos ) {
317 - if( !$args )
 456+ protected function checkParamsCount( $args, $pos, $count ) {
 457+ if( count( $args ) < $count )
318458 throw new ISUserVisibleException( 'notenoughargs', $pos );
 459+ }
319460
 461+ /** Functions */
 462+ protected function funcOut( $args, $pos ) {
 463+ $this->checkParamsCount( $args, $pos, 1 );
 464+
320465 for( $i = 0; $i < count( $args ); $i++ )
321466 $args[$i] = $args[$i]->toString();
322467 $str = implode( "\n", $args );
@@ -323,19 +468,149 @@
324469 return new ISData();
325470 }
326471
327 - function funcArg( $args, $pos ) {
328 - if( !$args )
329 - throw new ISUserVisibleException( 'notenoughargs', $pos );
 472+ protected function funcArg( $args, $pos ) {
 473+ $this->checkParamsCount( $args, $pos, 1 );
330474
331475 $argName = $args[0]->toString();
332 - return new ISData( ISData::DString, $this->mFrame->getArgument( $argName ) );
 476+ $default = isset( $args[1] ) ? $args[1] : new ISData();
 477+ if( $this->mFrame->getArgument( $argName ) === false )
 478+ return $default;
 479+ else
 480+ return new ISData( ISData::DString, $this->mFrame->getArgument( $argName ) );
333481 }
334482
335 - function funcArgs( $args, $pos ) {
 483+ protected function funcArgs( $args, $pos ) {
336484 return ISData::newFromPHPVar( $this->mFrame->getNumberedArguments() );
337485 }
338486
339 - function funcIsTranscluded( $args, $pos ) {
 487+ protected function funcIsTranscluded( $args, $pos ) {
340488 return new ISData( ISData::DBool, $this->mFrame->isTemplate() );
341489 }
 490+
 491+ protected function funcParse( $args, $pos ) {
 492+ $this->checkParamsCount( $args, $pos, 1 );
 493+
 494+ $text = $args[0]->toString();
 495+ $oldOT = $this->mParser->mOutputType;
 496+ $this->mParser->setOutputType( Parser::OT_PREPROCESS );
 497+ $parsed = $this->mParser->replaceVariables( $text, $this->mFrame );
 498+ $parsed = $this->mParser->mStripState->unstripBoth( $parsed );
 499+ $this->mParser->setOutputType( $oldOT );
 500+ return new ISData( ISData::DString, $parsed );
 501+ }
 502+
 503+ protected function funcLc( $args, $pos ) {
 504+ global $wgContLang;
 505+ $this->checkParamsCount( $args, $pos, 1 );
 506+ return new ISData( ISData::DString, $wgContLang->lc( $args[0]->toString() ) );
 507+ }
 508+
 509+ protected function funcUc( $args, $pos ) {
 510+ global $wgContLang;
 511+ $this->checkParamsCount( $args, $pos, 1 );
 512+ return new ISData( ISData::DString, $wgContLang->uc( $args[0]->toString() ) );
 513+ }
 514+
 515+ protected function funcUcFirst( $args, $pos ) {
 516+ global $wgContLang;
 517+ $this->checkParamsCount( $args, $pos, 1 );
 518+ return new ISData( ISData::DString, $wgContLang->ucfirst( $args[0]->toString() ) );
 519+ }
 520+
 521+ protected function funcUrlencode( $args, $pos ) {
 522+ $this->checkParamsCount( $args, $pos, 1 );
 523+ return new ISData( ISData::DString, urlencode( $args[0]->toString() ) );
 524+ }
 525+
 526+ protected function funcAnchorEncode( $args, $pos ) {
 527+ $this->checkParamsCount( $args, $pos, 1 );
 528+
 529+ $s = urlencode( $args[0]->toString() );
 530+ $s = strtr( $s, array( '%' => '.', '+' => '_' ) );
 531+ $s = str_replace( '.3A', ':', $s );
 532+
 533+ return new ISData( ISData::DString, $s );
 534+ }
 535+
 536+ protected function funcGrammar( $args, $pos ) {
 537+ $this->checkParamsCount( $args, $pos, 2 );
 538+ list( $case, $word ) = $args;
 539+ $res = $this->mParser->getFunctionLang()->convertGrammar(
 540+ $word->toString(), $case->toString() );
 541+ return new ISData( ISData::DString, $res );
 542+ }
 543+
 544+ protected function funcPlural( $args, $pos ) {
 545+ $this->checkParamsCount( $args, $pos, 2 );
 546+ $num = $args[0]->toInt();
 547+ for( $i = 1; $i < count( $args ); $i++ )
 548+ $forms[] = $args[$i]->toString();
 549+ $res = $this->mParser->getFunctionLang()->convertPlural( $num, $forms );
 550+ return new ISData( ISData::DString, $res );
 551+ }
 552+
 553+ protected function funcStrlen( $args, $pos ) {
 554+ $this->checkParamsCount( $args, $pos, 1 );
 555+ return new ISData( ISData::DInt, mb_strlen( $args[0]->toString() ) );
 556+ }
 557+
 558+ protected function funcSubstr( $args, $pos ) {
 559+ $this->checkParamsCount( $args, $pos, 3 );
 560+ $s = $args[0]->toString();
 561+ $start = $args[1]->toInt();
 562+ $end = $args[2]->toInt();
 563+ return new ISData( ISData::DString, mb_substr( $s, $start, $end ) );
 564+ }
 565+
 566+ protected function funcStrreplace( $args, $pos ) {
 567+ $this->checkParamsCount( $args, $pos, 3 );
 568+ $s = $args[0]->toString();
 569+ $old = $args[1]->toString();
 570+ $new = $args[2]->toString();
 571+ return new ISData( ISData::DString, str_replace( $old, $new, $s ) );
 572+ }
 573+
 574+ protected function funcSplit( $args, $pos ) {
 575+ $this->checkParamsCount( $args, $pos, 2 );
 576+ $list = explode( $args[0]->toString(), $args[1]->toString() );
 577+ return ISData::newFromPHPVar( $list );
 578+ }
 579+
 580+ protected function funcJoin( $args, $pos ) {
 581+ $this->checkParamsCount( $args, $pos, 2 );
 582+ $seperator = $args[0]->toString();
 583+ if( $args[1]->type == ISData::DList ) {
 584+ $bits = $args[1]->data;
 585+ } else {
 586+ $bits = array_slice( $args, 1 );
 587+ }
 588+ foreach( $bits as &$bit )
 589+ $bit = $bit->toString();
 590+ return new ISData( ISData::DString, implode( $seperator, $bits ) );
 591+ }
 592+
 593+ protected function funcCount( $args, $pos ) {
 594+ $this->checkParamsCount( $args, $pos, 1 );
 595+ return new ISData( ISData::DInt, count( $args[0]->toList()->data ) );
 596+ }
 597+
 598+ protected function castString( $args, $pos ) {
 599+ $this->checkParamsCount( $args, $pos, 1 );
 600+ return ISData::castTypes( $args[0], ISData::DString );
 601+ }
 602+
 603+ protected function castInt( $args, $pos ) {
 604+ $this->checkParamsCount( $args, $pos, 1 );
 605+ return ISData::castTypes( $args[0], ISData::DInt );
 606+ }
 607+
 608+ protected function castFloat( $args, $pos ) {
 609+ $this->checkParamsCount( $args, $pos, 1 );
 610+ return ISData::castTypes( $args[0], ISData::DFloat );
 611+ }
 612+
 613+ protected function castBool( $args, $pos ) {
 614+ $this->checkParamsCount( $args, $pos, 1 );
 615+ return ISData::castTypes( $args[0], ISData::DBool );
 616+ }
342617 }
Index: trunk/extensions/InlineScripts/interpreterTests.txt
@@ -0,0 +1,396 @@
 2+# Test cases for MediaWiki inline scripts engine
 3+
 4+!! test
 5+Basic mathematics
 6+!! input
 7+{{#inline:2 + 2 * 2 ** 2 - 3 * 7 % 5}}
 8+!! result
 9+<p>9
 10+</p>
 11+!! end
 12+
 13+!! test
 14+String contecation and out()
 15+!! input
 16+<wikiscript>
 17+out( "foo" + "bar" );
 18+</wikiscript>
 19+!! result
 20+<p>foobar
 21+</p>
 22+!! end
 23+
 24+!! test
 25+Multiple variable assignment
 26+!! input
 27+{{#inline: a = b = 3; a + b }}
 28+!! result
 29+<p>6
 30+</p>
 31+!! end
 32+
 33+!! test
 34+Assigment with arithmetics (+=, -=, etc)
 35+!! input
 36+{{#inline: a = 2; a += 3; a -= 7; a }}
 37+!! result
 38+<p>-2
 39+</p>
 40+!! end
 41+
 42+!! test
 43+Boolean shortcut
 44+!! input
 45+<wikiscript>
 46+!(b = 2) | (b = 3) | (b = 4);
 47+out( b );
 48+</wikiscript>
 49+!! result
 50+<p>3
 51+</p>
 52+!! end
 53+
 54+!! test
 55+Equality
 56+!! input
 57+{{#inline: "2" == 2 & "2" !== 2 & 4 === (2 + 2) &
 58+null == "" & false == null & 0 == "" }}
 59+!! result
 60+<p>1
 61+</p>
 62+!! end
 63+
 64+!! test
 65+Comments
 66+!! input
 67+{{#inline: 2 + /* 2 + */ 2}}
 68+!! result
 69+<p>4
 70+</p>
 71+!!end
 72+
 73+!! test
 74+Comparsions
 75+!! input
 76+{{#inline: 2 > 1 & 2 >= 2 & 2 <= 2 & 1 < 2}}
 77+!! result
 78+<p>1
 79+</p>
 80+!! end
 81+
 82+!! test
 83+Tag integration
 84+!! input
 85+{{lc:<wikiscript>out("AA")</wikiscript>}}
 86+!! result
 87+<p>aa
 88+</p>
 89+!! end
 90+
 91+!! test
 92+Conditions (?)
 93+!! input
 94+{{#inline: 2 + 2 == 4 ? "a" : "b"}}
 95+!! result
 96+<p>a
 97+</p>
 98+!! end
 99+
 100+!! test
 101+Conditions (if-then, if-then-else)
 102+!! input
 103+<wikiscript>
 104+if 2 * 7 > 3 * 4 then
 105+ a = 7
 106+else {
 107+ a = 10;
 108+};
 109+
 110+if a ** 2 < 50 then
 111+ out( "ok" );
 112+</wikiscript>
 113+!! result
 114+<p>ok
 115+</p>
 116+!! end
 117+
 118+!! article
 119+Template:Bullets
 120+!! text
 121+<wikiscript>
 122+foreach a in args() do
 123+ out( "* " + a + "\n" );
 124+</wikiscript>
 125+!! endarticle
 126+
 127+!! test
 128+args() function
 129+!! input
 130+{{bullets|a|b|c}}
 131+!! result
 132+<ul><li> a
 133+</li><li> b
 134+</li><li> c
 135+</li></ul>
 136+
 137+!! end
 138+
 139+!! article
 140+Template:TranscludedSwitch
 141+!! text
 142+{{#inline: isTranscluded() ? arg(1) : "?!"}}
 143+!! endarticle
 144+
 145+!! test
 146+isTranscluded()/arg() check
 147+!! input
 148+{{TranscludedSwitch|11}}
 149+!! result
 150+<p>11
 151+</p>
 152+!! end
 153+
 154+!! test
 155+Empty argument handling check
 156+!! input
 157+{{#inline: arg("test") === null}}
 158+!! result
 159+<p>1
 160+</p>
 161+!! end
 162+
 163+!! test
 164+Casts
 165+!! input
 166+{{#inline: string(float(2)) === "2.0" & int(7.99) === 7}}
 167+!! result
 168+<p>1
 169+</p>
 170+!! end
 171+
 172+!! test
 173+Exception handling
 174+!! input
 175+{{#inline: try 2 / 0 catch e e }}
 176+!! result
 177+<p>dividebyzero
 178+</p>
 179+!! end
 180+
 181+!! article
 182+Template:Numberofsomething
 183+!! text
 184+721
 185+!! endarticle
 186+
 187+!! test
 188+Template access via parse()
 189+!! input
 190+<wikiscript noparse="1">
 191+numofsmth = int( parse( '{{numberofsomething}}' ) ) + 279;
 192+out( '{{numberofsomething}}: ' + numofsmth );
 193+</wikiscript>
 194+!! result
 195+<p>{{numberofsomething}}: 1000
 196+</p>
 197+!! end
 198+
 199+!! test
 200+String functions 1
 201+!! input
 202+{{#inline: lc( 'FOO' ) == 'foo' & uc( 'foo' ) == 'FOO' &
 203+ucfirst( 'bar' ) == 'Bar' & urlencode( 'a="b"' ) == "a%3D%22b%22" }}
 204+!! result
 205+<p>1
 206+</p>
 207+!! end
 208+
 209+!! test
 210+String functions 2
 211+!! input
 212+{{#inline: strlen( "тест" ) == 4 & substr( "слово", 1, 2 ) == "ло" &
 213+ strreplace( "abcd", 'bc', 'ad' ) == 'aadd'
 214+}}
 215+!! result
 216+<p>1
 217+</p>
 218+!! end
 219+
 220+!! test
 221+split()/join()
 222+!! input
 223+{{#inline: join( '!', split( ':', 'a:b:c:d' ) ) + join( ' ', '', 'e', 'f' ) }}
 224+!! result
 225+<p>a!b!c!d e f
 226+</p>
 227+!! end
 228+
 229+!! test
 230+isset/unset
 231+!! input
 232+<wikiscript>
 233+a = null;
 234+b = 1;
 235+unset( b );
 236+out( 'a: ' + isset( a ) + '; b: ' + int( isset( b ) ) );
 237+!! result
 238+<p>a: 1; b: 0
 239+</p>
 240+!! end
 241+
 242+#
 243+## Lists
 244+#
 245+!! test
 246+Lists: basics
 247+!! input
 248+<wikiscript>
 249+a = [ b = "a", b = "b", b = "c" ];
 250+out( a[1] + b )
 251+</wikiscript>
 252+!! result
 253+<p>bc
 254+</p>
 255+!! end
 256+
 257+!! test
 258+Lists: foreach
 259+!! input
 260+<wikiscript>
 261+a = [ 1, 2, 3, 4, 5 ];
 262+foreach n in a do
 263+ out( n * n + "\n\n");
 264+</wikiscript>
 265+!! result
 266+<p>1
 267+</p><p>4
 268+</p><p>9
 269+</p><p>16
 270+</p><p>25
 271+</p>
 272+!! end
 273+
 274+!! test
 275+List merging
 276+!! input
 277+<wikiscript>
 278+foreach element in [ 7, 4 ] + [ 2, 8 ] do
 279+ out( element );
 280+</wikiscript>
 281+!! result
 282+<p>7428
 283+</p>
 284+!! end
 285+
 286+!! test
 287+Lists: loop control (break/continue)
 288+!! input
 289+<wikiscript>
 290+a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ];
 291+foreach e in a do {
 292+ if e >= 6 & e < 9 then continue;
 293+ out( e );
 294+};
 295+foreach e in a do {
 296+ if e == 3 then break;
 297+ out( e );
 298+}
 299+</wikiscript>
 300+!! result
 301+<p>12345912
 302+</p>
 303+!! end
 304+
 305+!! test
 306+Lists: changing value of an element
 307+!! input
 308+<wikiscript>
 309+a = [ [ 2, 3 ], [ 5, 6 ], 7 ];
 310+a[1][0] = 3;
 311+a[0][] = 1;
 312+out( a );
 313+</wikiscript>
 314+!! result
 315+<p>2
 316+3
 317+1
 318+</p><p>3
 319+6
 320+</p><p>7
 321+</p>
 322+!! end
 323+
 324+!! test
 325+Lists: isset
 326+!! input
 327+<wikiscript>
 328+lst = [ 'a', 'b', 'c' ];
 329+out( isset( lst[1] ) + isset( lst[2] ) + isset( list[3] ) );
 330+</wikiscript>
 331+!! result
 332+<p>2
 333+</p>
 334+!! end
 335+
 336+#
 337+## Error handling
 338+#
 339+!! test
 340+Error handling: unexcepted token
 341+!! input
 342+{{#inline: 2 2}}
 343+!! result
 344+<p><strong class="error">Unexpected token at char 3</strong>
 345+</p>
 346+!! end
 347+
 348+!! test
 349+Error handling: missing second argument
 350+!! input
 351+{{#inline: 2 + }}
 352+!! result
 353+<p><strong class="error">Not enough aruments for operator at char 3</strong>
 354+</p>
 355+!! end
 356+
 357+!! test
 358+Error handling: token limit
 359+!! config
 360+wgInlineScriptsParserParams=array('parserClass'=>'ISCodeParserShuntingYard', 'limits' => array( 'tokens' => 10, 'evaluations' => 1000, 'depth' => 100 ) )
 361+!! input
 362+{{#inline: 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2}}
 363+!! result
 364+<p><strong class="error">Exceeded tokens limit</strong>
 365+</p>
 366+!! end
 367+
 368+!! test
 369+Error handling: evaluations limit
 370+!! config
 371+wgInlineScriptsParserParams=array('parserClass'=>'ISCodeParserShuntingYard', 'limits' => array( 'tokens' => 25000, 'evaluations' => 5, 'depth' => 100 ) )
 372+!! input
 373+{{#inline: 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2}}
 374+!! result
 375+<p><strong class="error">Exceeded evaluations limit</strong>
 376+</p>
 377+!! end
 378+
 379+!! test
 380+Error handling: AST depth limit
 381+!! config
 382+wgInlineScriptsParserParams=array('parserClass'=>'ISCodeParserShuntingYard', 'limits' => array( 'tokens' => 25000, 'evaluations' => 1000, 'depth' => 5 ) )
 383+!! input
 384+{{#inline: 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 * 2 + 2 + 2}}
 385+!! result
 386+<p><strong class="error">Too deep abstract syntax tree</strong>
 387+</p>
 388+!! end
 389+
 390+!! test
 391+Error handling: missing arguments
 392+!! input
 393+{{#inline: arg()}}
 394+!! result
 395+<p><strong class="error">Not enough arguments for function at char 3</strong>
 396+</p>
 397+!! end
Property changes on: trunk/extensions/InlineScripts/interpreterTests.txt
___________________________________________________________________
Name: svn:eol-style
1398 + native
Index: trunk/extensions/InlineScripts/InlineScripts.i18n.php
@@ -29,6 +29,9 @@
3030 'inlinescripts-exception-unexceptedop' => 'Unexpected operator $2',
3131 'inlinescripts-exception-notenoughargs' => 'Not enough arguments for function at char $1',
3232 'inlinescripts-exception-notenoughopargs' => 'Not enough aruments for operator at char $1',
 33+ 'inlinescripts-exception-dividebyzero' => 'Division by zero at char $1',
 34+ 'inlinescripts-exception-break' => '"break" called outside of foreach at char $1',
 35+ 'inlinescripts-exception-continue' => '"continue" called outside of foreach at char $1',
3336 );
3437
3538 // == Magic words ==
Index: trunk/extensions/InlineScripts/InlineScripts.php
@@ -36,6 +36,7 @@
3737 $wgExtensionMessagesFiles['InlineScripts'] = $dir . 'InlineScripts.i18n.php';
3838 $wgAutoloadClasses['InlineScriptInterpreter'] = $dir . 'interpreter/Interpreter.php';
3939 $wgAutoloadClasses['ISCodeParserShuntingYard'] = $dir . 'interpreter/ParserShuntingYard.php';
 40+$wgParserTestFiles[] = $dir . 'interpreterTests.txt';
4041 $wgHooks['ParserFirstCallInit'][] = 'InlineScriptsHooks::setupParserHook';
4142 $wgHooks['ParserClearState'][] = 'InlineScriptsHooks::clearState';
4243 $wgHooks['ParserLimitReport'][] = 'InlineScriptsHooks::reportLimits';
@@ -50,12 +51,12 @@
5152 * Maximal amount of tokens (strings, keywords, numbers, operators,
5253 * but not whitespace) to be parsed.
5354 */
54 - 'tokens' => 20000,
 55+ 'tokens' => 25000,
5556 /**
5657 * Maximal amount of operations (multiplications, comarsionss, function
5758 * calls) to be done.
5859 */
59 - 'evaluations' => 5000,
 60+ 'evaluations' => 10000,
6061 /**
6162 * Maximal depth of recursion when evaluating the parser tree. For
6263 * example 2 + 2 * 2 ** 2 is parsed to (2 + (2 * (2 ** 2))) and needs
@@ -72,7 +73,7 @@
7374 * Register parser hook
7475 */
7576 public static function setupParserHook( &$parser ) {
76 - $parser->setFunctionHook( 'script', 'InlineScriptsHooks::scriptHook', SFH_OBJECT_ARGS );
 77+ $parser->setFunctionTagHook( 'wikiscript', 'InlineScriptsHooks::scriptHook', SFH_OBJECT_ARGS );
7778 $parser->setFunctionHook( 'inline', 'InlineScriptsHooks::inlineHook', SFH_OBJECT_ARGS );
7879 return true;
7980 }
@@ -85,26 +86,34 @@
8687 }
8788
8889 public static function inlineHook( &$parser, $frame, $args ) {
 90+ wfProfileIn( __METHOD__ );
8991 $scriptParser = self::getParser();
9092 try {
9193 $result = $scriptParser->evaluate( $parser->mStripState->unstripBoth( $args[0] ),
9294 $parser, $frame );
9395 } catch( ISException $e ) {
9496 $msg = nl2br( htmlspecialchars( $e->getMessage() ) );
 97+ wfProfileOut( __METHOD__ );
9598 return "<strong class=\"error\">{$msg}</strong>";
9699 }
 100+ wfProfileOut( __METHOD__ );
97101 return trim( $result );
98102 }
99103
100 - public static function scriptHook( &$parser, $frame, $args ) {
 104+ public static function scriptHook( &$parser, $frame, $code, $attribs ) {
 105+ wfProfileIn( __METHOD__ );
101106 $scriptParser = self::getParser();
102107 try {
103 - $result = $scriptParser->evaluateForOutput( $parser->mStripState->unstripBoth( $args[0] ),
104 - $parser, $frame );
 108+ $result = $scriptParser->evaluateForOutput( $code, $parser, $frame );
105109 } catch( ISException $e ) {
106110 $msg = nl2br( htmlspecialchars( $e->getMessage() ) );
 111+ wfProfileOut( __METHOD__ );
107112 return "<strong class=\"error\">{$msg}</strong>";
108113 }
 114+ if( !(isset( $attribs['noparse'] ) && $attribs['noparse']) ) {
 115+ $result = $parser->replaceVariables( $result, $frame );
 116+ }
 117+ wfProfileOut( __METHOD__ );
109118 return trim( $result );
110119 }
111120
@@ -119,9 +128,8 @@
120129 }
121130
122131 public static function getParser() {
123 - global $wgInlineScriptsParserParams;
124132 if( !self::$scriptParser )
125 - self::$scriptParser = new InlineScriptInterpreter( $wgInlineScriptsParserParams );
 133+ self::$scriptParser = new InlineScriptInterpreter();
126134 return self::$scriptParser;
127135 }
128136 }

Status & tagging log