cbtcompiler.php

来自「php 开发的内容管理系统」· PHP 代码 · 共 370 行

PHP
370
字号
<?php/** * This file contains functions to convert callback templates to other languages. * The template should first be pre-processed with CBTProcessor to remove static * sections. */require_once( dirname( __FILE__ ) . '/CBTProcessor.php' );/**  * Push a value onto the stack  * Argument 1: value */define( 'CBT_PUSH', 1 );/** * Pop, concatenate argument, push * Argument 1: value */define( 'CBT_CAT', 2 );/** * Concatenate where the argument is on the stack, instead of immediate */define( 'CBT_CATS', 3 );/** * Call a function, push the return value onto the stack and put it in the cache * Argument 1: argument count * * The arguments to the function are on the stack */define( 'CBT_CALL', 4 );/** * Pop, htmlspecialchars, push */define( 'CBT_HX', 5 );class CBTOp {	var $opcode;	var $arg1;	var $arg2;	function CBTOp( $opcode, $arg1, $arg2 ) {		$this->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<pre>\n";			$context = rtrim( str_replace( "\t", " ", substr( $this->mText, $startLine, $endLine - $startLine ) ) );			$text .= htmlspecialchars( $context ) . "\n" . str_repeat( ' ', $pos - $startLine ) . "^\n</pre>\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;			';	}}?>

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?