00001 <?php
00002
00010 require_once( dirname( __FILE__ ) . '/CBTProcessor.php' );
00011
00016 define( 'CBT_PUSH', 1 );
00017
00022 define( 'CBT_CAT', 2 );
00023
00027 define( 'CBT_CATS', 3 );
00028
00035 define( 'CBT_CALL', 4 );
00036
00040 define( 'CBT_HX', 5 );
00041
00042 class CBTOp {
00043 var $opcode;
00044 var $arg1;
00045 var $arg2;
00046
00047 function CBTOp( $opcode, $arg1, $arg2 ) {
00048 $this->opcode = $opcode;
00049 $this->arg1 = $arg1;
00050 $this->arg2 = $arg2;
00051 }
00052
00053 function name() {
00054 $opcodeNames = array(
00055 CBT_PUSH => 'PUSH',
00056 CBT_CAT => 'CAT',
00057 CBT_CATS => 'CATS',
00058 CBT_CALL => 'CALL',
00059 CBT_HX => 'HX',
00060 );
00061 return $opcodeNames[$this->opcode];
00062 }
00063 };
00064
00065 class CBTCompiler {
00066 var $mOps = array();
00067 var $mCode;
00068
00069 function CBTCompiler( $text ) {
00070 $this->mText = $text;
00071 }
00072
00077 function compile() {
00078 $this->mLastError = false;
00079 $this->mOps = array();
00080
00081 $this->doText( 0, strlen( $this->mText ) );
00082
00083 if ( $this->mLastError !== false ) {
00084 $pos = $this->mErrorPos;
00085
00086
00087 $startLine = 0;
00088 $endLine = 0;
00089 $line = 0;
00090 do {
00091 if ( $endLine ) {
00092 $startLine = $endLine + 1;
00093 }
00094 $endLine = strpos( $this->mText, "\n", $startLine );
00095 ++$line;
00096 } while ( $endLine !== false && $endLine < $pos );
00097
00098 $text = "Template error at line $line: $this->mLastError\n<pre>\n";
00099
00100 $context = rtrim( str_replace( "\t", " ", substr( $this->mText, $startLine, $endLine - $startLine ) ) );
00101 $text .= htmlspecialchars( $context ) . "\n" . str_repeat( ' ', $pos - $startLine ) . "^\n</pre>\n";
00102 } else {
00103 $text = true;
00104 }
00105
00106 return $text;
00107 }
00108
00110 function doText( $start, $end ) {
00111 return $this->doOpenText( $start, $end, false );
00112 }
00113
00114 function phpQuote( $text ) {
00115 return "'" . strtr( $text, array( "\\" => "\\\\", "'" => "\\'" ) ) . "'";
00116 }
00117
00118 function op( $opcode, $arg1 = null, $arg2 = null) {
00119 return new CBTOp( $opcode, $arg1, $arg2 );
00120 }
00121
00135 function doOpenText( &$p, $end, $needClosing = true ) {
00136 $in =& $this->mText;
00137 $start = $p;
00138 $atStart = true;
00139
00140 $foundClosing = false;
00141 while ( $p < $end ) {
00142 $matchLength = strcspn( $in, CBT_BRACE, $p, $end - $p );
00143 $pToken = $p + $matchLength;
00144
00145 if ( $pToken >= $end ) {
00146
00147 if ( $atStart ) {
00148 $this->mOps[] = $this->op( CBT_PUSH, substr( $in, $p ) );
00149 $atStart = false;
00150 } else {
00151 $this->mOps[] = $this->op( CBT_CAT, substr( $in, $p ) );
00152 }
00153 $p = $end;
00154 break;
00155 }
00156
00157
00158 if ( $atStart ) {
00159 $this->mOps[] = $this->op( CBT_PUSH, substr( $in, $p, $matchLength ) );
00160 $atStart = false;
00161 } else {
00162 $this->mOps[] = $this->op( CBT_CAT, substr( $in, $p, $matchLength ) );
00163 }
00164
00165
00166 $p = $pToken + 1;
00167
00168
00169 if ( $in[$pToken] == '}' ) {
00170 $foundClosing = true;
00171 break;
00172 }
00173
00174
00175 if ( $pToken > 0 && $in[$pToken-1] == '"' ) {
00176 $this->doOpenFunction( $p, $end );
00177 if ( $p < $end && $in[$p] == '"' ) {
00178 $this->mOps[] = $this->op( CBT_HX );
00179 }
00180 } else {
00181 $this->doOpenFunction( $p, $end );
00182 }
00183 if ( $atStart ) {
00184 $atStart = false;
00185 } else {
00186 $this->mOps[] = $this->op( CBT_CATS );
00187 }
00188 }
00189 if ( $foundClosing && !$needClosing ) {
00190 $this->error( 'Errant closing brace', $p );
00191 } elseif ( !$foundClosing && $needClosing ) {
00192 $this->error( 'Unclosed text section', $start );
00193 } else {
00194 if ( $atStart ) {
00195 $this->mOps[] = $this->op( CBT_PUSH, '' );
00196 }
00197 }
00198 }
00199
00213 function doOpenFunction( &$p, $end, $needClosing = true ) {
00214 $in =& $this->mText;
00215 $start = $p;
00216 $argCount = 0;
00217
00218 $foundClosing = false;
00219 while ( $p < $end ) {
00220 $char = $in[$p];
00221 if ( $char == '{' ) {
00222
00223 ++$p;
00224 $this->doOpenText( $p, $end );
00225 ++$argCount;
00226 } elseif ( $char == '}' ) {
00227
00228 ++$p;
00229 $foundClosing = true;
00230 break;
00231 } elseif ( false !== strpos( CBT_WHITE, $char ) ) {
00232
00233
00234 $p += strspn( $in, CBT_WHITE, $p, $end - $p );
00235 } else {
00236
00237 $tokenLength = strcspn( $in, CBT_DELIM, $p, $end - $p );
00238 $this->mOps[] = $this->op( CBT_PUSH, substr( $in, $p, $tokenLength ) );
00239
00240
00241 if ( $argCount ) {
00242 $this->mOps[] = $this->op( CBT_CALL, 1 );
00243 }
00244
00245 $p += $tokenLength;
00246 ++$argCount;
00247 }
00248 }
00249 if ( !$foundClosing && $needClosing ) {
00250 $this->error( 'Unclosed function', $start );
00251 return '';
00252 }
00253
00254 $this->mOps[] = $this->op( CBT_CALL, $argCount );
00255 }
00256
00260 function error( $text, $pos = false ) {
00261 $this->mLastError = $text;
00262 if ( $pos === false ) {
00263 $this->mErrorPos = $this->mCurrentPos;
00264 } else {
00265 $this->mErrorPos = $pos;
00266 }
00267 }
00268
00269 function getLastError() {
00270 return $this->mLastError;
00271 }
00272
00273 function opsToString() {
00274 $s = '';
00275 foreach( $this->mOps as $op ) {
00276 $s .= $op->name();
00277 if ( !is_null( $op->arg1 ) ) {
00278 $s .= ' ' . var_export( $op->arg1, true );
00279 }
00280 if ( !is_null( $op->arg2 ) ) {
00281 $s .= ' ' . var_export( $op->arg2, true );
00282 }
00283 $s .= "\n";
00284 }
00285 return $s;
00286 }
00287
00288 function generatePHP( $functionObj ) {
00289 $fname = 'CBTCompiler::generatePHP';
00290 wfProfileIn( $fname );
00291 $stack = array();
00292
00293 foreach( $this->mOps as $op ) {
00294 switch( $op->opcode ) {
00295 case CBT_PUSH:
00296 $stack[] = $this->phpQuote( $op->arg1 );
00297 break;
00298 case CBT_CAT:
00299 $val = array_pop( $stack );
00300 array_push( $stack, "$val . " . $this->phpQuote( $op->arg1 ) );
00301 break;
00302 case CBT_CATS:
00303 $right = array_pop( $stack );
00304 $left = array_pop( $stack );
00305 array_push( $stack, "$left . $right" );
00306 break;
00307 case CBT_CALL:
00308 $args = array_slice( $stack, count( $stack ) - $op->arg1, $op->arg1 );
00309 $stack = array_slice( $stack, 0, count( $stack ) - $op->arg1 );
00310
00311
00312 if ( $op->arg1 == 0 ) {
00313 $result = '';
00314 } else {
00315 $func = array_shift( $args );
00316 if ( substr( $func, 0, 1 ) == "'" && substr( $func, -1 ) == "'" ) {
00317 $func = substr( $func, 1, strlen( $func ) - 2 );
00318 if ( $func == "if" ) {
00319 if ( $op->arg1 < 3 ) {
00320
00321 return "Not enough arguments to if";
00322 } elseif ( $op->arg1 == 3 ) {
00323 $result = "(({$args[0]} != '') ? ({$args[1]}) : '')";
00324 } else {
00325 $result = "(({$args[0]} != '') ? ({$args[1]}) : ({$args[2]}))";
00326 }
00327 } elseif ( $func == "true" ) {
00328 $result = "true";
00329 } elseif( $func == "lbrace" || $func == "{" ) {
00330 $result = "{";
00331 } elseif( $func == "rbrace" || $func == "}" ) {
00332 $result = "}";
00333 } elseif ( $func == "escape" || $func == "~" ) {
00334 $result = "htmlspecialchars({$args[0]})";
00335 } else {
00336
00337 $result = "{$functionObj}->{$func}(" . implode( ', ', $args ) . ')';
00338 }
00339 } else {
00340
00341 $result = "call_user_func(array($functionObj, $func), " . implode( ', ', $args ) . ' )';
00342 }
00343 }
00344 array_push( $stack, $result );
00345 break;
00346 case CBT_HX:
00347 $val = array_pop( $stack );
00348 array_push( $stack, "htmlspecialchars( $val )" );
00349 break;
00350 default:
00351 return "Unknown opcode {$op->opcode}\n";
00352 }
00353 }
00354 wfProfileOut( $fname );
00355 if ( count( $stack ) !== 1 ) {
00356 return "Error, stack count incorrect\n";
00357 }
00358 return '
00359 global $cbtExecutingGenerated;
00360 ++$cbtExecutingGenerated;
00361 $output = ' . $stack[0] . ';
00362 --$cbtExecutingGenerated;
00363 return $output;
00364 ';
00365 }
00366 }