From 183851b06bd6c52f3cae5375f433da720d410447 Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Wed, 11 Oct 2006 18:12:39 +0000 Subject: MediaWiki 1.7.1 wiederhergestellt --- includes/cbt/CBTCompiler.php | 369 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 369 insertions(+) create mode 100644 includes/cbt/CBTCompiler.php (limited to 'includes/cbt/CBTCompiler.php') diff --git a/includes/cbt/CBTCompiler.php b/includes/cbt/CBTCompiler.php new file mode 100644 index 00000000..4ef8ee4a --- /dev/null +++ b/includes/cbt/CBTCompiler.php @@ -0,0 +1,369 @@ +opcode = $opcode; + $this->arg1 = $arg1; + $this->arg2 = $arg2; + } + + function name() { + $opcodeNames = array( + CBT_PUSH => 'PUSH', + CBT_CAT => 'CAT', + CBT_CATS => 'CATS', + CBT_CALL => 'CALL', + CBT_HX => 'HX', + ); + return $opcodeNames[$this->opcode]; + } +}; + +class CBTCompiler { + var $mOps = array(); + var $mCode; + + function CBTCompiler( $text ) { + $this->mText = $text; + } + + /** + * Compile the text. + * Returns true on success, error message on failure + */ + function compile() { + $fname = 'CBTProcessor::compile'; + $this->mLastError = false; + $this->mOps = array(); + + $this->doText( 0, strlen( $this->mText ) ); + + if ( $this->mLastError !== false ) { + $pos = $this->mErrorPos; + + // Find the line number at which the error occurred + $startLine = 0; + $endLine = 0; + $line = 0; + do { + if ( $endLine ) { + $startLine = $endLine + 1; + } + $endLine = strpos( $this->mText, "\n", $startLine ); + ++$line; + } while ( $endLine !== false && $endLine < $pos ); + + $text = "Template error at line $line: $this->mLastError\n
\n";
+
+			$context = rtrim( str_replace( "\t", " ", substr( $this->mText, $startLine, $endLine - $startLine ) ) );
+			$text .= htmlspecialchars( $context ) . "\n" . str_repeat( ' ', $pos - $startLine ) . "^\n
\n"; + } else { + $text = true; + } + + return $text; + } + + /** Shortcut for doOpenText( $start, $end, false */ + function doText( $start, $end ) { + return $this->doOpenText( $start, $end, false ); + } + + function phpQuote( $text ) { + return "'" . strtr( $text, array( "\\" => "\\\\", "'" => "\\'" ) ) . "'"; + } + + function op( $opcode, $arg1 = null, $arg2 = null) { + return new CBTOp( $opcode, $arg1, $arg2 ); + } + + /** + * Recursive workhorse for text mode. + * + * Processes text mode starting from offset $p, until either $end is + * reached or a closing brace is found. If $needClosing is false, a + * closing brace will flag an error, if $needClosing is true, the lack + * of a closing brace will flag an error. + * + * The parameter $p is advanced to the position after the closing brace, + * or after the end. A CBTValue is returned. + * + * @private + */ + function doOpenText( &$p, $end, $needClosing = true ) { + $in =& $this->mText; + $start = $p; + $atStart = true; + + $foundClosing = false; + while ( $p < $end ) { + $matchLength = strcspn( $in, CBT_BRACE, $p, $end - $p ); + $pToken = $p + $matchLength; + + if ( $pToken >= $end ) { + // No more braces, output remainder + if ( $atStart ) { + $this->mOps[] = $this->op( CBT_PUSH, substr( $in, $p ) ); + $atStart = false; + } else { + $this->mOps[] = $this->op( CBT_CAT, substr( $in, $p ) ); + } + $p = $end; + break; + } + + // Output the text before the brace + if ( $atStart ) { + $this->mOps[] = $this->op( CBT_PUSH, substr( $in, $p, $matchLength ) ); + $atStart = false; + } else { + $this->mOps[] = $this->op( CBT_CAT, substr( $in, $p, $matchLength ) ); + } + + // Advance the pointer + $p = $pToken + 1; + + // Check for closing brace + if ( $in[$pToken] == '}' ) { + $foundClosing = true; + break; + } + + // Handle the "{fn}" special case + if ( $pToken > 0 && $in[$pToken-1] == '"' ) { + $this->doOpenFunction( $p, $end ); + if ( $p < $end && $in[$p] == '"' ) { + $this->mOps[] = $this->op( CBT_HX ); + } + } else { + $this->doOpenFunction( $p, $end ); + } + if ( $atStart ) { + $atStart = false; + } else { + $this->mOps[] = $this->op( CBT_CATS ); + } + } + if ( $foundClosing && !$needClosing ) { + $this->error( 'Errant closing brace', $p ); + } elseif ( !$foundClosing && $needClosing ) { + $this->error( 'Unclosed text section', $start ); + } else { + if ( $atStart ) { + $this->mOps[] = $this->op( CBT_PUSH, '' ); + } + } + } + + /** + * Recursive workhorse for function mode. + * + * Processes function mode starting from offset $p, until either $end is + * reached or a closing brace is found. If $needClosing is false, a + * closing brace will flag an error, if $needClosing is true, the lack + * of a closing brace will flag an error. + * + * The parameter $p is advanced to the position after the closing brace, + * or after the end. A CBTValue is returned. + * + * @private + */ + function doOpenFunction( &$p, $end, $needClosing = true ) { + $in =& $this->mText; + $start = $p; + $argCount = 0; + + $foundClosing = false; + while ( $p < $end ) { + $char = $in[$p]; + if ( $char == '{' ) { + // Switch to text mode + ++$p; + $tokenStart = $p; + $this->doOpenText( $p, $end ); + ++$argCount; + } elseif ( $char == '}' ) { + // Block end + ++$p; + $foundClosing = true; + break; + } elseif ( false !== strpos( CBT_WHITE, $char ) ) { + // Whitespace + // Consume the rest of the whitespace + $p += strspn( $in, CBT_WHITE, $p, $end - $p ); + } else { + // Token, find the end of it + $tokenLength = strcspn( $in, CBT_DELIM, $p, $end - $p ); + $this->mOps[] = $this->op( CBT_PUSH, substr( $in, $p, $tokenLength ) ); + + // Execute the token as a function if it's not the function name + if ( $argCount ) { + $this->mOps[] = $this->op( CBT_CALL, 1 ); + } + + $p += $tokenLength; + ++$argCount; + } + } + if ( !$foundClosing && $needClosing ) { + $this->error( 'Unclosed function', $start ); + return ''; + } + + $this->mOps[] = $this->op( CBT_CALL, $argCount ); + } + + /** + * Set a flag indicating that an error has been found. + */ + function error( $text, $pos = false ) { + $this->mLastError = $text; + if ( $pos === false ) { + $this->mErrorPos = $this->mCurrentPos; + } else { + $this->mErrorPos = $pos; + } + } + + function getLastError() { + return $this->mLastError; + } + + function opsToString() { + $s = ''; + foreach( $this->mOps as $op ) { + $s .= $op->name(); + if ( !is_null( $op->arg1 ) ) { + $s .= ' ' . var_export( $op->arg1, true ); + } + if ( !is_null( $op->arg2 ) ) { + $s .= ' ' . var_export( $op->arg2, true ); + } + $s .= "\n"; + } + return $s; + } + + function generatePHP( $functionObj ) { + $fname = 'CBTCompiler::generatePHP'; + wfProfileIn( $fname ); + $stack = array(); + + foreach( $this->mOps as $index => $op ) { + switch( $op->opcode ) { + case CBT_PUSH: + $stack[] = $this->phpQuote( $op->arg1 ); + break; + case CBT_CAT: + $val = array_pop( $stack ); + array_push( $stack, "$val . " . $this->phpQuote( $op->arg1 ) ); + break; + case CBT_CATS: + $right = array_pop( $stack ); + $left = array_pop( $stack ); + array_push( $stack, "$left . $right" ); + break; + case CBT_CALL: + $args = array_slice( $stack, count( $stack ) - $op->arg1, $op->arg1 ); + $stack = array_slice( $stack, 0, count( $stack ) - $op->arg1 ); + + // Some special optimised expansions + if ( $op->arg1 == 0 ) { + $result = ''; + } else { + $func = array_shift( $args ); + if ( substr( $func, 0, 1 ) == "'" && substr( $func, -1 ) == "'" ) { + $func = substr( $func, 1, strlen( $func ) - 2 ); + if ( $func == "if" ) { + if ( $op->arg1 < 3 ) { + // This should have been caught during processing + return "Not enough arguments to if"; + } elseif ( $op->arg1 == 3 ) { + $result = "(({$args[0]} != '') ? ({$args[1]}) : '')"; + } else { + $result = "(({$args[0]} != '') ? ({$args[1]}) : ({$args[2]}))"; + } + } elseif ( $func == "true" ) { + $result = "true"; + } elseif( $func == "lbrace" || $func == "{" ) { + $result = "{"; + } elseif( $func == "rbrace" || $func == "}" ) { + $result = "}"; + } elseif ( $func == "escape" || $func == "~" ) { + $result = "htmlspecialchars({$args[0]})"; + } else { + // Known function name + $result = "{$functionObj}->{$func}(" . implode( ', ', $args ) . ')'; + } + } else { + // Unknown function name + $result = "call_user_func(array($functionObj, $func), " . implode( ', ', $args ) . ' )'; + } + } + array_push( $stack, $result ); + break; + case CBT_HX: + $val = array_pop( $stack ); + array_push( $stack, "htmlspecialchars( $val )" ); + break; + default: + return "Unknown opcode {$op->opcode}\n"; + } + } + wfProfileOut( $fname ); + if ( count( $stack ) !== 1 ) { + return "Error, stack count incorrect\n"; + } + return ' + global $cbtExecutingGenerated; + ++$cbtExecutingGenerated; + $output = ' . $stack[0] . '; + --$cbtExecutingGenerated; + return $output; + '; + } +} +?> -- cgit v1.2.3-54-g00ecf