<?php
/**
 * @package EasyLexSQL
 * @varsion $Id: EasyLex_SQLScanner.class.php,v 1.1.2.1 2006/10/19 10:20:58 minahito Exp $
 * 
 * In the original BSD license, both occurrences of the phrase "COPYRIGHT
 * HOLDERS AND CONTRIBUTORS" in the disclaimer read "REGENTS AND CONTRIBUTORS".
 * 
 * Copyright (c) 2006, XOOPS Cube Project team/minahito (minahito@users.sourceforge.jp)
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * a) Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * b) Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * c) Neither the name of the XOOPS Cube Project team nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *    
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

define('EASYLEX_SQL_UNKNOWN', 0);
define('EASYLEX_SQL_DIGIT', 1);
define('EASYLEX_SQL_LETTER', 2);
define('EASYLEX_SQL_STRING_LITERAL', 3);
define('EASYLEX_SQL_STRING_LITERAL_ESCAPE', 10);
define('EASYLEX_SQL_OPEN_PARENTHESIS', 4);
define('EASYLEX_SQL_CLOSE_PARENTHESIS', 5);
define('EASYLEX_SQL_SEPARATER', 6);
define('EASYLEX_SQL_SEMICOLON', 7);
define('EASYLEX_SQL_MARK', 8);
define('EASYLEX_SQL_COMMA', 9);

/**
 * This is BSD easy lexcal scanner for SQL.
 * 
 * @version 1.00
 */
class EasyLex_SQLScanner
{
	var $mTokens;
	var $mStatus = EASYLEX_SQL_UNKNOWN;
	
	/**
	 * @var Array of char
	 */
	var $mBuffer = array();
	
	var $mIndex = 0;
	
	var $mActiveToken = '';
	
	var $mActiveQuoteMark = null;
	
	function setBuffer($buffer)
	{
		$this->mBuffer = array();
		for ($i = 0; $i < strlen($buffer); $i++) {
			$this->mBuffer[$i] = $buffer{$i};
		}
		
		$this->mIndex = 0;
	}
	
	function parse()
	{
		while ($this->mIndex <= count($this->mBuffer)) {
			if ($this->mIndex == count($this->mBuffer)) {
				$ch = '';
				$type = EASYLEX_SQL_UNKNOWN;
			}
			else {
				$ch = $this->mBuffer[$this->mIndex];
				$type = $this->_getChrType($ch);
			}
			
			switch ($this->mStatus) {
				case EASYLEX_SQL_UNKNOWN:
					$this->_parseUnknown($ch, $type);
					break;
					
				case EASYLEX_SQL_DIGIT:
					$this->_parseDigit($ch, $type);
					break;
					
				case EASYLEX_SQL_LETTER:
					$this->_parseLetter($ch, $type);
					break;
					
				case EASYLEX_SQL_STRING_LITERAL:
					$this->_parseStringLiteral($ch, $type);
					break;
					
				case EASYLEX_SQL_STRING_LITERAL_ESCAPE:
					$this->_parseStringLiteralEscape($ch, $type);
					break;
					
				case EASYLEX_SQL_OPEN_PARENTHESIS:
					$this->_parseOpenParenthesis($ch, $type);
					break;
					
				case EASYLEX_SQL_CLOSE_PARENTHESIS:
					$this->_parseCloseParenthesis($ch, $type);
					break;
					
				case EASYLEX_SQL_SEPARATER:
					$this->_parseSeparater($ch, $type);
					break;
					
				case EASYLEX_SQL_MARK:
					$this->_parseMark($ch, $type);
					break;
					
				case EASYLEX_SQL_SEMICOLON:
					$this->_parseSemicolon($ch, $type);
					break;
					
				case EASYLEX_SQL_COMMA:
					$this->_parseComma($ch, $type);
					break;
			}
		}
	}
	
	/**
	 * Load file and set buffer. If $preprocess is true, scan commetns and
	 * remove these.
	 * 
	 * @param  string $path file path
	 * @param  bool   $preprocess
	 * @return bool
	 */
	function loadFile($path, $preprocess = true)
	{
		if (!file_exists($path)) {
			return false;
		}

		$fp = fopen($path, "rb");
		if (!$fp) {
			return false;
		}
		
		$t_buff = "";
		while ($str = fgets($fp)) {
			if ($preprocess) {
				$str = preg_replace("/^\s*\#.*/", "", $str);
			}
			$t_buff .= $str;
		}
		
		$this->setBuffer($t_buff);
		
		fclose($fp);
		return true;
	}
	
	function _getChrType($ch)
	{
		if (preg_match("/\s/", $ch)) {
			return EASYLEX_SQL_SEPARATER;
		}
		
		if ($ch == '(') {
			return EASYLEX_SQL_OPEN_PARENTHESIS;
		}
		
		if ($ch == ')') {
			return EASYLEX_SQL_CLOSE_PARENTHESIS;
		}
		
		if ($ch == ';') {
			return EASYLEX_SQL_SEMICOLON;
		}
		
		if ($ch == ',') {
			return EASYLEX_SQL_COMMA;
		}
		
		if (preg_match("/[0-9]/", $ch)) {
			return EASYLEX_SQL_DIGIT;
		}
		
		if (preg_match("/[!=<>%\*]/", $ch)) {
			return EASYLEX_SQL_MARK;
		}
		
		return EASYLEX_SQL_LETTER;
	}
	
	function _parseUnknown($ch, $type)
	{
		$this->mStatus = $type;
		$this->mActiveToken .= $ch;
		$this->mIndex++;
		
		if ($ch == "'" || $ch == '"' || $ch == '`') {
			$this->mStatus = EASYLEX_SQL_STRING_LITERAL;
			$this->mActiveQuoteMark = $ch;
		}

	}
	
	function _parseDigit($ch, $type)
	{
		if ($type == EASYLEX_SQL_DIGIT) {
			$this->mActiveToken .= $ch;
			$this->mIndex++;
		}
		elseif ($type == EASYLEX_SQL_LETTER) {
			$this->mStatus = EASYLEX_SQL_LETTER;
			$this->mActiveToken .= $ch;
			$this->mIndex++;
		}
		else {
			$this->_createToken();
		}
	}
	
	function _parseLetter($ch, $type)
	{
		if ($type == EASYLEX_SQL_LETTER || $type == EASYLEX_SQL_DIGIT) {
			$this->mActiveToken .= $ch;
			$this->mIndex++;
		}
		else {
			$this->_createToken();
		}
	}
	
	function _parseStringLiteral($ch, $type)
	{
		$this->mActiveToken .= $ch;
		$this->mIndex++;
		
		if ($ch == "\\") {
			$this->mStatus = EASYLEX_SQL_STRING_LITERAL_ESCAPE;
		}
		elseif ($ch == $this->mActiveQuoteMark) {
			$this->_createToken();
		}
	}
	
	function _parseStringLiteralEscape($ch, $type)
	{
		$this->mStatus = EASYLEX_SQL_STRING_LITERAL;
	}
	
	function _parseOpenParenthesis($ch, $type)
	{
		$this->_createToken();
	}
	
	function _parseCloseParenthesis($ch, $type)
	{
		$this->_createToken();
	}
	
	function _parseSeparater($ch, $type)
	{
		if ($type == EASYLEX_SQL_SEPARATER) {
			$this->mActiveToken .= $ch;
			$this->mIndex++;
		}
		else {
			// $this->_createToken();
			$this->mStatus = EASYLEX_SQL_UNKNOWN;
			$this->mActiveToken = "";
		}
	}
	
	function _parseSemicolon($ch, $type)
	{
		$this->_createToken();
	}
	
	function _parseMark($ch, $type)
	{
		if ($type == EASYLEX_SQL_MARK) {
			$this->mActiveToken .= $ch;
			$this->mIndex++;
		}
		else {
			$this->_createToken();
		}
	}
	
	function _parseComma($ch, $type)
	{
		$this->_createToken();
	}
	
	function _createToken($type = null, $value = null)
	{
		if ($type === null) {
			$type = $this->mStatus;
		}
		
		if ($value === null) {
			$value = $this->mActiveToken;
		}
		
		$token =& new EasyLex_SQLToken($type, $value);
		$this->mTokens[] =& $token;
		
		$this->mStatus = EASYLEX_SQL_UNKNOWN;
		$this->mActiveToken = "";
		
		return $token;
	}
	
	/**
	 * Return Array of operations.
	 * 
	 * @return Array $ret[Index] = Array of tokens.
	 */
	function &getOperations()
	{
		$ret = array();
		$t_tokens = array();
		$depth = 0;
		
		foreach (array_keys($this->mTokens) as $key) {
			if ($this->mTokens[$key]->mType == EASYLEX_SQL_OPEN_PARENTHESIS) {
				$depth++;
			}
			elseif ($this->mTokens[$key]->mType == EASYLEX_SQL_CLOSE_PARENTHESIS) {
				$depth--;
			}
			
			$t_tokens[] =& $this->mTokens[$key];
			
			if ($this->mTokens[$key]->mType == EASYLEX_SQL_SEMICOLON && $depth == 0) {
				$ret[] =& $t_tokens;
				unset($t_tokens);
				$t_tokens = array();
			}
		}
		
		if (count($t_tokens) > 0) {
			$ret[] =& $t_tokens;
			unset($t_tokens);
		}
		
		return $ret;
	}
	
	function getSQL()
	{
		$sqls = array();
		$lines =& $this->getOperations();
		
		foreach ($lines as $line) {
			$t_arr = array();
			foreach ($line as $token) {
				$t_arr[] = $token->getOutputValue();
			}
			$sqls[] = join(" ", $t_arr);
		}
		
		return $sqls;
	}
}

class EasyLex_SQLToken
{
	var $mType = EASYLEX_SQL_UNKNOWN;
	var $mValue = "";
	
	function EasyLex_SQLToken($type, $value)
	{
		$this->mType = $type;
		$this->mValue = $value;
	}
	
	function getOutputValue()
	{
		if ($this->mType == EASYLEX_SQL_SEPARATER) {
			return "";
		}
		else {
			return $this->mValue;
		}
	}
	
	function getValue()
	{
		if ($this->mType == EASYLEX_SQL_SEPARATER) {
			return "";
		}
		
		if ($this->mType == EASYLEX_SQL_STRING_LITERAL) {
			return substr($this->mValue, 1, strlen($this->mValue) - 2);
		}
		
		return $this->mValue;
	}
}

?>