<?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;
}
}
?>