r89318 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r89317‎ | r89318 | r89319 >
Date:02:44, 2 June 2011
Author:tstarling
Status:ok (Comments)
Tags:
Comment:
Explored some ideas for HipHop optimisation. Made a preprocessor implementation, based on a copy of Preprocessor_Hash, with a preprocessToObj() which is optimised. It takes 33% less time than Preprocessor_Hash for a certain realistic test case (the Barack Obama article). Some notes about what I did:
* Set EnableHipHopSyntax=true to enable string and integer type hints. I gave the file a .hphp extension to avoid false alarms in syntax checking scripts.
* Made sure almost all the local variables in preprocessToObj() have a specific type, instead of being variants. This is useful for integers, but has the largest impact for objects, since dynamic method calls can be avoided.
* Stopped using extract() since it forces all local variables to be variants, and adds some hashtable initialisation overhead.
* Found a way to cast a variant to a specific object class, by abusing argument type hinting. The method does not require special syntax; it is harmless in Zend PHP.
* Wrapped various internal function calls with type casts. strspn() and substr() need to be wrapped with intval() and strval() respectively, since they return a variant to support special error return values. HipHop isn't smart enough to know whether the error case will be triggered.
* Replaced most instances of double-equals with triple-equals. Profiling indicates that this makes a very large difference when comparing strings, much more so than in Zend.
Modified paths:
  • /trunk/phase3/includes/AutoLoader.php (modified) (history)
  • /trunk/phase3/includes/parser/Preprocessor_HipHop.hphp (added) (history)
  • /trunk/phase3/maintenance/hiphop/compiler.conf (modified) (history)

Diff [purge]

Index: trunk/phase3/maintenance/hiphop/compiler.conf
@@ -1,3 +1,5 @@
22 GenerateSourceInfo = true
33 EnableEval = 2
44 AllDynamic = true
 5+EnableHipHopSyntax = true
 6+EnableHipHopExperimentalSyntax = true
Index: trunk/phase3/includes/parser/Preprocessor_HipHop.hphp
@@ -0,0 +1,1941 @@
 2+<?php
 3+/**
 4+ * A preprocessor optimised for HipHop, using HipHop-specific syntax.
 5+ * vim: ft=php
 6+ *
 7+ * @file
 8+ * @ingroup Parser
 9+ */
 10+
 11+/**
 12+ * @ingroup Parser
 13+ */
 14+class Preprocessor_HipHop implements Preprocessor {
 15+ /**
 16+ * @var Parser
 17+ */
 18+ var $parser;
 19+
 20+ const CACHE_VERSION = 1;
 21+
 22+ function __construct( $parser ) {
 23+ $this->parser = $parser;
 24+ }
 25+
 26+ /**
 27+ * @return PPFrame_HipHop
 28+ */
 29+ function newFrame() {
 30+ return new PPFrame_HipHop( $this );
 31+ }
 32+
 33+ /**
 34+ * @param $args
 35+ * @return PPCustomFrame_HipHop
 36+ */
 37+ function newCustomFrame( array $args ) {
 38+ return new PPCustomFrame_HipHop( $this, $args );
 39+ }
 40+
 41+ /**
 42+ * @param $values array
 43+ * @return PPNode_HipHop_Array
 44+ */
 45+ function newPartNodeArray( $values ) {
 46+ $list = array();
 47+
 48+ foreach ( $values as $k => $val ) {
 49+ $partNode = new PPNode_HipHop_Tree( 'part' );
 50+ $nameNode = new PPNode_HipHop_Tree( 'name' );
 51+
 52+ if ( is_int( $k ) ) {
 53+ $nameNode->addChild( new PPNode_HipHop_Attr( 'index', $k ) );
 54+ $partNode->addChild( $nameNode );
 55+ } else {
 56+ $nameNode->addChild( new PPNode_HipHop_Text( $k ) );
 57+ $partNode->addChild( $nameNode );
 58+ $partNode->addChild( new PPNode_HipHop_Text( '=' ) );
 59+ }
 60+
 61+ $valueNode = new PPNode_HipHop_Tree( 'value' );
 62+ $valueNode->addChild( new PPNode_HipHop_Text( $val ) );
 63+ $partNode->addChild( $valueNode );
 64+
 65+ $list[] = $partNode;
 66+ }
 67+
 68+ $node = new PPNode_HipHop_Array( $list );
 69+ return $node;
 70+ }
 71+
 72+ /**
 73+ * Preprocess some wikitext and return the document tree.
 74+ * This is the ghost of Parser::replace_variables().
 75+ *
 76+ * @param $text String: the text to parse
 77+ * @param $flags Integer: bitwise combination of:
 78+ * Parser::PTD_FOR_INCLUSION Handle <noinclude>/<includeonly> as if the text is being
 79+ * included. Default is to assume a direct page view.
 80+ *
 81+ * The generated DOM tree must depend only on the input text and the flags.
 82+ * The DOM tree must be the same in OT_HTML and OT_WIKI mode, to avoid a regression of bug 4899.
 83+ *
 84+ * Any flag added to the $flags parameter here, or any other parameter liable to cause a
 85+ * change in the DOM tree for a given text, must be passed through the section identifier
 86+ * in the section edit link and thus back to extractSections().
 87+ *
 88+ * The output of this function is currently only cached in process memory, but a persistent
 89+ * cache may be implemented at a later date which takes further advantage of these strict
 90+ * dependency requirements.
 91+ *
 92+ * @return PPNode_HipHop_Tree
 93+ */
 94+ function preprocessToObj( string $text, int $flags = 0 ) {
 95+ wfProfileIn( __METHOD__ );
 96+
 97+ // Check cache.
 98+ global $wgMemc, $wgPreprocessorCacheThreshold;
 99+
 100+ $cacheable = ($wgPreprocessorCacheThreshold !== false && strlen( $text ) > $wgPreprocessorCacheThreshold);
 101+ if ( $cacheable ) {
 102+ wfProfileIn( __METHOD__.'-cacheable' );
 103+
 104+ $cacheKey = strval( wfMemcKey( 'preprocess-hash', md5($text), $flags ) );
 105+ $cacheValue = strval( $wgMemc->get( $cacheKey ) );
 106+ if ( $cacheValue !== '' ) {
 107+ $version = substr( $cacheValue, 0, 8 );
 108+ if ( intval( $version ) == self::CACHE_VERSION ) {
 109+ $hash = unserialize( substr( $cacheValue, 8 ) );
 110+ // From the cache
 111+ wfDebugLog( "Preprocessor",
 112+ "Loaded preprocessor hash from memcached (key $cacheKey)" );
 113+ wfProfileOut( __METHOD__.'-cacheable' );
 114+ wfProfileOut( __METHOD__ );
 115+ return $hash;
 116+ }
 117+ }
 118+ wfProfileIn( __METHOD__.'-cache-miss' );
 119+ }
 120+
 121+ $rules = array(
 122+ '{' => array(
 123+ 'end' => '}',
 124+ 'names' => array(
 125+ 2 => 'template',
 126+ 3 => 'tplarg',
 127+ ),
 128+ 'min' => 2,
 129+ 'max' => 3,
 130+ ),
 131+ '[' => array(
 132+ 'end' => ']',
 133+ 'names' => array( 2 => 'LITERAL' ),
 134+ 'min' => 2,
 135+ 'max' => 2,
 136+ )
 137+ );
 138+
 139+ $forInclusion = (bool)( $flags & Parser::PTD_FOR_INCLUSION );
 140+
 141+ $xmlishElements = (array)$this->parser->getStripList();
 142+ $enableOnlyinclude = false;
 143+ if ( $forInclusion ) {
 144+ $ignoredTags = array( 'includeonly', '/includeonly' );
 145+ $ignoredElements = array( 'noinclude' );
 146+ $xmlishElements[] = 'noinclude';
 147+ if ( strpos( $text, '<onlyinclude>' ) !== false && strpos( $text, '</onlyinclude>' ) !== false ) {
 148+ $enableOnlyinclude = true;
 149+ }
 150+ } else if ( $this->parser->ot['wiki'] ) {
 151+ $ignoredTags = array( 'noinclude', '/noinclude', 'onlyinclude', '/onlyinclude', 'includeonly', '/includeonly' );
 152+ $ignoredElements = array();
 153+ } else {
 154+ $ignoredTags = array( 'noinclude', '/noinclude', 'onlyinclude', '/onlyinclude' );
 155+ $ignoredElements = array( 'includeonly' );
 156+ $xmlishElements[] = 'includeonly';
 157+ }
 158+ $xmlishRegex = implode( '|', array_merge( $xmlishElements, $ignoredTags ) );
 159+
 160+ // Use "A" modifier (anchored) instead of "^", because ^ doesn't work with an offset
 161+ $elementsRegex = "~($xmlishRegex)(?:\s|\/>|>)|(!--)~iA";
 162+
 163+ $stack = new PPDStack_HipHop;
 164+
 165+ $searchBase = "[{<\n";
 166+ $revText = strrev( $text ); // For fast reverse searches
 167+
 168+ $i = 0; # Input pointer, starts out pointing to a pseudo-newline before the start
 169+ $accum = $stack->getAccum(); # Current accumulator
 170+ $headingIndex = 1;
 171+ $stackFlags = array(
 172+ 'findPipe' => false, # True to take notice of pipe characters
 173+ 'findEquals' => false, # True to find equals signs in arguments
 174+ 'inHeading' => false, # True if $i is inside a possible heading
 175+ );
 176+ $noMoreGT = false; # True if there are no more greater-than (>) signs right of $i
 177+ $findOnlyinclude = $enableOnlyinclude; # True to ignore all input up to the next <onlyinclude>
 178+ $fakeLineStart = true; # Do a line-start run without outputting an LF character
 179+
 180+ while ( true ) {
 181+ //$this->memCheck();
 182+
 183+ if ( $findOnlyinclude ) {
 184+ // Ignore all input up to the next <onlyinclude>
 185+ $variantStartPos = strpos( $text, '<onlyinclude>', $i );
 186+ if ( $variantStartPos === false ) {
 187+ // Ignored section runs to the end
 188+ $accum->addNodeWithText( 'ignore', strval( substr( $text, $i ) ) );
 189+ break;
 190+ }
 191+ $startPos1 = intval( $variantStartPos );
 192+ $tagEndPos = $startPos1 + strlen( '<onlyinclude>' ); // past-the-end
 193+ $accum->addNodeWithText( 'ignore', strval( substr( $text, $i, $tagEndPos - $i ) ) );
 194+ $i = $tagEndPos;
 195+ $findOnlyinclude = false;
 196+ }
 197+
 198+ if ( $fakeLineStart ) {
 199+ $found = 'line-start';
 200+ $curChar = '';
 201+ } else {
 202+ # Find next opening brace, closing brace or pipe
 203+ $search = $searchBase;
 204+ if ( $stack->top === false ) {
 205+ $currentClosing = '';
 206+ } else {
 207+ $currentClosing = strval( $stack->getTop()->close );
 208+ $search .= $currentClosing;
 209+ }
 210+ if ( $stackFlags['findPipe'] ) {
 211+ $search .= '|';
 212+ }
 213+ if ( $stackFlags['findEquals'] ) {
 214+ // First equals will be for the template
 215+ $search .= '=';
 216+ }
 217+ $rule = null;
 218+ # Output literal section, advance input counter
 219+ $literalLength = intval( strcspn( $text, $search, $i ) );
 220+ if ( $literalLength > 0 ) {
 221+ $accum->addLiteral( strval( substr( $text, $i, $literalLength ) ) );
 222+ $i += $literalLength;
 223+ }
 224+ if ( $i >= strlen( $text ) ) {
 225+ if ( $currentClosing === "\n" ) {
 226+ // Do a past-the-end run to finish off the heading
 227+ $curChar = '';
 228+ $found = 'line-end';
 229+ } else {
 230+ # All done
 231+ break;
 232+ }
 233+ } else {
 234+ $curChar = $text[$i];
 235+ if ( $curChar === '|' ) {
 236+ $found = 'pipe';
 237+ } elseif ( $curChar === '=' ) {
 238+ $found = 'equals';
 239+ } elseif ( $curChar === '<' ) {
 240+ $found = 'angle';
 241+ } elseif ( $curChar === "\n" ) {
 242+ if ( $stackFlags['inHeading'] ) {
 243+ $found = 'line-end';
 244+ } else {
 245+ $found = 'line-start';
 246+ }
 247+ } elseif ( $curChar === $currentClosing ) {
 248+ $found = 'close';
 249+ } elseif ( isset( $rules[$curChar] ) ) {
 250+ $found = 'open';
 251+ $rule = $rules[$curChar];
 252+ } else {
 253+ # Some versions of PHP have a strcspn which stops on null characters
 254+ # Ignore and continue
 255+ ++$i;
 256+ continue;
 257+ }
 258+ }
 259+ }
 260+
 261+ if ( $found === 'angle' ) {
 262+ $matches = false;
 263+ // Handle </onlyinclude>
 264+ if ( $enableOnlyinclude
 265+ && substr( $text, $i, strlen( '</onlyinclude>' ) ) === '</onlyinclude>' )
 266+ {
 267+ $findOnlyinclude = true;
 268+ continue;
 269+ }
 270+
 271+ // Determine element name
 272+ if ( !preg_match( $elementsRegex, $text, $matches, 0, $i + 1 ) ) {
 273+ // Element name missing or not listed
 274+ $accum->addLiteral( '<' );
 275+ ++$i;
 276+ continue;
 277+ }
 278+ // Handle comments
 279+ if ( isset( $matches[2] ) && $matches[2] === '!--' ) {
 280+ // To avoid leaving blank lines, when a comment is both preceded
 281+ // and followed by a newline (ignoring spaces), trim leading and
 282+ // trailing spaces and one of the newlines.
 283+
 284+ // Find the end
 285+ $variantEndPos = strpos( $text, '-->', $i + 4 );
 286+ if ( $variantEndPos === false ) {
 287+ // Unclosed comment in input, runs to end
 288+ $inner = strval( substr( $text, $i ) );
 289+ $accum->addNodeWithText( 'comment', $inner );
 290+ $i = strlen( $text );
 291+ } else {
 292+ $endPos = intval( $variantEndPos );
 293+ // Search backwards for leading whitespace
 294+ if ( $i ) {
 295+ $wsStart = $i - intval( strspn( $revText, ' ', strlen( $text ) - $i ) );
 296+ } else {
 297+ $wsStart = 0;
 298+ }
 299+ // Search forwards for trailing whitespace
 300+ // $wsEnd will be the position of the last space (or the '>' if there's none)
 301+ $wsEnd = $endPos + 2 + intval( strspn( $text, ' ', $endPos + 3 ) );
 302+ // Eat the line if possible
 303+ // TODO: This could theoretically be done if $wsStart == 0, i.e. for comments at
 304+ // the overall start. That's not how Sanitizer::removeHTMLcomments() did it, but
 305+ // it's a possible beneficial b/c break.
 306+ if ( $wsStart > 0 && substr( $text, $wsStart - 1, 1 ) === "\n"
 307+ && substr( $text, $wsEnd + 1, 1 ) === "\n" )
 308+ {
 309+ $startPos2 = $wsStart;
 310+ $endPos = $wsEnd + 1;
 311+ // Remove leading whitespace from the end of the accumulator
 312+ // Sanity check first though
 313+ $wsLength = $i - $wsStart;
 314+ if ( $wsLength > 0
 315+ && $accum->lastNode instanceof PPNode_HipHop_Text
 316+ && substr( $accum->lastNode->value, -$wsLength ) === str_repeat( ' ', $wsLength ) )
 317+ {
 318+ $accum->lastNode->value = strval( substr( $accum->lastNode->value, 0, -$wsLength ) );
 319+ }
 320+ // Do a line-start run next time to look for headings after the comment
 321+ $fakeLineStart = true;
 322+ } else {
 323+ // No line to eat, just take the comment itself
 324+ $startPos2 = $i;
 325+ $endPos += 2;
 326+ }
 327+
 328+ if ( $stack->top ) {
 329+ $part = $stack->getTop()->getCurrentPart();
 330+ if ( ! (isset( $part->commentEnd ) && $part->commentEnd == $wsStart - 1 )) {
 331+ $part->visualEnd = $wsStart;
 332+ }
 333+ // Else comments abutting, no change in visual end
 334+ $part->commentEnd = $endPos;
 335+ }
 336+ $i = $endPos + 1;
 337+ $inner = strval( substr( $text, $startPos2, $endPos - $startPos2 + 1 ) );
 338+ $accum->addNodeWithText( 'comment', $inner );
 339+ }
 340+ continue;
 341+ }
 342+ $name = strval( $matches[1] );
 343+ $lowerName = strtolower( $name );
 344+ $attrStart = $i + strlen( $name ) + 1;
 345+
 346+ // Find end of tag
 347+ $variantTagEndPos = $noMoreGT ? false : strpos( $text, '>', $attrStart );
 348+ if ( $variantTagEndPos === false ) {
 349+ // Infinite backtrack
 350+ // Disable tag search to prevent worst-case O(N^2) performance
 351+ $noMoreGT = true;
 352+ $accum->addLiteral( '<' );
 353+ ++$i;
 354+ continue;
 355+ }
 356+ $tagEndPos = intval( $variantTagEndPos );
 357+
 358+ // Handle ignored tags
 359+ if ( in_array( $lowerName, $ignoredTags ) ) {
 360+ $accum->addNodeWithText( 'ignore', strval( substr( $text, $i, $tagEndPos - $i + 1 ) ) );
 361+ $i = $tagEndPos + 1;
 362+ continue;
 363+ }
 364+
 365+ $tagStartPos = $i;
 366+ $inner = $close = '';
 367+ if ( $text[$tagEndPos-1] === '/' ) {
 368+ // Short end tag
 369+ $attrEnd = $tagEndPos - 1;
 370+ $shortEnd = true;
 371+ $inner = '';
 372+ $i = $tagEndPos + 1;
 373+ $haveClose = false;
 374+ } else {
 375+ $attrEnd = $tagEndPos;
 376+ $shortEnd = false;
 377+ // Find closing tag
 378+ if ( preg_match( "/<\/" . preg_quote( $name, '/' ) . "\s*>/i",
 379+ $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 ) )
 380+ {
 381+ $inner = strval( substr( $text, $tagEndPos + 1, $matches[0][1] - $tagEndPos - 1 ) );
 382+ $i = intval( $matches[0][1] ) + strlen( $matches[0][0] );
 383+ $close = strval( $matches[0][0] );
 384+ $haveClose = true;
 385+ } else {
 386+ // No end tag -- let it run out to the end of the text.
 387+ $inner = strval( substr( $text, $tagEndPos + 1 ) );
 388+ $i = strlen( $text );
 389+ $haveClose = false;
 390+ }
 391+ }
 392+ // <includeonly> and <noinclude> just become <ignore> tags
 393+ if ( in_array( $lowerName, $ignoredElements ) ) {
 394+ $accum->addNodeWithText( 'ignore', strval( substr( $text, $tagStartPos, $i - $tagStartPos ) ) );
 395+ continue;
 396+ }
 397+
 398+ if ( $attrEnd <= $attrStart ) {
 399+ $attr = '';
 400+ } else {
 401+ // Note that the attr element contains the whitespace between name and attribute,
 402+ // this is necessary for precise reconstruction during pre-save transform.
 403+ $attr = strval( substr( $text, $attrStart, $attrEnd - $attrStart ) );
 404+ }
 405+
 406+ $extNode = new PPNode_HipHop_Tree( 'ext' );
 407+ $extNode->addChild( PPNode_HipHop_Tree::newWithText( 'name', $name ) );
 408+ $extNode->addChild( PPNode_HipHop_Tree::newWithText( 'attr', $attr ) );
 409+ if ( !$shortEnd ) {
 410+ $extNode->addChild( PPNode_HipHop_Tree::newWithText( 'inner', $inner ) );
 411+ }
 412+ if ( $haveClose ) {
 413+ $extNode->addChild( PPNode_HipHop_Tree::newWithText( 'close', $close ) );
 414+ }
 415+ $accum->addNode( $extNode );
 416+ }
 417+
 418+ elseif ( $found === 'line-start' ) {
 419+ // Is this the start of a heading?
 420+ // Line break belongs before the heading element in any case
 421+ if ( $fakeLineStart ) {
 422+ $fakeLineStart = false;
 423+ } else {
 424+ $accum->addLiteral( $curChar );
 425+ $i++;
 426+ }
 427+
 428+ $count = intval( strspn( $text, '=', $i, 6 ) );
 429+ if ( $count == 1 && $stackFlags['findEquals'] ) {
 430+ // DWIM: This looks kind of like a name/value separator
 431+ // Let's let the equals handler have it and break the potential heading
 432+ // This is heuristic, but AFAICT the methods for completely correct disambiguation are very complex.
 433+ } elseif ( $count > 0 ) {
 434+ $partData = array(
 435+ 'open' => "\n",
 436+ 'close' => "\n",
 437+ 'parts' => array( new PPDPart_HipHop( str_repeat( '=', $count ) ) ),
 438+ 'startPos' => $i,
 439+ 'count' => $count );
 440+ $stack->push( $partData );
 441+ $accum = $stack->getAccum();
 442+ $stackFlags = $stack->getFlags();
 443+ $i += $count;
 444+ }
 445+ } elseif ( $found === 'line-end' ) {
 446+ $piece = $stack->getTop();
 447+ // A heading must be open, otherwise \n wouldn't have been in the search list
 448+ assert( $piece->open === "\n" );
 449+ $part = $piece->getCurrentPart();
 450+ // Search back through the input to see if it has a proper close
 451+ // Do this using the reversed string since the other solutions (end anchor, etc.) are inefficient
 452+ $wsLength = intval( strspn( $revText, " \t", strlen( $text ) - $i ) );
 453+ $searchStart = $i - $wsLength;
 454+ if ( isset( $part->commentEnd ) && $searchStart - 1 == $part->commentEnd ) {
 455+ // Comment found at line end
 456+ // Search for equals signs before the comment
 457+ $searchStart = intval( $part->visualEnd );
 458+ $searchStart -= intval( strspn( $revText, " \t", strlen( $text ) - $searchStart ) );
 459+ }
 460+ $count = intval( $piece->count );
 461+ $equalsLength = intval( strspn( $revText, '=', strlen( $text ) - $searchStart ) );
 462+ $isTreeNode = false;
 463+ $resultAccum = $accum;
 464+ if ( $equalsLength > 0 ) {
 465+ if ( $searchStart - $equalsLength == $piece->startPos ) {
 466+ // This is just a single string of equals signs on its own line
 467+ // Replicate the doHeadings behaviour /={count}(.+)={count}/
 468+ // First find out how many equals signs there really are (don't stop at 6)
 469+ $count = $equalsLength;
 470+ if ( $count < 3 ) {
 471+ $count = 0;
 472+ } else {
 473+ $count = intval( ( $count - 1 ) / 2 );
 474+ if ( $count > 6 ) {
 475+ $count = 6;
 476+ }
 477+ }
 478+ } else {
 479+ if ( $count > $equalsLength ) {
 480+ $count = $equalsLength;
 481+ }
 482+ }
 483+ if ( $count > 0 ) {
 484+ // Normal match, output <h>
 485+ $tree = new PPNode_HipHop_Tree( 'possible-h' );
 486+ $tree->addChild( new PPNode_HipHop_Attr( 'level', $count ) );
 487+ $tree->addChild( new PPNode_HipHop_Attr( 'i', $headingIndex++ ) );
 488+ $tree->lastChild->nextSibling = $accum->firstNode;
 489+ $tree->lastChild = $accum->lastNode;
 490+ $isTreeNode = true;
 491+ } else {
 492+ // Single equals sign on its own line, count=0
 493+ // Output $resultAccum
 494+ }
 495+ } else {
 496+ // No match, no <h>, just pass down the inner text
 497+ // Output $resultAccum
 498+ }
 499+ // Unwind the stack
 500+ $stack->pop();
 501+ $accum = $stack->getAccum();
 502+ $stackFlags = $stack->getFlags();
 503+
 504+ // Append the result to the enclosing accumulator
 505+ if ( $isTreeNode ) {
 506+ $accum->addNode( $tree );
 507+ } else {
 508+ $accum->addAccum( $resultAccum );
 509+ }
 510+ // Note that we do NOT increment the input pointer.
 511+ // This is because the closing linebreak could be the opening linebreak of
 512+ // another heading. Infinite loops are avoided because the next iteration MUST
 513+ // hit the heading open case above, which unconditionally increments the
 514+ // input pointer.
 515+ } elseif ( $found === 'open' ) {
 516+ # count opening brace characters
 517+ $count = intval( strspn( $text, $curChar, $i ) );
 518+
 519+ # we need to add to stack only if opening brace count is enough for one of the rules
 520+ if ( $count >= $rule['min'] ) {
 521+ # Add it to the stack
 522+ $partData = array(
 523+ 'open' => $curChar,
 524+ 'close' => $rule['end'],
 525+ 'count' => $count,
 526+ 'lineStart' => ($i == 0 || $text[$i-1] === "\n"),
 527+ );
 528+
 529+ $stack->push( $partData );
 530+ $accum = $stack->getAccum();
 531+ $stackFlags = $stack->getFlags();
 532+ } else {
 533+ # Add literal brace(s)
 534+ $accum->addLiteral( str_repeat( $curChar, $count ) );
 535+ }
 536+ $i += $count;
 537+ } elseif ( $found === 'close' ) {
 538+ $piece = $stack->getTop();
 539+ # lets check if there are enough characters for closing brace
 540+ $maxCount = intval( $piece->count );
 541+ $count = intval( strspn( $text, $curChar, $i, $maxCount ) );
 542+
 543+ # check for maximum matching characters (if there are 5 closing
 544+ # characters, we will probably need only 3 - depending on the rules)
 545+ $rule = $rules[$piece->open];
 546+ if ( $count > $rule['max'] ) {
 547+ # The specified maximum exists in the callback array, unless the caller
 548+ # has made an error
 549+ $matchingCount = intval( $rule['max'] );
 550+ } else {
 551+ # Count is less than the maximum
 552+ # Skip any gaps in the callback array to find the true largest match
 553+ # Need to use array_key_exists not isset because the callback can be null
 554+ $matchingCount = $count;
 555+ while ( $matchingCount > 0 && !array_key_exists( $matchingCount, $rule['names'] ) ) {
 556+ --$matchingCount;
 557+ }
 558+ }
 559+
 560+ if ($matchingCount <= 0) {
 561+ # No matching element found in callback array
 562+ # Output a literal closing brace and continue
 563+ $accum->addLiteral( str_repeat( $curChar, $count ) );
 564+ $i += $count;
 565+ continue;
 566+ }
 567+ $name = strval( $rule['names'][$matchingCount] );
 568+ $isTreeNode = false;
 569+ if ( $name === 'LITERAL' ) {
 570+ // No element, just literal text
 571+ $resultAccum = $piece->breakSyntax( $matchingCount );
 572+ $resultAccum->addLiteral( str_repeat( $rule['end'], $matchingCount ) );
 573+ } else {
 574+ # Create XML element
 575+ # Note: $parts is already XML, does not need to be encoded further
 576+ $isTreeNode = true;
 577+ $parts = $piece->parts;
 578+ $titleAccum = PPDAccum_HipHop::cast( $parts[0]->out );
 579+ unset( $parts[0] );
 580+
 581+ $tree = new PPNode_HipHop_Tree( $name );
 582+
 583+ # The invocation is at the start of the line if lineStart is set in
 584+ # the stack, and all opening brackets are used up.
 585+ if ( $maxCount == $matchingCount && !empty( $piece->lineStart ) ) {
 586+ $tree->addChild( new PPNode_HipHop_Attr( 'lineStart', 1 ) );
 587+ }
 588+ $titleNode = new PPNode_HipHop_Tree( 'title' );
 589+ $titleNode->firstChild = $titleAccum->firstNode;
 590+ $titleNode->lastChild = $titleAccum->lastNode;
 591+ $tree->addChild( $titleNode );
 592+ $argIndex = 1;
 593+ foreach ( $parts as $variantPart ) {
 594+ $part = PPDPart_HipHop::cast( $variantPart );
 595+ if ( isset( $part->eqpos ) ) {
 596+ // Find equals
 597+ $lastNode = false;
 598+ for ( $node = $part->out->firstNode; $node; $node = $node->nextSibling ) {
 599+ if ( $node === $part->eqpos ) {
 600+ break;
 601+ }
 602+ $lastNode = $node;
 603+ }
 604+ if ( !$node ) {
 605+ throw new MWException( __METHOD__. ': eqpos not found' );
 606+ }
 607+ if ( $node->name !== 'equals' ) {
 608+ throw new MWException( __METHOD__ .': eqpos is not equals' );
 609+ }
 610+ $equalsNode = $node;
 611+
 612+ // Construct name node
 613+ $nameNode = new PPNode_HipHop_Tree( 'name' );
 614+ if ( $lastNode !== false ) {
 615+ $lastNode->nextSibling = false;
 616+ $nameNode->firstChild = $part->out->firstNode;
 617+ $nameNode->lastChild = $lastNode;
 618+ }
 619+
 620+ // Construct value node
 621+ $valueNode = new PPNode_HipHop_Tree( 'value' );
 622+ if ( $equalsNode->nextSibling !== false ) {
 623+ $valueNode->firstChild = $equalsNode->nextSibling;
 624+ $valueNode->lastChild = $part->out->lastNode;
 625+ }
 626+ $partNode = new PPNode_HipHop_Tree( 'part' );
 627+ $partNode->addChild( $nameNode );
 628+ $partNode->addChild( $equalsNode->firstChild );
 629+ $partNode->addChild( $valueNode );
 630+ $tree->addChild( $partNode );
 631+ } else {
 632+ $partNode = new PPNode_HipHop_Tree( 'part' );
 633+ $nameNode = new PPNode_HipHop_Tree( 'name' );
 634+ $nameNode->addChild( new PPNode_HipHop_Attr( 'index', $argIndex++ ) );
 635+ $valueNode = new PPNode_HipHop_Tree( 'value' );
 636+ $valueNode->firstChild = $part->out->firstNode;
 637+ $valueNode->lastChild = $part->out->lastNode;
 638+ $partNode->addChild( $nameNode );
 639+ $partNode->addChild( $valueNode );
 640+ $tree->addChild( $partNode );
 641+ }
 642+ }
 643+ }
 644+
 645+ # Advance input pointer
 646+ $i += $matchingCount;
 647+
 648+ # Unwind the stack
 649+ $stack->pop();
 650+ $accum = $stack->getAccum();
 651+
 652+ # Re-add the old stack element if it still has unmatched opening characters remaining
 653+ if ($matchingCount < $piece->count) {
 654+ $piece->parts = array( new PPDPart_HipHop );
 655+ $piece->count -= $matchingCount;
 656+ # do we still qualify for any callback with remaining count?
 657+ $names = $rules[$piece->open]['names'];
 658+ $skippedBraces = 0;
 659+ $enclosingAccum = $accum;
 660+ while ( $piece->count ) {
 661+ if ( array_key_exists( $piece->count, $names ) ) {
 662+ $stack->push( $piece );
 663+ $accum = $stack->getAccum();
 664+ break;
 665+ }
 666+ --$piece->count;
 667+ $skippedBraces ++;
 668+ }
 669+ $enclosingAccum->addLiteral( str_repeat( $piece->open, $skippedBraces ) );
 670+ }
 671+
 672+ $stackFlags = $stack->getFlags();
 673+
 674+ # Add XML element to the enclosing accumulator
 675+ if ( $isTreeNode ) {
 676+ $accum->addNode( $tree );
 677+ } else {
 678+ $accum->addAccum( $resultAccum );
 679+ }
 680+ } elseif ( $found === 'pipe' ) {
 681+ $stackFlags['findEquals'] = true; // shortcut for getFlags()
 682+ $stack->addPart();
 683+ $accum = $stack->getAccum();
 684+ ++$i;
 685+ } elseif ( $found === 'equals' ) {
 686+ $stackFlags['findEquals'] = false; // shortcut for getFlags()
 687+ $accum->addNodeWithText( 'equals', '=' );
 688+ $stack->getCurrentPart()->eqpos = $accum->lastNode;
 689+ ++$i;
 690+ }
 691+ }
 692+
 693+ # Output any remaining unclosed brackets
 694+ foreach ( $stack->stack as $variantPiece ) {
 695+ $piece = PPDStackElement_HipHop::cast( $variantPiece );
 696+ $stack->rootAccum->addAccum( $piece->breakSyntax() );
 697+ }
 698+
 699+ # Enable top-level headings
 700+ for ( $node = $stack->rootAccum->firstNode; $node; $node = $node->nextSibling ) {
 701+ if ( isset( $node->name ) && $node->name === 'possible-h' ) {
 702+ $node->name = 'h';
 703+ }
 704+ }
 705+
 706+ $rootNode = new PPNode_HipHop_Tree( 'root' );
 707+ $rootNode->firstChild = $stack->rootAccum->firstNode;
 708+ $rootNode->lastChild = $stack->rootAccum->lastNode;
 709+
 710+ // Cache
 711+ if ($cacheable) {
 712+ $cacheValue = sprintf( "%08d", self::CACHE_VERSION ) . serialize( $rootNode );
 713+ $wgMemc->set( $cacheKey, $cacheValue, 86400 );
 714+ wfProfileOut( __METHOD__.'-cache-miss' );
 715+ wfProfileOut( __METHOD__.'-cacheable' );
 716+ wfDebugLog( "Preprocessor", "Saved preprocessor Hash to memcached (key $cacheKey)" );
 717+ }
 718+
 719+ wfProfileOut( __METHOD__ );
 720+ return $rootNode;
 721+ }
 722+}
 723+
 724+
 725+
 726+/**
 727+ * Stack class to help Preprocessor::preprocessToObj()
 728+ * @ingroup Parser
 729+ */
 730+class PPDStack_HipHop {
 731+ var $stack, $rootAccum;
 732+
 733+ /**
 734+ * @var PPDStack
 735+ */
 736+ var $top;
 737+ var $out;
 738+
 739+ static $false = false;
 740+
 741+ function __construct() {
 742+ $this->stack = array();
 743+ $this->top = false;
 744+ $this->rootAccum = new PPDAccum_HipHop;
 745+ $this->accum = $this->rootAccum;
 746+ }
 747+
 748+ /**
 749+ * @return int
 750+ */
 751+ function count() {
 752+ return count( $this->stack );
 753+ }
 754+
 755+ function getAccum() {
 756+ return PPDAccum_HipHop::cast( $this->accum );
 757+ }
 758+
 759+ function getCurrentPart() {
 760+ return $this->getTop()->getCurrentPart();
 761+ }
 762+
 763+ function getTop() {
 764+ return PPDStackElement_HipHop::cast( $this->top );
 765+ }
 766+
 767+ function push( $data ) {
 768+ if ( $data instanceof PPDStackElement_HipHop ) {
 769+ $this->stack[] = $data;
 770+ } else {
 771+ $this->stack[] = new PPDStackElement_HipHop( $data );
 772+ }
 773+ $this->top = $this->stack[ count( $this->stack ) - 1 ];
 774+ $this->accum = $this->top->getAccum();
 775+ }
 776+
 777+ function pop() {
 778+ if ( !count( $this->stack ) ) {
 779+ throw new MWException( __METHOD__.': no elements remaining' );
 780+ }
 781+ $temp = array_pop( $this->stack );
 782+
 783+ if ( count( $this->stack ) ) {
 784+ $this->top = $this->stack[ count( $this->stack ) - 1 ];
 785+ $this->accum = $this->top->getAccum();
 786+ } else {
 787+ $this->top = self::$false;
 788+ $this->accum = $this->rootAccum;
 789+ }
 790+ return $temp;
 791+ }
 792+
 793+ function addPart( $s = '' ) {
 794+ $this->top->addPart( $s );
 795+ $this->accum = $this->top->getAccum();
 796+ }
 797+
 798+ /**
 799+ * @return array
 800+ */
 801+ function getFlags() {
 802+ if ( !count( $this->stack ) ) {
 803+ return array(
 804+ 'findEquals' => false,
 805+ 'findPipe' => false,
 806+ 'inHeading' => false,
 807+ );
 808+ } else {
 809+ return $this->top->getFlags();
 810+ }
 811+ }
 812+}
 813+
 814+/**
 815+ * @ingroup Parser
 816+ */
 817+class PPDStackElement_HipHop {
 818+ var $open, // Opening character (\n for heading)
 819+ $close, // Matching closing character
 820+ $count, // Number of opening characters found (number of "=" for heading)
 821+ $parts, // Array of PPDPart objects describing pipe-separated parts.
 822+ $lineStart; // True if the open char appeared at the start of the input line. Not set for headings.
 823+
 824+ static function cast( PPDStackElement_HipHop $obj ) {
 825+ return $obj;
 826+ }
 827+
 828+ function __construct( $data = array() ) {
 829+ $this->parts = array( new PPDPart_HipHop );
 830+
 831+ foreach ( $data as $name => $value ) {
 832+ $this->$name = $value;
 833+ }
 834+ }
 835+
 836+ function getAccum() {
 837+ return PPDAccum_HipHop::cast( $this->parts[count($this->parts) - 1]->out );
 838+ }
 839+
 840+ function addPart( $s = '' ) {
 841+ $this->parts[] = new PPDPart_HipHop( $s );
 842+ }
 843+
 844+ function getCurrentPart() {
 845+ return PPDPart_HipHop::cast( $this->parts[count($this->parts) - 1] );
 846+ }
 847+
 848+ /**
 849+ * @return array
 850+ */
 851+ function getFlags() {
 852+ $partCount = count( $this->parts );
 853+ $findPipe = $this->open !== "\n" && $this->open !== '[';
 854+ return array(
 855+ 'findPipe' => $findPipe,
 856+ 'findEquals' => $findPipe && $partCount > 1 && !isset( $this->parts[$partCount - 1]->eqpos ),
 857+ 'inHeading' => $this->open === "\n",
 858+ );
 859+ }
 860+
 861+ /**
 862+ * Get the accumulator that would result if the close is not found.
 863+ *
 864+ * @return PPDAccum_HipHop
 865+ */
 866+ function breakSyntax( $openingCount = false ) {
 867+ if ( $this->open === "\n" ) {
 868+ $accum = PPDAccum_HipHop::cast( $this->parts[0]->out );
 869+ } else {
 870+ if ( $openingCount === false ) {
 871+ $openingCount = $this->count;
 872+ }
 873+ $accum = new PPDAccum_HipHop;
 874+ $accum->addLiteral( str_repeat( $this->open, $openingCount ) );
 875+ $first = true;
 876+ foreach ( $this->parts as $part ) {
 877+ if ( $first ) {
 878+ $first = false;
 879+ } else {
 880+ $accum->addLiteral( '|' );
 881+ }
 882+ $accum->addAccum( $part->out );
 883+ }
 884+ }
 885+ return $accum;
 886+ }
 887+}
 888+
 889+/**
 890+ * @ingroup Parser
 891+ */
 892+class PPDPart_HipHop {
 893+ var $out; // Output accumulator object
 894+
 895+ // Optional member variables:
 896+ // eqpos Position of equals sign in output accumulator
 897+ // commentEnd Past-the-end input pointer for the last comment encountered
 898+ // visualEnd Past-the-end input pointer for the end of the accumulator minus comments
 899+
 900+ function __construct( $out = '' ) {
 901+ $this->out = new PPDAccum_HipHop;
 902+ if ( $out !== '' ) {
 903+ $this->out->addLiteral( $out );
 904+ }
 905+ }
 906+
 907+ static function cast( PPDPart_HipHop $obj ) {
 908+ return $obj;
 909+ }
 910+}
 911+
 912+/**
 913+ * @ingroup Parser
 914+ */
 915+class PPDAccum_HipHop {
 916+ var $firstNode, $lastNode;
 917+
 918+ function __construct() {
 919+ $this->firstNode = $this->lastNode = false;
 920+ }
 921+
 922+ static function cast( PPDAccum_HipHop $obj ) {
 923+ return $obj;
 924+ }
 925+
 926+ /**
 927+ * Append a string literal
 928+ */
 929+ function addLiteral( string $s ) {
 930+ if ( $this->lastNode === false ) {
 931+ $this->firstNode = $this->lastNode = new PPNode_HipHop_Text( $s );
 932+ } elseif ( $this->lastNode instanceof PPNode_HipHop_Text ) {
 933+ $this->lastNode->value .= $s;
 934+ } else {
 935+ $this->lastNode->nextSibling = new PPNode_HipHop_Text( $s );
 936+ $this->lastNode = $this->lastNode->nextSibling;
 937+ }
 938+ }
 939+
 940+ /**
 941+ * Append a PPNode
 942+ */
 943+ function addNode( PPNode $node ) {
 944+ if ( $this->lastNode === false ) {
 945+ $this->firstNode = $this->lastNode = $node;
 946+ } else {
 947+ $this->lastNode->nextSibling = $node;
 948+ $this->lastNode = $node;
 949+ }
 950+ }
 951+
 952+ /**
 953+ * Append a tree node with text contents
 954+ */
 955+ function addNodeWithText( string $name, string $value ) {
 956+ $node = PPNode_HipHop_Tree::newWithText( $name, $value );
 957+ $this->addNode( $node );
 958+ }
 959+
 960+ /**
 961+ * Append a PPDAccum_HipHop
 962+ * Takes over ownership of the nodes in the source argument. These nodes may
 963+ * subsequently be modified, especially nextSibling.
 964+ */
 965+ function addAccum( PPDAccum_HipHop $accum ) {
 966+ if ( $accum->lastNode === false ) {
 967+ // nothing to add
 968+ } elseif ( $this->lastNode === false ) {
 969+ $this->firstNode = $accum->firstNode;
 970+ $this->lastNode = $accum->lastNode;
 971+ } else {
 972+ $this->lastNode->nextSibling = $accum->firstNode;
 973+ $this->lastNode = $accum->lastNode;
 974+ }
 975+ }
 976+}
 977+
 978+/**
 979+ * An expansion frame, used as a context to expand the result of preprocessToObj()
 980+ * @ingroup Parser
 981+ */
 982+class PPFrame_HipHop implements PPFrame {
 983+
 984+ /**
 985+ * @var Parser
 986+ */
 987+ var $parser;
 988+
 989+ /**
 990+ * @var Preprocessor
 991+ */
 992+ var $preprocessor;
 993+
 994+ /**
 995+ * @var Title
 996+ */
 997+ var $title;
 998+ var $titleCache;
 999+
 1000+ /**
 1001+ * Hashtable listing templates which are disallowed for expansion in this frame,
 1002+ * having been encountered previously in parent frames.
 1003+ */
 1004+ var $loopCheckHash;
 1005+
 1006+ /**
 1007+ * Recursion depth of this frame, top = 0
 1008+ * Note that this is NOT the same as expansion depth in expand()
 1009+ */
 1010+ var $depth;
 1011+
 1012+
 1013+ /**
 1014+ * Construct a new preprocessor frame.
 1015+ * @param $preprocessor Preprocessor: the parent preprocessor
 1016+ */
 1017+ function __construct( $preprocessor ) {
 1018+ $this->preprocessor = $preprocessor;
 1019+ $this->parser = $preprocessor->parser;
 1020+ $this->title = $this->parser->mTitle;
 1021+ $this->titleCache = array( $this->title ? $this->title->getPrefixedDBkey() : false );
 1022+ $this->loopCheckHash = array();
 1023+ $this->depth = 0;
 1024+ }
 1025+
 1026+ /**
 1027+ * Create a new child frame
 1028+ * $args is optionally a multi-root PPNode or array containing the template arguments
 1029+ *
 1030+ * @param $args PPNode_HipHop_Array|array
 1031+ * @param $title Title|false
 1032+ *
 1033+ * @return PPTemplateFrame_HipHop
 1034+ */
 1035+ function newChild( $args = false, $title = false ) {
 1036+ $namedArgs = array();
 1037+ $numberedArgs = array();
 1038+ if ( $title === false ) {
 1039+ $title = $this->title;
 1040+ }
 1041+ if ( $args !== false ) {
 1042+ if ( $args instanceof PPNode_HipHop_Array ) {
 1043+ $args = $args->value;
 1044+ } elseif ( !is_array( $args ) ) {
 1045+ throw new MWException( __METHOD__ . ': $args must be array or PPNode_HipHop_Array' );
 1046+ }
 1047+ foreach ( $args as $arg ) {
 1048+ $bits = $arg->splitArg();
 1049+ if ( $bits['index'] !== '' ) {
 1050+ // Numbered parameter
 1051+ $numberedArgs[$bits['index']] = $bits['value'];
 1052+ unset( $namedArgs[$bits['index']] );
 1053+ } else {
 1054+ // Named parameter
 1055+ $name = trim( $this->expand( $bits['name'], PPFrame::STRIP_COMMENTS ) );
 1056+ $namedArgs[$name] = $bits['value'];
 1057+ unset( $numberedArgs[$name] );
 1058+ }
 1059+ }
 1060+ }
 1061+ return new PPTemplateFrame_HipHop( $this->preprocessor, $this, $numberedArgs, $namedArgs, $title );
 1062+ }
 1063+
 1064+ /**
 1065+ * @throws MWException
 1066+ * @param $root
 1067+ * @param $flags int
 1068+ * @return string
 1069+ */
 1070+ function expand( $root, $flags = 0 ) {
 1071+ static $expansionDepth = 0;
 1072+ if ( is_string( $root ) ) {
 1073+ return $root;
 1074+ }
 1075+
 1076+ if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->getMaxPPNodeCount() ) {
 1077+ return '<span class="error">Node-count limit exceeded</span>';
 1078+ }
 1079+ if ( $expansionDepth > $this->parser->mOptions->getMaxPPExpandDepth() ) {
 1080+ return '<span class="error">Expansion depth limit exceeded</span>';
 1081+ }
 1082+ ++$expansionDepth;
 1083+
 1084+ $outStack = array( '', '' );
 1085+ $iteratorStack = array( false, $root );
 1086+ $indexStack = array( 0, 0 );
 1087+
 1088+ while ( count( $iteratorStack ) > 1 ) {
 1089+ $level = count( $outStack ) - 1;
 1090+ $iteratorNode =& $iteratorStack[ $level ];
 1091+ $out =& $outStack[$level];
 1092+ $index =& $indexStack[$level];
 1093+
 1094+ if ( is_array( $iteratorNode ) ) {
 1095+ if ( $index >= count( $iteratorNode ) ) {
 1096+ // All done with this iterator
 1097+ $iteratorStack[$level] = false;
 1098+ $contextNode = false;
 1099+ } else {
 1100+ $contextNode = $iteratorNode[$index];
 1101+ $index++;
 1102+ }
 1103+ } elseif ( $iteratorNode instanceof PPNode_HipHop_Array ) {
 1104+ if ( $index >= $iteratorNode->getLength() ) {
 1105+ // All done with this iterator
 1106+ $iteratorStack[$level] = false;
 1107+ $contextNode = false;
 1108+ } else {
 1109+ $contextNode = $iteratorNode->item( $index );
 1110+ $index++;
 1111+ }
 1112+ } else {
 1113+ // Copy to $contextNode and then delete from iterator stack,
 1114+ // because this is not an iterator but we do have to execute it once
 1115+ $contextNode = $iteratorStack[$level];
 1116+ $iteratorStack[$level] = false;
 1117+ }
 1118+
 1119+ $newIterator = false;
 1120+
 1121+ if ( $contextNode === false ) {
 1122+ // nothing to do
 1123+ } elseif ( is_string( $contextNode ) ) {
 1124+ $out .= $contextNode;
 1125+ } elseif ( is_array( $contextNode ) || $contextNode instanceof PPNode_HipHop_Array ) {
 1126+ $newIterator = $contextNode;
 1127+ } elseif ( $contextNode instanceof PPNode_HipHop_Attr ) {
 1128+ // No output
 1129+ } elseif ( $contextNode instanceof PPNode_HipHop_Text ) {
 1130+ $out .= $contextNode->value;
 1131+ } elseif ( $contextNode instanceof PPNode_HipHop_Tree ) {
 1132+ if ( $contextNode->name === 'template' ) {
 1133+ # Double-brace expansion
 1134+ $bits = $contextNode->splitTemplate();
 1135+ if ( $flags & PPFrame::NO_TEMPLATES ) {
 1136+ $newIterator = $this->virtualBracketedImplode( '{{', '|', '}}', $bits['title'], $bits['parts'] );
 1137+ } else {
 1138+ $ret = $this->parser->braceSubstitution( $bits, $this );
 1139+ if ( isset( $ret['object'] ) ) {
 1140+ $newIterator = $ret['object'];
 1141+ } else {
 1142+ $out .= $ret['text'];
 1143+ }
 1144+ }
 1145+ } elseif ( $contextNode->name === 'tplarg' ) {
 1146+ # Triple-brace expansion
 1147+ $bits = $contextNode->splitTemplate();
 1148+ if ( $flags & PPFrame::NO_ARGS ) {
 1149+ $newIterator = $this->virtualBracketedImplode( '{{{', '|', '}}}', $bits['title'], $bits['parts'] );
 1150+ } else {
 1151+ $ret = $this->parser->argSubstitution( $bits, $this );
 1152+ if ( isset( $ret['object'] ) ) {
 1153+ $newIterator = $ret['object'];
 1154+ } else {
 1155+ $out .= $ret['text'];
 1156+ }
 1157+ }
 1158+ } elseif ( $contextNode->name === 'comment' ) {
 1159+ # HTML-style comment
 1160+ # Remove it in HTML, pre+remove and STRIP_COMMENTS modes
 1161+ if ( $this->parser->ot['html']
 1162+ || ( $this->parser->ot['pre'] && $this->parser->mOptions->getRemoveComments() )
 1163+ || ( $flags & PPFrame::STRIP_COMMENTS ) )
 1164+ {
 1165+ $out .= '';
 1166+ }
 1167+ # Add a strip marker in PST mode so that pstPass2() can run some old-fashioned regexes on the result
 1168+ # Not in RECOVER_COMMENTS mode (extractSections) though
 1169+ elseif ( $this->parser->ot['wiki'] && ! ( $flags & PPFrame::RECOVER_COMMENTS ) ) {
 1170+ $out .= $this->parser->insertStripItem( $contextNode->firstChild->value );
 1171+ }
 1172+ # Recover the literal comment in RECOVER_COMMENTS and pre+no-remove
 1173+ else {
 1174+ $out .= $contextNode->firstChild->value;
 1175+ }
 1176+ } elseif ( $contextNode->name === 'ignore' ) {
 1177+ # Output suppression used by <includeonly> etc.
 1178+ # OT_WIKI will only respect <ignore> in substed templates.
 1179+ # The other output types respect it unless NO_IGNORE is set.
 1180+ # extractSections() sets NO_IGNORE and so never respects it.
 1181+ if ( ( !isset( $this->parent ) && $this->parser->ot['wiki'] ) || ( $flags & PPFrame::NO_IGNORE ) ) {
 1182+ $out .= $contextNode->firstChild->value;
 1183+ } else {
 1184+ //$out .= '';
 1185+ }
 1186+ } elseif ( $contextNode->name === 'ext' ) {
 1187+ # Extension tag
 1188+ $bits = $contextNode->splitExt() + array( 'attr' => null, 'inner' => null, 'close' => null );
 1189+ $out .= $this->parser->extensionSubstitution( $bits, $this );
 1190+ } elseif ( $contextNode->name === 'h' ) {
 1191+ # Heading
 1192+ if ( $this->parser->ot['html'] ) {
 1193+ # Expand immediately and insert heading index marker
 1194+ $s = '';
 1195+ for ( $node = $contextNode->firstChild; $node; $node = $node->nextSibling ) {
 1196+ $s .= $this->expand( $node, $flags );
 1197+ }
 1198+
 1199+ $bits = $contextNode->splitHeading();
 1200+ $titleText = $this->title->getPrefixedDBkey();
 1201+ $this->parser->mHeadings[] = array( $titleText, $bits['i'] );
 1202+ $serial = count( $this->parser->mHeadings ) - 1;
 1203+ $marker = "{$this->parser->mUniqPrefix}-h-$serial-" . Parser::MARKER_SUFFIX;
 1204+ $s = substr( $s, 0, $bits['level'] ) . $marker . substr( $s, $bits['level'] );
 1205+ $this->parser->mStripState->addGeneral( $marker, '' );
 1206+ $out .= $s;
 1207+ } else {
 1208+ # Expand in virtual stack
 1209+ $newIterator = $contextNode->getChildren();
 1210+ }
 1211+ } else {
 1212+ # Generic recursive expansion
 1213+ $newIterator = $contextNode->getChildren();
 1214+ }
 1215+ } else {
 1216+ throw new MWException( __METHOD__.': Invalid parameter type' );
 1217+ }
 1218+
 1219+ if ( $newIterator !== false ) {
 1220+ $outStack[] = '';
 1221+ $iteratorStack[] = $newIterator;
 1222+ $indexStack[] = 0;
 1223+ } elseif ( $iteratorStack[$level] === false ) {
 1224+ // Return accumulated value to parent
 1225+ // With tail recursion
 1226+ while ( $iteratorStack[$level] === false && $level > 0 ) {
 1227+ $outStack[$level - 1] .= $out;
 1228+ array_pop( $outStack );
 1229+ array_pop( $iteratorStack );
 1230+ array_pop( $indexStack );
 1231+ $level--;
 1232+ }
 1233+ }
 1234+ }
 1235+ --$expansionDepth;
 1236+ return $outStack[0];
 1237+ }
 1238+
 1239+ /**
 1240+ * @param $sep
 1241+ * @param $flags
 1242+ * @return string
 1243+ */
 1244+ function implodeWithFlags( $sep, $flags /*, ... */ ) {
 1245+ $args = array_slice( func_get_args(), 2 );
 1246+
 1247+ $first = true;
 1248+ $s = '';
 1249+ foreach ( $args as $root ) {
 1250+ if ( $root instanceof PPNode_HipHop_Array ) {
 1251+ $root = $root->value;
 1252+ }
 1253+ if ( !is_array( $root ) ) {
 1254+ $root = array( $root );
 1255+ }
 1256+ foreach ( $root as $node ) {
 1257+ if ( $first ) {
 1258+ $first = false;
 1259+ } else {
 1260+ $s .= $sep;
 1261+ }
 1262+ $s .= $this->expand( $node, $flags );
 1263+ }
 1264+ }
 1265+ return $s;
 1266+ }
 1267+
 1268+ /**
 1269+ * Implode with no flags specified
 1270+ * This previously called implodeWithFlags but has now been inlined to reduce stack depth
 1271+ * @return string
 1272+ */
 1273+ function implode( $sep /*, ... */ ) {
 1274+ $args = array_slice( func_get_args(), 1 );
 1275+
 1276+ $first = true;
 1277+ $s = '';
 1278+ foreach ( $args as $root ) {
 1279+ if ( $root instanceof PPNode_HipHop_Array ) {
 1280+ $root = $root->value;
 1281+ }
 1282+ if ( !is_array( $root ) ) {
 1283+ $root = array( $root );
 1284+ }
 1285+ foreach ( $root as $node ) {
 1286+ if ( $first ) {
 1287+ $first = false;
 1288+ } else {
 1289+ $s .= $sep;
 1290+ }
 1291+ $s .= $this->expand( $node );
 1292+ }
 1293+ }
 1294+ return $s;
 1295+ }
 1296+
 1297+ /**
 1298+ * Makes an object that, when expand()ed, will be the same as one obtained
 1299+ * with implode()
 1300+ *
 1301+ * @return PPNode_HipHop_Array
 1302+ */
 1303+ function virtualImplode( $sep /*, ... */ ) {
 1304+ $args = array_slice( func_get_args(), 1 );
 1305+ $out = array();
 1306+ $first = true;
 1307+
 1308+ foreach ( $args as $root ) {
 1309+ if ( $root instanceof PPNode_HipHop_Array ) {
 1310+ $root = $root->value;
 1311+ }
 1312+ if ( !is_array( $root ) ) {
 1313+ $root = array( $root );
 1314+ }
 1315+ foreach ( $root as $node ) {
 1316+ if ( $first ) {
 1317+ $first = false;
 1318+ } else {
 1319+ $out[] = $sep;
 1320+ }
 1321+ $out[] = $node;
 1322+ }
 1323+ }
 1324+ return new PPNode_HipHop_Array( $out );
 1325+ }
 1326+
 1327+ /**
 1328+ * Virtual implode with brackets
 1329+ *
 1330+ * @return PPNode_HipHop_Array
 1331+ */
 1332+ function virtualBracketedImplode( $start, $sep, $end /*, ... */ ) {
 1333+ $args = array_slice( func_get_args(), 3 );
 1334+ $out = array( $start );
 1335+ $first = true;
 1336+
 1337+ foreach ( $args as $root ) {
 1338+ if ( $root instanceof PPNode_HipHop_Array ) {
 1339+ $root = $root->value;
 1340+ }
 1341+ if ( !is_array( $root ) ) {
 1342+ $root = array( $root );
 1343+ }
 1344+ foreach ( $root as $node ) {
 1345+ if ( $first ) {
 1346+ $first = false;
 1347+ } else {
 1348+ $out[] = $sep;
 1349+ }
 1350+ $out[] = $node;
 1351+ }
 1352+ }
 1353+ $out[] = $end;
 1354+ return new PPNode_HipHop_Array( $out );
 1355+ }
 1356+
 1357+ function __toString() {
 1358+ return 'frame{}';
 1359+ }
 1360+
 1361+ /**
 1362+ * @param $level bool
 1363+ * @return array|bool|String
 1364+ */
 1365+ function getPDBK( $level = false ) {
 1366+ if ( $level === false ) {
 1367+ return $this->title->getPrefixedDBkey();
 1368+ } else {
 1369+ return isset( $this->titleCache[$level] ) ? $this->titleCache[$level] : false;
 1370+ }
 1371+ }
 1372+
 1373+ /**
 1374+ * @return array
 1375+ */
 1376+ function getArguments() {
 1377+ return array();
 1378+ }
 1379+
 1380+ /**
 1381+ * @return array
 1382+ */
 1383+ function getNumberedArguments() {
 1384+ return array();
 1385+ }
 1386+
 1387+ /**
 1388+ * @return array
 1389+ */
 1390+ function getNamedArguments() {
 1391+ return array();
 1392+ }
 1393+
 1394+ /**
 1395+ * Returns true if there are no arguments in this frame
 1396+ *
 1397+ * @return bool
 1398+ */
 1399+ function isEmpty() {
 1400+ return true;
 1401+ }
 1402+
 1403+ /**
 1404+ * @param $name
 1405+ * @return bool
 1406+ */
 1407+ function getArgument( $name ) {
 1408+ return false;
 1409+ }
 1410+
 1411+ /**
 1412+ * Returns true if the infinite loop check is OK, false if a loop is detected
 1413+ *
 1414+ * @param $title Title
 1415+ *
 1416+ * @return bool
 1417+ */
 1418+ function loopCheck( $title ) {
 1419+ return !isset( $this->loopCheckHash[$title->getPrefixedDBkey()] );
 1420+ }
 1421+
 1422+ /**
 1423+ * Return true if the frame is a template frame
 1424+ *
 1425+ * @return bool
 1426+ */
 1427+ function isTemplate() {
 1428+ return false;
 1429+ }
 1430+}
 1431+
 1432+/**
 1433+ * Expansion frame with template arguments
 1434+ * @ingroup Parser
 1435+ */
 1436+class PPTemplateFrame_HipHop extends PPFrame_HipHop {
 1437+ var $numberedArgs, $namedArgs, $parent;
 1438+ var $numberedExpansionCache, $namedExpansionCache;
 1439+
 1440+ /**
 1441+ * @param $preprocessor
 1442+ * @param $parent
 1443+ * @param $numberedArgs array
 1444+ * @param $namedArgs array
 1445+ * @param $title Title
 1446+ */
 1447+ function __construct( $preprocessor, $parent = false, $numberedArgs = array(), $namedArgs = array(), $title = false ) {
 1448+ parent::__construct( $preprocessor );
 1449+
 1450+ $this->parent = $parent;
 1451+ $this->numberedArgs = $numberedArgs;
 1452+ $this->namedArgs = $namedArgs;
 1453+ $this->title = $title;
 1454+ $pdbk = $title ? $title->getPrefixedDBkey() : false;
 1455+ $this->titleCache = $parent->titleCache;
 1456+ $this->titleCache[] = $pdbk;
 1457+ $this->loopCheckHash = /*clone*/ $parent->loopCheckHash;
 1458+ if ( $pdbk !== false ) {
 1459+ $this->loopCheckHash[$pdbk] = true;
 1460+ }
 1461+ $this->depth = $parent->depth + 1;
 1462+ $this->numberedExpansionCache = $this->namedExpansionCache = array();
 1463+ }
 1464+
 1465+ function __toString() {
 1466+ $s = 'tplframe{';
 1467+ $first = true;
 1468+ $args = $this->numberedArgs + $this->namedArgs;
 1469+ foreach ( $args as $name => $value ) {
 1470+ if ( $first ) {
 1471+ $first = false;
 1472+ } else {
 1473+ $s .= ', ';
 1474+ }
 1475+ $s .= "\"$name\":\"" .
 1476+ str_replace( '"', '\\"', $value->__toString() ) . '"';
 1477+ }
 1478+ $s .= '}';
 1479+ return $s;
 1480+ }
 1481+ /**
 1482+ * Returns true if there are no arguments in this frame
 1483+ *
 1484+ * @return bool
 1485+ */
 1486+ function isEmpty() {
 1487+ return !count( $this->numberedArgs ) && !count( $this->namedArgs );
 1488+ }
 1489+
 1490+ /**
 1491+ * @return array
 1492+ */
 1493+ function getArguments() {
 1494+ $arguments = array();
 1495+ foreach ( array_merge(
 1496+ array_keys($this->numberedArgs),
 1497+ array_keys($this->namedArgs)) as $key ) {
 1498+ $arguments[$key] = $this->getArgument($key);
 1499+ }
 1500+ return $arguments;
 1501+ }
 1502+
 1503+ /**
 1504+ * @return array
 1505+ */
 1506+ function getNumberedArguments() {
 1507+ $arguments = array();
 1508+ foreach ( array_keys($this->numberedArgs) as $key ) {
 1509+ $arguments[$key] = $this->getArgument($key);
 1510+ }
 1511+ return $arguments;
 1512+ }
 1513+
 1514+ /**
 1515+ * @return array
 1516+ */
 1517+ function getNamedArguments() {
 1518+ $arguments = array();
 1519+ foreach ( array_keys($this->namedArgs) as $key ) {
 1520+ $arguments[$key] = $this->getArgument($key);
 1521+ }
 1522+ return $arguments;
 1523+ }
 1524+
 1525+ /**
 1526+ * @param $index
 1527+ * @return array|bool
 1528+ */
 1529+ function getNumberedArgument( $index ) {
 1530+ if ( !isset( $this->numberedArgs[$index] ) ) {
 1531+ return false;
 1532+ }
 1533+ if ( !isset( $this->numberedExpansionCache[$index] ) ) {
 1534+ # No trimming for unnamed arguments
 1535+ $this->numberedExpansionCache[$index] = $this->parent->expand( $this->numberedArgs[$index], PPFrame::STRIP_COMMENTS );
 1536+ }
 1537+ return $this->numberedExpansionCache[$index];
 1538+ }
 1539+
 1540+ /**
 1541+ * @param $name
 1542+ * @return bool
 1543+ */
 1544+ function getNamedArgument( $name ) {
 1545+ if ( !isset( $this->namedArgs[$name] ) ) {
 1546+ return false;
 1547+ }
 1548+ if ( !isset( $this->namedExpansionCache[$name] ) ) {
 1549+ # Trim named arguments post-expand, for backwards compatibility
 1550+ $this->namedExpansionCache[$name] = trim(
 1551+ $this->parent->expand( $this->namedArgs[$name], PPFrame::STRIP_COMMENTS ) );
 1552+ }
 1553+ return $this->namedExpansionCache[$name];
 1554+ }
 1555+
 1556+ /**
 1557+ * @param $name
 1558+ * @return array|bool
 1559+ */
 1560+ function getArgument( $name ) {
 1561+ $text = $this->getNumberedArgument( $name );
 1562+ if ( $text === false ) {
 1563+ $text = $this->getNamedArgument( $name );
 1564+ }
 1565+ return $text;
 1566+ }
 1567+
 1568+ /**
 1569+ * Return true if the frame is a template frame
 1570+ *
 1571+ * @return bool
 1572+ */
 1573+ function isTemplate() {
 1574+ return true;
 1575+ }
 1576+}
 1577+
 1578+/**
 1579+ * Expansion frame with custom arguments
 1580+ * @ingroup Parser
 1581+ */
 1582+class PPCustomFrame_HipHop extends PPFrame_HipHop {
 1583+ var $args;
 1584+
 1585+ function __construct( $preprocessor, $args ) {
 1586+ parent::__construct( $preprocessor );
 1587+ $this->args = $args;
 1588+ }
 1589+
 1590+ function __toString() {
 1591+ $s = 'cstmframe{';
 1592+ $first = true;
 1593+ foreach ( $this->args as $name => $value ) {
 1594+ if ( $first ) {
 1595+ $first = false;
 1596+ } else {
 1597+ $s .= ', ';
 1598+ }
 1599+ $s .= "\"$name\":\"" .
 1600+ str_replace( '"', '\\"', $value->__toString() ) . '"';
 1601+ }
 1602+ $s .= '}';
 1603+ return $s;
 1604+ }
 1605+
 1606+ /**
 1607+ * @return bool
 1608+ */
 1609+ function isEmpty() {
 1610+ return !count( $this->args );
 1611+ }
 1612+
 1613+ /**
 1614+ * @param $index
 1615+ * @return bool
 1616+ */
 1617+ function getArgument( $index ) {
 1618+ if ( !isset( $this->args[$index] ) ) {
 1619+ return false;
 1620+ }
 1621+ return $this->args[$index];
 1622+ }
 1623+}
 1624+
 1625+/**
 1626+ * @ingroup Parser
 1627+ */
 1628+class PPNode_HipHop_Tree implements PPNode {
 1629+ var $name, $firstChild, $lastChild, $nextSibling;
 1630+
 1631+ function __construct( $name ) {
 1632+ $this->name = $name;
 1633+ $this->firstChild = $this->lastChild = $this->nextSibling = false;
 1634+ }
 1635+
 1636+ function __toString() {
 1637+ $inner = '';
 1638+ $attribs = '';
 1639+ for ( $node = $this->firstChild; $node; $node = $node->nextSibling ) {
 1640+ if ( $node instanceof PPNode_HipHop_Attr ) {
 1641+ $attribs .= ' ' . $node->name . '="' . htmlspecialchars( $node->value ) . '"';
 1642+ } else {
 1643+ $inner .= $node->__toString();
 1644+ }
 1645+ }
 1646+ if ( $inner === '' ) {
 1647+ return "<{$this->name}$attribs/>";
 1648+ } else {
 1649+ return "<{$this->name}$attribs>$inner</{$this->name}>";
 1650+ }
 1651+ }
 1652+
 1653+ /**
 1654+ * @param $name
 1655+ * @param $text
 1656+ * @return PPNode_HipHop_Tree
 1657+ */
 1658+ static function newWithText( $name, $text ) {
 1659+ $obj = new self( $name );
 1660+ $obj->addChild( new PPNode_HipHop_Text( $text ) );
 1661+ return $obj;
 1662+ }
 1663+
 1664+ function addChild( $node ) {
 1665+ if ( $this->lastChild === false ) {
 1666+ $this->firstChild = $this->lastChild = $node;
 1667+ } else {
 1668+ $this->lastChild->nextSibling = $node;
 1669+ $this->lastChild = $node;
 1670+ }
 1671+ }
 1672+
 1673+ /**
 1674+ * @return PPNode_HipHop_Array
 1675+ */
 1676+ function getChildren() {
 1677+ $children = array();
 1678+ for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
 1679+ $children[] = $child;
 1680+ }
 1681+ return new PPNode_HipHop_Array( $children );
 1682+ }
 1683+
 1684+ function getFirstChild() {
 1685+ return $this->firstChild;
 1686+ }
 1687+
 1688+ function getNextSibling() {
 1689+ return $this->nextSibling;
 1690+ }
 1691+
 1692+ function getChildrenOfType( $name ) {
 1693+ $children = array();
 1694+ for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
 1695+ if ( isset( $child->name ) && $child->name === $name ) {
 1696+ $children[] = $name;
 1697+ }
 1698+ }
 1699+ return $children;
 1700+ }
 1701+
 1702+ /**
 1703+ * @return bool
 1704+ */
 1705+ function getLength() {
 1706+ return false;
 1707+ }
 1708+
 1709+ /**
 1710+ * @param $i
 1711+ * @return bool
 1712+ */
 1713+ function item( $i ) {
 1714+ return false;
 1715+ }
 1716+
 1717+ /**
 1718+ * @return string
 1719+ */
 1720+ function getName() {
 1721+ return $this->name;
 1722+ }
 1723+
 1724+ /**
 1725+ * Split a <part> node into an associative array containing:
 1726+ * name PPNode name
 1727+ * index String index
 1728+ * value PPNode value
 1729+ *
 1730+ * @return array
 1731+ */
 1732+ function splitArg() {
 1733+ $bits = array();
 1734+ for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
 1735+ if ( !isset( $child->name ) ) {
 1736+ continue;
 1737+ }
 1738+ if ( $child->name === 'name' ) {
 1739+ $bits['name'] = $child;
 1740+ if ( $child->firstChild instanceof PPNode_HipHop_Attr
 1741+ && $child->firstChild->name === 'index' )
 1742+ {
 1743+ $bits['index'] = $child->firstChild->value;
 1744+ }
 1745+ } elseif ( $child->name === 'value' ) {
 1746+ $bits['value'] = $child;
 1747+ }
 1748+ }
 1749+
 1750+ if ( !isset( $bits['name'] ) ) {
 1751+ throw new MWException( 'Invalid brace node passed to ' . __METHOD__ );
 1752+ }
 1753+ if ( !isset( $bits['index'] ) ) {
 1754+ $bits['index'] = '';
 1755+ }
 1756+ return $bits;
 1757+ }
 1758+
 1759+ /**
 1760+ * Split an <ext> node into an associative array containing name, attr, inner and close
 1761+ * All values in the resulting array are PPNodes. Inner and close are optional.
 1762+ *
 1763+ * @return array
 1764+ */
 1765+ function splitExt() {
 1766+ $bits = array();
 1767+ for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
 1768+ if ( !isset( $child->name ) ) {
 1769+ continue;
 1770+ }
 1771+ if ( $child->name === 'name' ) {
 1772+ $bits['name'] = $child;
 1773+ } elseif ( $child->name === 'attr' ) {
 1774+ $bits['attr'] = $child;
 1775+ } elseif ( $child->name === 'inner' ) {
 1776+ $bits['inner'] = $child;
 1777+ } elseif ( $child->name === 'close' ) {
 1778+ $bits['close'] = $child;
 1779+ }
 1780+ }
 1781+ if ( !isset( $bits['name'] ) ) {
 1782+ throw new MWException( 'Invalid ext node passed to ' . __METHOD__ );
 1783+ }
 1784+ return $bits;
 1785+ }
 1786+
 1787+ /**
 1788+ * Split an <h> node
 1789+ *
 1790+ * @return array
 1791+ */
 1792+ function splitHeading() {
 1793+ if ( $this->name !== 'h' ) {
 1794+ throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
 1795+ }
 1796+ $bits = array();
 1797+ for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
 1798+ if ( !isset( $child->name ) ) {
 1799+ continue;
 1800+ }
 1801+ if ( $child->name === 'i' ) {
 1802+ $bits['i'] = $child->value;
 1803+ } elseif ( $child->name === 'level' ) {
 1804+ $bits['level'] = $child->value;
 1805+ }
 1806+ }
 1807+ if ( !isset( $bits['i'] ) ) {
 1808+ throw new MWException( 'Invalid h node passed to ' . __METHOD__ );
 1809+ }
 1810+ return $bits;
 1811+ }
 1812+
 1813+ /**
 1814+ * Split a <template> or <tplarg> node
 1815+ *
 1816+ * @return array
 1817+ */
 1818+ function splitTemplate() {
 1819+ $parts = array();
 1820+ $bits = array( 'lineStart' => '' );
 1821+ for ( $child = $this->firstChild; $child; $child = $child->nextSibling ) {
 1822+ if ( !isset( $child->name ) ) {
 1823+ continue;
 1824+ }
 1825+ if ( $child->name === 'title' ) {
 1826+ $bits['title'] = $child;
 1827+ }
 1828+ if ( $child->name === 'part' ) {
 1829+ $parts[] = $child;
 1830+ }
 1831+ if ( $child->name === 'lineStart' ) {
 1832+ $bits['lineStart'] = '1';
 1833+ }
 1834+ }
 1835+ if ( !isset( $bits['title'] ) ) {
 1836+ throw new MWException( 'Invalid node passed to ' . __METHOD__ );
 1837+ }
 1838+ $bits['parts'] = new PPNode_HipHop_Array( $parts );
 1839+ return $bits;
 1840+ }
 1841+}
 1842+
 1843+/**
 1844+ * @ingroup Parser
 1845+ */
 1846+class PPNode_HipHop_Text implements PPNode {
 1847+ var $value, $nextSibling;
 1848+
 1849+ function __construct( $value ) {
 1850+ if ( is_object( $value ) ) {
 1851+ throw new MWException( __CLASS__ . ' given object instead of string' );
 1852+ }
 1853+ $this->value = $value;
 1854+ }
 1855+
 1856+ function __toString() {
 1857+ return htmlspecialchars( $this->value );
 1858+ }
 1859+
 1860+ function getNextSibling() {
 1861+ return $this->nextSibling;
 1862+ }
 1863+
 1864+ function getChildren() { return false; }
 1865+ function getFirstChild() { return false; }
 1866+ function getChildrenOfType( $name ) { return false; }
 1867+ function getLength() { return false; }
 1868+ function item( $i ) { return false; }
 1869+ function getName() { return '#text'; }
 1870+ function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); }
 1871+ function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); }
 1872+ function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); }
 1873+}
 1874+
 1875+/**
 1876+ * @ingroup Parser
 1877+ */
 1878+class PPNode_HipHop_Array implements PPNode {
 1879+ var $value, $nextSibling;
 1880+
 1881+ function __construct( $value ) {
 1882+ $this->value = $value;
 1883+ }
 1884+
 1885+ function __toString() {
 1886+ return var_export( $this, true );
 1887+ }
 1888+
 1889+ function getLength() {
 1890+ return count( $this->value );
 1891+ }
 1892+
 1893+ function item( $i ) {
 1894+ return $this->value[$i];
 1895+ }
 1896+
 1897+ function getName() { return '#nodelist'; }
 1898+
 1899+ function getNextSibling() {
 1900+ return $this->nextSibling;
 1901+ }
 1902+
 1903+ function getChildren() { return false; }
 1904+ function getFirstChild() { return false; }
 1905+ function getChildrenOfType( $name ) { return false; }
 1906+ function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); }
 1907+ function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); }
 1908+ function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); }
 1909+}
 1910+
 1911+/**
 1912+ * @ingroup Parser
 1913+ */
 1914+class PPNode_HipHop_Attr implements PPNode {
 1915+ var $name, $value, $nextSibling;
 1916+
 1917+ function __construct( $name, $value ) {
 1918+ $this->name = $name;
 1919+ $this->value = $value;
 1920+ }
 1921+
 1922+ function __toString() {
 1923+ return "<@{$this->name}>" . htmlspecialchars( $this->value ) . "</@{$this->name}>";
 1924+ }
 1925+
 1926+ function getName() {
 1927+ return $this->name;
 1928+ }
 1929+
 1930+ function getNextSibling() {
 1931+ return $this->nextSibling;
 1932+ }
 1933+
 1934+ function getChildren() { return false; }
 1935+ function getFirstChild() { return false; }
 1936+ function getChildrenOfType( $name ) { return false; }
 1937+ function getLength() { return false; }
 1938+ function item( $i ) { return false; }
 1939+ function splitArg() { throw new MWException( __METHOD__ . ': not supported' ); }
 1940+ function splitExt() { throw new MWException( __METHOD__ . ': not supported' ); }
 1941+ function splitHeading() { throw new MWException( __METHOD__ . ': not supported' ); }
 1942+}
Property changes on: trunk/phase3/includes/parser/Preprocessor_HipHop.hphp
___________________________________________________________________
Added: svn:eol-style
11943 + native
Index: trunk/phase3/includes/AutoLoader.php
@@ -569,35 +569,47 @@
570570 'LinkHolderArray' => 'includes/parser/LinkHolderArray.php',
571571 'LinkMarkerReplacer' => 'includes/parser/Parser_LinkHooks.php',
572572 'MWTidy' => 'includes/parser/Tidy.php',
573 - 'Parser' => 'includes/parser/Parser.php',
574 - 'Parser_DiffTest' => 'includes/parser/Parser_DiffTest.php',
575 - 'Parser_LinkHooks' => 'includes/parser/Parser_LinkHooks.php',
576 - 'ParserCache' => 'includes/parser/ParserCache.php',
577 - 'ParserOptions' => 'includes/parser/ParserOptions.php',
578 - 'ParserOutput' => 'includes/parser/ParserOutput.php',
579573 'PPCustomFrame_DOM' => 'includes/parser/Preprocessor_DOM.php',
580574 'PPCustomFrame_Hash' => 'includes/parser/Preprocessor_Hash.php',
 575+ 'PPCustomFrame_HipHop' => 'includes/parser/Preprocessor_HipHop.hphp',
581576 'PPDAccum_Hash' => 'includes/parser/Preprocessor_Hash.php',
 577+ 'PPDAccum_HipHop' => 'includes/parser/Preprocessor_HipHop.hphp',
582578 'PPDPart' => 'includes/parser/Preprocessor_DOM.php',
583579 'PPDPart_Hash' => 'includes/parser/Preprocessor_Hash.php',
 580+ 'PPDPart_HipHop' => 'includes/parser/Preprocessor_HipHop.hphp',
584581 'PPDStack' => 'includes/parser/Preprocessor_DOM.php',
585 - 'PPDStack_Hash' => 'includes/parser/Preprocessor_Hash.php',
586582 'PPDStackElement' => 'includes/parser/Preprocessor_DOM.php',
587583 'PPDStackElement_Hash' => 'includes/parser/Preprocessor_Hash.php',
 584+ 'PPDStackElement_HipHop' => 'includes/parser/Preprocessor_HipHop.hphp',
 585+ 'PPDStack_Hash' => 'includes/parser/Preprocessor_Hash.php',
 586+ 'PPDStack_HipHop' => 'includes/parser/Preprocessor_HipHop.hphp',
588587 'PPFrame' => 'includes/parser/Preprocessor.php',
589588 'PPFrame_DOM' => 'includes/parser/Preprocessor_DOM.php',
590589 'PPFrame_Hash' => 'includes/parser/Preprocessor_Hash.php',
 590+ 'PPFrame_HipHop' => 'includes/parser/Preprocessor_HipHop.hphp',
591591 'PPNode' => 'includes/parser/Preprocessor.php',
592592 'PPNode_DOM' => 'includes/parser/Preprocessor_DOM.php',
593593 'PPNode_Hash_Array' => 'includes/parser/Preprocessor_Hash.php',
594594 'PPNode_Hash_Attr' => 'includes/parser/Preprocessor_Hash.php',
595595 'PPNode_Hash_Text' => 'includes/parser/Preprocessor_Hash.php',
596596 'PPNode_Hash_Tree' => 'includes/parser/Preprocessor_Hash.php',
 597+ 'PPNode_HipHop_Array' => 'includes/parser/Preprocessor_HipHop.hphp',
 598+ 'PPNode_HipHop_Attr' => 'includes/parser/Preprocessor_HipHop.hphp',
 599+ 'PPNode_HipHop_Text' => 'includes/parser/Preprocessor_HipHop.hphp',
 600+ 'PPNode_HipHop_Tree' => 'includes/parser/Preprocessor_HipHop.hphp',
597601 'PPTemplateFrame_DOM' => 'includes/parser/Preprocessor_DOM.php',
598602 'PPTemplateFrame_Hash' => 'includes/parser/Preprocessor_Hash.php',
 603+ 'PPTemplateFrame_HipHop' => 'includes/parser/Preprocessor_HipHop.hphp',
 604+ 'Parser' => 'includes/parser/Parser.php',
 605+ 'ParserCache' => 'includes/parser/ParserCache.php',
 606+ 'ParserOptions' => 'includes/parser/ParserOptions.php',
 607+ 'ParserOutput' => 'includes/parser/ParserOutput.php',
 608+ 'Parser_DiffTest' => 'includes/parser/Parser_DiffTest.php',
 609+ 'Parser_LinkHooks' => 'includes/parser/Parser_LinkHooks.php',
599610 'Preprocessor' => 'includes/parser/Preprocessor.php',
600611 'Preprocessor_DOM' => 'includes/parser/Preprocessor_DOM.php',
601612 'Preprocessor_Hash' => 'includes/parser/Preprocessor_Hash.php',
 613+ 'Preprocessor_HipHop' => 'includes/parser/Preprocessor_HipHop.hphp',
602614 'StripState' => 'includes/parser/StripState.php',
603615
604616 # includes/profiler

Comments

#Comment by Brion VIBBER (talk | contribs)   19:00, 3 June 2011

Awesome! Definitely good to get a better idea what the performance characteristics are on hphp...

Status & tagging log