Location: PHPKode > scripts > Transparent PHP AOP > transparent-php-aop/class.AOP_CodeCompiler.php
<?php


require_once "class.AOP_CodeParser.php";
require_once "class.AOP_CodeCruncher.php";
require_once "class.Advice.php";


class AOP_CodeCompiler
{
    var $code;
	var $codeParser;
	
	var $defStack;
	var $defPointcuts;

	var $weave;
	var $compact;
	
	var $returned;


    function AOP_CodeCompiler($sCode, $weave)
    {
    	$this->defStack = array();
    	$this->defPointcuts = array();
    	$this->codeParser = null;

		// Store initial Code Source
		$this->code = $sCode;

		// Store the aspectList
		$this->weave = $weave;

		// Define compact mode
		$this->compact = true;
		
		// Define returned as false
		$this->returned = false;
    }
    
    
    function getCode()
    {
    	return $this->code;
    }


	function compile($compact = true)
	{
		// Compact Mode
		$this->compact = (bool) $compact;
		
		// Correct missing curly braces in Original Code
		$this->compileCurlyBracesMissings();

		// Pre-Compile User Defined Pointcuts
		$this->compileCustomPointcuts();

		// Correct missing curly braces in Custom Pointcuts
		$this->compileCurlyBracesMissings();

        // Compile Automatic Pointcuts
		$this->compileAutoPointcuts();

		// Some PHP versions remove the PHP Close Tag...
		// Check for it and include if necessary
		if (substr($this->code, strlen($this->code) - 2, 2) != "?>") {
			$this->code .= "?>";
		}
	}
	

	function compileCustomPointcuts()
	{
    	// Create Code Parser
		$this->codeParser = new AOP_CodeParser($this->code);
		$this->codeParser->setIndex(0);

        // Blank result string
		$result = "";

        // Loop through each PHP token
		while (($tok = $this->codeParser->currentToken()) !== null) {
			$result .= $this->analizeCustomToken($tok);
			$this->codeParser->nextToken();
		}

		// Assigning properties
		$this->code = $result;
		$this->codeParser = null;
	}
	
	
	function analizeCustomToken($token)
	{
		$result = "";

        if (!is_array($token)) {
			$result .= $token;

			switch ($token) {
				case "{":
					// Include the curly open into stack
					array_push($this->defStack, "{");

					break;

				case "}":
					// Just process if there's at least a function or class defined
					if (count($this->defStack) >= 2) {
						// Retrieve possible curly open and method name
						$curly = array_pop($this->defStack);
						$method = array_pop($this->defStack);

						if ($curly == "{" && ($method == "{" || $method == "}")) {
							array_push($this->defStack, $method);
						}
					} else {
						// Remove class definition and/or garbage
						$this->defStack = array();
					}

					break;
			}
        } else {
        	switch (token_name((int) $token[0])) {
				case "T_CLASS":
				case "T_FUNCTION":
					$result .= $this->compileCustomClassOrMethodToken($token);
					break;

	            case "T_COMMENT":
	        		$result .= $this->compileCustomCommentToken($token);
					break;

	            default:
	            	// Append token into result string
					$result .= $token[1];
					break;
	        }
        }

		return $result;
	}

	
	function compileCustomClassOrMethodToken($token)
	{
        // Append token into result string
		$result = $token[1];
		
		$tk = $this->codeParser->nextToken();

        // Finding the next parsable token
        while(!is_array($tk) || (is_array($tk) && token_name((int) $tk[0]) != "T_STRING")) {
            // T_WHITESPACE or &
    		$tk = $this->codeParser->nextToken();
        }
        
        // T_STRING
        $nextToken = $this->codeParser->currentToken();
		
		// Include the class/function name into stack
		array_push($this->defStack, $nextToken[1]);

		$tk = $this->codeParser->currentToken();

        // Returning to the last parsable token
        while(!is_array($tk) || (is_array($tk) && (token_name((int) $tk[0]) != "T_CLASS" && token_name((int) $tk[0]) != "T_FUNCTION"))) {
            // Anything else: T_CLASS / T_FUNCTION
    		$tk = $this->codeParser->previousToken();
        }
		
		return $result;
	}
	
	
	function compileCustomCommentToken($token)
	{
		$result = "";

		// Just process if there's at least a function or class defined
		if (count($this->defStack) >= 2) {
			preg_match_all("/\/\/\/\s*Pointcut\s*:\s*([^\r\n]*)/i", $token[1], $pointcut, PREG_OFFSET_CAPTURE);

            // Pointcuts found?
			if (is_array($pointcut) && count($pointcut) > 0 && count($pointcut[0]) > 0) {
				// Retrieve the class name
				$class = array_shift($this->defStack);
				
				// Look for method name
				$method = "";

				for ($i = count($this->defStack) - 1; $i >= 0; $i--) {
					if ($this->defStack[$i] != "{" && $this->defStack[$i] != "}") {
						$method = $this->defStack[$i];
						break;
					}
				}

				// Grab the Advice code
				$advice = & $this->getAdviceFromCustomPointcut($class, $method, $pointcut[1][0][0]);
				$result .= $advice->getData();// . "\r\n";

				// Put the class name back into stack
				array_unshift($this->defStack, $class);
			} else {
				// Append token into result string
				$result .= $token[1];
			}
		} else {
			// Append token into result string
			$result .= $token[1];
		}
		
		return $result;
	}
	
	
	function compileCurlyBracesMissings()
	{
		// Inserting missing braces (does only match up to 2 nested parenthesis)
	    $this->code = preg_replace("/(if|for|while|switch)\s*(\([^()]*(\([^()]*\)[^()]*)*\))([^{;]*;)/i", "\\1 \\2 {\\4 }", $this->code);

		// [FIXME] Missing braces for else statements
	    $this->code = preg_replace("/(else)\s*([^{;]*;)/i", "\\1 {\\2 }", $this->code);
	}

	
	function compileAutoPointcuts()
	{
    	// Create Code Parser
		$this->codeParser = new AOP_CodeParser($this->code);
		$this->codeParser->setIndex(0);
		
		// Blank result string
		$result = "";

		// Loop through each PHP token
		while (($tok = $this->codeParser->currentToken()) !== null) {
			$result .= $this->analizeAutoToken($tok);

			$this->codeParser->nextToken();
		}

		// Assigning properties
		$this->code = $result;
		$this->codeParser = null;
	}
	
	
	function analizeAutoToken($token)
	{
		$result = "";

        if (!is_array($token)) {
        	switch ($token) {
				case "{":
					$result .= $this->compileAutoCurlyOpenToken();
					break;

				case "}":
                    $result .= $this->compileAutoCurlyCloseToken();
					break;

				default:
					$result .= $token;
					break;
			}
        } else {
        	switch (token_name((int) $token[0])) {
				case "T_CLASS":
				case "T_FUNCTION":
					$result .= $this->compileAutoClassOrMethodToken($token);
					break;

				case "T_EXIT":
				case "T_RETURN":
					$result .= $this->compileAutoExitOrReturnToken($token);
					break;

	            default:
	            	// Append token into result string
					$result .= $token[1];
					break;
	        }
        }

		return $result;
	}
	
	
	function compileAutoCurlyOpenToken()
	{
		// Append token into result string
		$result = "{";

		// Just process if there's at least a function or class defined
		if (count($this->defStack) >= 1) {
			// Retrieve the possible method name
			$method = array_pop($this->defStack);

            // Check for inner definition of curly. If the last defined token
			// is not a method name, do not use it.
			if (is_array($method)) {
				// Retrieve the class name
				$class = array_shift($this->defStack);

				// Check if it is a function or a method
				if (($class === null || (is_array($class) && $class[0] === "class")) && $method[0] !== "class") {
					// Grab the Advice code
					$advice = & $this->getAdviceFromAutoPointcut($class[1], $method[1], "before");
					$result .= " " . $advice->getData();
				}

				// If it is a method ($class contains a class token), put back on stack
				if ($class !== null) {
					// Put the class name back into stack
					array_unshift($this->defStack, $class);
				}
			}

			// Put the method, curly or other token back to the stack
			array_push($this->defStack, $method);
		}

		// Include the curly open into stack
		array_push($this->defStack, "{");

		return $result;
	}
	
	
	function compileAutoCurlyCloseToken()
	{
		$result = "";

		// Just process if there's at least a function or class defined
		if (count($this->defStack) >= 1) {
			// Retrieve possible curly open and method name
			$curly = array_pop($this->defStack);
			$method = array_pop($this->defStack);

			// Check if it's really a method definition
			if (!is_array($curly) && $curly == "{" && is_array($method)) {
				// Retrieve the class name
				$class = array_shift($this->defStack);

                // Check if it is a function or a method
				if (($class === null || (is_array($class) && $class[0] === "class")) && 
					$method[0] !== "class" && $this->returned === false) {
					// Grab the Advice code
					$advice = & $this->getAdviceFromAutoPointcut($class[1], $method[1], "after");
					$result .= $advice->getData();
				}

                // If it is a method ($class contains a class token), put back on stack
                if ($class !== null) {
					// Put the class name back into stack
					array_unshift($this->defStack, $class);
				}
				
				$this->returned = false;
			} elseif ((!is_array($curly) && $curly == "{") && (!is_array($method) && ($method == "{" || $method == "}"))) {
				// Put the function, curly or other token back to the stack
				array_push($this->defStack, $method);
			}
		} else {
			// Remove class definition and/or garbage
			$this->defStack = array();
		}

        // Append token into result string
		return $result . "}";
	}
	
	
	function compileAutoClassOrMethodToken($token)
	{
		// Append token into result string
		$result = $token[1];

        $tk = $this->codeParser->nextToken();

        // Finding the next parsable token
        while(!is_array($tk) || (is_array($tk) && token_name((int) $tk[0]) != "T_STRING")) {
            // T_WHITESPACE or &
    		$tk = $this->codeParser->nextToken();
        }
        
        // T_STRING
        $nextToken = $this->codeParser->currentToken();

        // Include the class/function name into stack
		array_push($this->defStack, array($token[1], $nextToken[1]));

        $tk = $this->codeParser->currentToken();

        // Returning to the last parsable token
        while(!is_array($tk) || (is_array($tk) && (token_name((int) $tk[0]) != "T_CLASS" && token_name((int) $tk[0]) != "T_FUNCTION"))) {
            // Anything else: T_CLASS / T_FUNCTION
    		$tk = $this->codeParser->previousToken();
        }

		return $result;
	}
	
	
	function compileAutoExitOrReturnToken($token)
	{
		$result = "";

		// Retrieve the class name
		$class = array_shift($this->defStack);

		// Look for method name
		$method = "";

		for ($i = count($this->defStack) - 1; $i >= 0; $i--) {
			if (is_array($this->defStack[$i])) {
				$method = $this->defStack[$i];
				break;
			}
		}

		// Retrieve defined user code
		$advice = & $this->getAdviceFromAutoPointcut($class[1], $method[1], "after");
		$code = $advice->getData();

		// Add space if any code is defined
    	if (strlen($code) > 0) {
			$result .= $code . " ";
		}

		// Retrieve token name
		$tokenName = token_name((int) $token[0]);

		// Append token into result string
		$result .= ($tokenName == "T_EXIT") ? "exit" : "return";
		
		// Put the class name back into stack
		array_unshift($this->defStack, $class);

		// Check if last statement is a return (inclusion of advice after the last command as return)
		if ((is_array($class) && $class[0] === "class" && count($this->defStack) < 5) || count($this->defStack) < 3) {
        	$this->returned = true;
		}
		
		return $result;
	}

	
	function & getAdviceFromCustomPointcut($class, $method, $pointcutName)
	{
		$advice = & new Advice();

		$a = & $this->weave->getAdviceFromCustomPointcut($class, $method, $pointcutName);
		$code = $a->getData();

		// Does it has any code to replace?
		if (strlen($code) > 0) {
			// PHP Code Cruncher
			if ($this->compact) {
				$code = AOP_CodeCruncher::process($code);
			} else {
				$code = "\r\n" . $code . "\r\n";
			}

			// Add an informative text
			$code = "/* AOP \"" . $pointcutName . "\" Code */ " . $code . " ";
		}
		
		$advice->addData($code);
		
		return $advice;
	}
	
	
	function & getAdviceFromAutoPointcut($class, $method, $autoPointcut)
	{
		$advice = & new Advice();

		$a = & $this->weave->getAdviceFromAutoPointcut($class, $method, $autoPointcut);
		$code = $a->getData();

		// Does it has any code to replace?
		if (strlen($code) > 0) {
			// PHP Code Cruncher
			if ($this->compact) {
				$code = AOP_CodeCruncher::process($code);
			} else {
				$code = "\r\n" . $code . "\r\n";
			}

			// Add an informative text
			$code = "/* AOP \"" . $autoPointcut . "\" Auto Code */ " . $code . " ";
		}
		
		$advice->addData($code);
		
		return $advice;
	}
}


?>
Return current item: Transparent PHP AOP