index.php
<?php

error_reporting(E_ALL | E_STRICT);
ini_set('display_errors', 'on');

include 'lib.php';

$str = $_SERVER['QUERY_STRING'];

$parser = new Parser();

$fac = function($v){
	$n = round($v[0]);
	if($n > 0) {
		$return = $n;
		while(--$n > 0) {
			$return *= $n;
		}
		return $return;
	} elseif($n == 0) {
		return 1;
	} else {
		throw new Exception('Faculty using neg. number');
		return false;
	}
};
			
$parser ->addOperator('Multiply', '*', 2, function($v){return $v[0]*$v[1];})
		->addOperator('Division', '/', 2, function($v){if($v[1] == 0) {throw new Exception('Division by 0');} return $v[0]/$v[1];})
		->addOperator('Plus', '+', 1, function($v){return $v[0]+$v[1];})
		->addOperator('Minus', '-', 1, function($v){return $v[0]-$v[1];})
		->addOperator('Power', '^', 3, function($v){return pow($v[0],$v[1]);})
		->addOperator('Faculty', '!', 4, $fac, OperatorRule::VAR_BEFORE)
		->addOperator('SquareRoot', 'W', 3, function($v){if($v[0] < 0) {throw new Exception('Irreal result');} return sqrt($v[0]);}, OperatorRule::VAR_AFTER)
		->addOperator('Logarithm', 'log', 4, function($v){if($v[0] < 0 || $v[1] < 0) {throw new Exception('Irreal result');} return log($v[0],$v[1]);})
		->addOperator('Pi', 'pi', null, function($v){return M_PI;}, OperatorRule::VAR_NONE)
		->addOperator('E', 'e', null, function($v){return M_E;}, OperatorRule::VAR_NONE)
		->addOperator('Equation', '=', 0, function($v){return ($v[0]==$v[1])?1:0;});
try {
	$res =  $parser->parse($str);
	echo $str.'&nbsp;&nbsp;&nbsp;=&nbsp;&nbsp;&nbsp;'.$res;
} catch(Exception $e) {
	echo $e->getMessage();
}
?>
index_old.ph
<?php

error_reporting(E_ALL | E_STRICT);
ini_set('display_errors', 'on');

include 'lib.php';

$str = $_SERVER['QUERY_STRING'];

$parser = new Parser();

function calcMultiply($v) {
	return $v[0]*$v[1];
}

function calcDivision($v) {
	if($v[1] == 0) {
		throw new Exception('Division by 0');
	}
	return $v[0]/$v[1];
}

function calcPlus($v) {
	return $v[0]+$v[1];
}

function calcMinus($v) {
	return $v[0]-$v[1];
}

function calcPower($v) {
	return pow($v[0], $v[1]);
}

function calcFaculty($v){
	$n = round($v[0]);
	if($n > 0) {
		$return = $n;
		while(--$n > 0) {
			$return *= $n;
		}
		return $return;
	} elseif($n == 0) {
		return 1;
	} else {
		throw new Exception('Faculty using neg. number');
		return false;
	}
};
			
function calcSquareRoot($v) {
	if($v[0] < 0) {
		throw new Exception('Irreal result');
	}
	return sqrt($v[0]);
}

function calcLogarithm($v) {
	if($v[0] < 0 || $v[1] < 0) {
		throw new Exception('Irreal result');
	}
	return log($v[0], $v[1]);
}

function calcE($v) {
	return M_E;
}

function calcEquation($v) {
	return ($v[0]==$v[1])?1:0;
}

$parser ->addOperator('Multiply', '*', 2, 'calcMultiply')
		->addOperator('Division', '/', 2, 'calcDivision')
		->addOperator('Plus', '+', 1, 'calcPlus')
		->addOperator('Minus', '-', 1, 'calcMinus')
		->addOperator('Power', '^', 3, 'calcPower')
		->addOperator('Faculty', '!', 4, 'calcFaculty', OperatorRule::VAR_BEFORE)
		->addOperator('SquareRoot', 'W', 3, 'calcSquareRoot', OperatorRule::VAR_AFTER)
		->addOperator('Logarithm', 'log', 4, 'calcLogarithm')
		->addOperator('Pi', 'pi', null, 'pi', OperatorRule::VAR_NONE)
		->addOperator('E', 'e', null, 'calcE', OperatorRule::VAR_NONE)
		->addOperator('Equation', '=', 0, 'calcEquation');
try {
	$res =  $parser->parse($str);
	echo $str.'&nbsp;&nbsp;&nbsp;=&nbsp;&nbsp;&nbsp;'.$res;
} catch(Exception $e) {
	echo $e->getMessage();
}
?>
lib.php
<?php
/**
 * CalculationParser
 * 
 * @autor Pim de Haan <pimdehaan@gmail.com>
 * @version 1.0
 * @package CalculationParser
 * 
 * @todo Support for argumented operators, e.g. x(a, b)
 * @todo Improving error messages
 * @todo Seperate Lexer and part handler
 */
/**
 * The Parser class
 * @package CalculationParser
 */
class Parser {
	
	/**
	 * Storing the lexer
	 * @var Lexer
	 */
	private $_lex;
	
	/**
	 * List of added {@link OperatorRule}
	 * @var array
	 */
	private $_operators = array();								
	
	/**
	 * Constructor sets up the {@link $_lex} 
	 */
	public function __construct() {
		$this->_lex = new Lexer;
	}
	
	/**
	 * Adds an {@link OperatorRule} to {@link $_operators}
	 * Return itself for chained calls
	 * 
	 * @param string $name Name
	 * @param string $symbol Symbol
	 * @param int $hierarchy Hierarchy level
	 * @param Closure $function The anonymous function which provides the actual operation
	 * @param int $varPos Providing where the operators elements are located
	 * @return Parser
	 */
	public function addOperator($name, $symbol, $hierarchy, $function, $varPos = null) {
		$this->_operators[$symbol] = new OperatorRule($name, $symbol, $hierarchy, $function, $varPos);
		return $this;
	}
	
	/**
	 * Return the array of operator symbols
	 * @return array
	 */
	private function _operatorArray() {
		$return = array();
		foreach($this->_operators as $op) {
			$return[] = $op->getSymbol();
		}
	}
	
	/**
	 * The actual parsing.
	 * At first, splits the input string by the {@link Lexer}
	 * Then loops following code until only a single float remains:
	 * <ul>
	 * 
	 * 	<li>All tokens, {@link Token}, are retrieved one by one.
	 * 	<ul>
	 * 		<li>If the token is a operator, {@link OperatorToken}, a new {@link Operator} is added to the operators array along with its hierarchy and part offset.</li>
	 * 		<li>If it is a parentesis, {@link OpenToken} or {@link CloseToken}, the parenthesesOpen variable is modified, which alters the next operators hierarchy.</li>
	 * 	</ul></li>
	 * 
	 * 	<li>Then the operator with the higherst hierarchy is selected.</li>
	 * 
	 * 	<li>If the operator has a variable in front of it, the next actions are exectued
	 * 	<ul>
	 * 		<li>The number in front of it is retrieved</li>
	 * 		<li>It is tested wether it's a number</li>
	 * 		<li>It is deleted from the {@link Lexer}s parts list</li>
	 * 		<li>It is added as a variable to the operator</li>
	 * 	</ul>
	 * 	This is also executed if it has a variable after it</li>
	 * 
	 * 	<li>The operators result is calculated</li>
	 * 	<li>The result replaces the operator in the {@link Lexer}s parts list</li>
	 * 	<li>The parenteses behind and after it are deleted from the {@link Lexer}s parts list</li>
	 * 
	 * </ul>
	 * 
	 * @return int
	 */
	public function parse($text) {
		
		$this->_lex->lex($text, array_keys($this->_operators));
		
		while(false === $res = $this->_lex->done()) {
			
			$parenthesesOpen = 0;
			
			$operators = array();
			
			$this->_lex->reset();
			
			while(false !== $token = $this->_lex->getToken()) {
				if($token instanceof OperatorToken) {
	
					$hierarchy = $this->_operators[$token->getOperation()]->getHierarchy() + 20*$parenthesesOpen;
					$operators[] = new Operator($this->_operators[$token->getOperation()], $hierarchy, $token->getOffset());
					
				} elseif($token instanceof OpenToken) {
					$parenthesesOpen++;
				} elseif($token instanceof CloseToken) {
					if($parenthesesOpen-- == 0) {
						throw new Exception('Syntax Error');
					}
				}
			}
			
			if(empty($operators)) {
				throw new Exception('Syntax Error');
			}
			
			$highestOperator = null;
			$highestHierarchy = -10;
			
			foreach($operators as $operator) {
				if($operator->getHierarchy() > $highestHierarchy) {
					$highestOperator = $operator;
					$highestHierarchy = $operator->getHierarchy();
				}
			}
			
			$operator = $highestOperator;
			$operatorOffset = $operator->getOffset();
			
			$varPositions = $operator->getRule()->getVarPositions();
			
			$parenthesisBeforeOffset = 1;
			$parenthesisAfterOffset = 1;
			
			// If operator has a variable before it
			if($varPositions & OperatorRule::VAR_BEFORE) {
				$num = $this->_lex->getToken($operatorOffset-1);
				if(!is_float($num)) {
					throw new Exception('Syntax error');
				}
				$this->_lex->delete($operatorOffset-1);
				$operator->addVar($num);
				
				$parenthesisBeforeOffset = 2;
			}
			
			// If operator has a variable after it
			if($varPositions & OperatorRule::VAR_AFTER) {
				$num = $this->_lex->getToken($operatorOffset+1);
				if(!is_float($num)) {
					throw new Exception('Syntax error');
				}
				$this->_lex->delete($operatorOffset+1);
				$operator->addVar($num);
				
				$parenthesisAfterOffset = 2;
			}
			
			$res = $operator->calc();
			
			$this->_lex->replace($operatorOffset, $res);
			
			
			// Delete Haakjes
			if($this->_lex->getToken($operatorOffset-$parenthesisBeforeOffset) instanceof OpenToken &&
					$this->_lex->getToken($operatorOffset+$parenthesisAfterOffset) instanceof CloseToken) {
				$this->_lex->delete($operatorOffset-$parenthesisBeforeOffset)->delete($operatorOffset+$parenthesisAfterOffset);
			}
			
			$this->_lex->update();
		}
		
		return $res;
	}
}

/**
 * The lexer wich splits the input text into parts and provides and modifies these parts
 * @package CalculationParser
 */

class Lexer {
	
	/**
	 * The list of parts
	 * @var array
	 */
	private $_parts;
	
	/**
	 * The amount of parts
	 * @var int
	 */
	private $_partsCount;
	
	/**
	 * The internal pointer
	 * @var int
	 */
	private $_pointer = -1;
	
	/**
	 * The list of operator symbols
	 * @var array
	 */
	private $_operators;
	
	/**
	 * The lexer function, which:
	 * <ul>
	 * 	<li>Removes spaces</li>
	 * 	<li>Modefies the shorthand writing, 4(5+2) to 4*(5+2)</li>
	 * 	<li>Prepares the operator list using {@link $_operators} to be used in the regex</li>
	 * 	<li>Splits using preg_match_all</li>
	 * 	<li>Checks wether parts are remaining and if so, throw an excpetion</li>
	 * 	<li>Fills {@link $_parts} and {@link $_partsCount}</li>
	 * </ul>
	 * 
	 * @param string $text The input text
	 * @param array $operators The array of operator symbols {@link $_operators}
	 */
	public function lex($text, $operators) {
		$text = str_replace(' ', '', $text);
		$text = preg_replace('/([0-9|\)])\(/', '$1*(', $text);
		
		$this->_operators = $operators;
		
		$tokenList = implode('|', array_map('preg_quote', $operators));
		
		/**
		 * Explanation regex:
		 * Define part if string:
		 * - is a Operator
		 * - is a Parentesis
		 * - is a number may or may not be followed by a dot and another number
		 * 								   following a minus sign, which is not following by a number
		 * 
		 * 									followed by a E and a positive or negative number
		 * - is a dot follewed by a number may or may not be following a minus sign, which is not following a number
		 */
		
		$reg = '#((?<!\d)\-)?((\d+(\.?\d+)?)|(\.\d+))(E[\+\-]?\d+?)?|'.$tokenList.'|\(|\)#';

		preg_match_all($reg, $text, $matches);
		
		// Checks for remaining parts
		$total = 0;
		foreach($matches[0] as $match) {
			$total += strlen($match);
		}
		if($total != strlen($text)) {
			throw new Exception('Syntax error');
		}
		
		$this->_parts = $matches[0];
		
		$this->_partsCount = count($this->_parts);
		
		if($this->_partsCount == 0) {
			throw new Exception('No input given');
		}
	}
	
	/**
	 * Detemines wether only one float is left and if so, returns its value
	 * 
	 * @return int|false
	 */
	public function done() {
		if($this->_partsCount == 1 && is_numeric($this->_parts[0])) {
			return ((float)$this->_parts[0]);
		} else {
			return false;
		}
	}
	
	/**
	 * Analyzes {@link _analyze()} and returns next {@link Token} or the token at the specified offset
	 * 
	 * @param int $offset The optional offset
	 * @return Token
	 */
	public function getToken($offset = null) {
		if(is_null($offset)) {
			$offset = ++$this->_pointer;
		}
		
		if(isset($this->_parts[$offset])) {
			return $this->_analyze($this->_parts[$offset], $offset);
		} else {
			return false;
		}
	}
	
	/**
	 * Analyses the given string and returns a {@link Token} or float
	 * 
	 * @param string $token The input string
	 * @param int $offset The strings offset
	 * @return float|OpenToken|CloseToken|OperatorToken
	 */
	private function _analyze($token, $offset) {
		
		if(is_numeric($token)) {
			return (float) $token;
		} elseif($token == '(') {
			return new OpenToken;
		} elseif($token == ')') {
			return new CloseToken;
		} else {
			if(!in_array($token, $this->_operators)) {
				throw new Exception('Foute operator: "'.$token.'"');
				return;
			} else {
				return new OperatorToken($token, $offset);
			}
		}	
	}
	
	/**
	 * Updates {@link $_parts} by removing empty parts
	 * 
	 * @return Lexer
	 */	
	public function update() {
		$new = array();
		foreach($this->_parts as $part) {
			$new[] = $part;
		}
		$this->_parts = $new;
		$this->_partsCount = count($this->_parts);
		
		return $this;
	}
	
	/**
	 * Deletes part from {@link $_parts} at given offset
	 * 
	 * @param int $offset Offset
	 * @return Lexer
	 */
	public function delete($offset) {
		if(isset($this->_parts[$offset])) {
			unset($this->_parts[$offset]);
		}
		return $this;
	}
	
	/**
	 * Resets internal {@link $_pointer}
	 * 
	 * @return Lexer
	 */
	public function reset() {
		$this->_pointer = -1;
		return $this;
	}
	
	/**
	 * Replaces part at given offset with given part
	 * 
	 * @param int $offset Offset
	 * @param float|string $part Replacement value
	 */
	public function replace($offset, $part) {
		$this->_parts[$offset] = $part;
		return $this;
	}
}

/**
 * Currently empty abstract class which is extends by specified Token classes
 * 
 * Tokens are parts returned by {@link Lexer::_getToken()} which contain a certain part of the input string
 * @package CalculationParser
 */
abstract class Token {}

/**
 * Token for an opening parentesis
 * @package CalculationParser
 */
class OpenToken extends Token {}

/**
 * Token for an closing parentesis
 * @package CalculationParser
 */
class CloseToken extends Token {}

/**
 * Token for an operator
 * @package CalculationParser
 */
class OperatorToken extends Token {
	
	/**
	 * The operation symbol
	 * @var string
	 */
	private $_operation;
	
	/**
	 * The offset in the {@link Lexer::$_parts}
	 * @var string
	 */
	private $_offset;
	
	/**
	 * The constructor, sets {@link $_operation} and {@link $_offset}
	 * 
	 * @param string $operation The operation symbol
	 * @param int $offset The offset
	 */
	public function __construct($operation, $offset) {
		$this->_operation = $operation;
		$this->_offset = $offset;
	}
	
	/**
	 * Returns {@link $_operation}
	 * @return string
	 */
	public function getOperation() {
		return $this->_operation;
	}
	
	/**
	 * Returns {@link $_offset}
	 * @return int
	 */
	public function getOffset() {
		return $this->_offset;
	}
}

/**
 * The operator in {@link Parser::parse()}
 * @package CalculationParser
 */
class Operator {
	
	/**
	 * The {@link OperatorRule}
	 * @var OperatorRule
	 */
	private $_rule;
	
	/**
	 * The hierarchy
	 * @var int
	 */
	private $_hierarchy;
	
	/**
	 * The offset in the {@link Lexer::$_parts}
	 * @var int
	 */
	private $_offset;
	
	/**
	 * The variables
	 * @var array
	 */
	private $_vars = array();
	
	/**
	 * The constructor, sets {@link $_rule}, {@link $_hierarchy} and {@link $_offset}
	 * 
	 * @param OperatorRule $rule The OperatorRule
	 * @param int $hierarchy The hierarchy
	 * @param int $offset The offset
	 */
	public function __construct(OperatorRule $rule, $hierarchy, $offset) {
		$this->_rule = $rule;
		$this->_hierarchy = $hierarchy;
		$this->_offset = $offset;
	}
	
	/**
	 * Returns {@link $_rule}
	 * @return OperatorRule
	 */
	public function getRule() {
		return $this->_rule;
	}
	
	/**
	 * Returns {@link $_hierarchy}
	 * @return int
	 */
	public function getHierarchy() {
		return $this->_hierarchy;
	}
	
	/**
	 * Returns {@link $_offset}
	 * @return int
	 */
	public function getOffset() {
		return $this->_offset;
	}
	
	/**
	 * Adds a variable to {@link $_vars}
	 * 
	 * @param float $var The variable
	 * @return Operator
	 */
	public function addVar($var) {
		$this->_vars[] = $var; 
		return $this;
	}
	
	/**
	 * Do the calculation based on {@link $_vars} by calling the anonymous funtion in the {@link $_rule}
	 */
	public function calc() {
		return $this->_rule->calc($this->_vars);
	}
}

/**
 * Class for defining operations
 * @package CalculationParser
 */
class OperatorRule {
	
	/**
	 * The name
	 * @var string
	 */
	private $_name;
	
	/**
	 * The symbol
	 * @var string
	 */
	private $_symbol;
	
	/**
	 * The base hierarchy
	 * @var int
	 */
	private $_hierarchy;
	
	/**
	 * The anonymous function
	 * @var Closure
	 */
	private $_function;
	
	/**
	 * The positions of the variables, bitwise
	 * @var int
	 */
	private $_varPositions;
	
	
	/**
	 * The constant for the variable position 'before'
	 * @see $_varPositions
	 * 
	 * @var int
	 */
	const VAR_BEFORE = 1;
	
	/**
	 * The constant for the variable position 'after'
	 * @see $_varPositions
	 * 
	 * @var int
	 */
	const VAR_AFTER = 2;
	
	/**
	 * The constant for the variable position 'after' and 'before'
	 * @see $_varPositions
	 * 
	 * @var int
	 */
	const VAR_BOTH = 3;
	
	/**
	 * The constant for no variable positions, meaning the operator is a constant
	 * @see $_varPositions
	 * 
	 * @var int
	 */
	const VAR_NONE = 0;
	
	/**
	 * The constructor, sets {@link $_name}, {@link $_symbol}, {@link $_hierarchy}, {@link $_function}, and optionally {@link $_varPositions}
	 * If the operator is a constant, the hierarchy is very high
	 * 
	 * @param string $name The name
	 * @param string $symbol The symbol
	 * @param int $hierarchy The hierarchy
	 * @param Closure $function The anonymous function
	 * @param int $varPositions The optional variable positions
	 */
	public function __construct($name, $symbol, $hierarchy, $function, $varPositions = null) {
		$this->_name = $name;
		$this->_symbol = $symbol;
		$this->_hierarchy = ($varPositions === self::VAR_NONE)?1000:$hierarchy;
		$this->_function = $function;
		$this->_varPositions = (!is_null($varPositions) ? $varPositions : self::VAR_BOTH);
	}
	
	/**
	 * Returns {@link $_name}
	 * @return string
	 */
	public function getName() {
		return $this->_name;
	}
	
	/**
	 * Returns {@link $_symbol}
	 * @return string
	 */
	public function getSymbol() {
		return $this->_symbol;
	}
	
	/**
	 * Returns {@link $_hierarchy}
	 * @return int
	 */
	public function getHierarchy() {
		return $this->_hierarchy;
	}
	
	/**
	 * Returns {@link $_varPositions}
	 * @return int
	 */
	public function getVarPositions() {
		return $this->_varPositions;
	}
	
	/**
	 * Do the calculation by executing the {@link $_function} and give the arguments to this method as arguments to the anonymous function
	 * @param float $v,... The variables
	 * @return float The result
	 */
	public function calc() {
		$args = func_get_args();
		return (float) call_user_func_array($this->_function, $args);
	}
	
}
?>