<?php
/*
 * SHL ver 0.4.0                        | Universal Syntax HighLighter |
 * ---------------------------------------------------------------------

   Copyright (C) 2002-2003  Juraj 'hvge' Durech

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
   
 * ---------------------------------------------------------------------
 * shl.php
 *
 * Slow debug version. This code working directly with lang/XXX_lang.php 
 * files. While you testing new language use this class. If everything 
 * in your new grammar works fine, generate final fshl file with 
 * fshlGenerator class.
 *
 * ---------------------------------------------------------------------
 * NOTE: This piece of code is history. Latest versions of FSHL parsers 
 *       and languages are not compatible with shl.php. Now i using 
 *       directly FSHL and FSHL Generator for testing...
 * ---------------------------------------------------------------------
 */

define ("FSHL_PARSER_VERSION", "0.4.0");

class shlParser
{
	// class variables
	var $text, $textlen, $textpos;
	var $lang, $options, $output;
	var $langname;

	var $out, $groups;
	var $_trans, $_flags, $_data, $_delim, $_class, $_states;
	var $_ret,$_quit;

	var $error;

	// -----------------------------------------------------------------------
	// USER LEVEL functions
	//

	// class constructor
	function shlParser($language, $options = P_DEFAULT)
	{
		global $group_delimiters;

		$this->error = 0;
		
		$_lang = $language."_lang";
		$this->langname = "null";
		if(!file_exists(FSHL_LANG."$_lang.php"))
		{
			$this->print_error("Language file <b>'$_lang.php'</b> not found.");
			return;
		}
		require_once (FSHL_LANG."$_lang.php");
		$this->langname = $language;
		
		$this->lang = new $_lang;
		$this->options  = $options;
		$this->groups = &$group_delimiters;
		
		// internal language structures initialization
		$j=0;
		foreach ($this->lang->states as $state => $state_array)
		{
			if($state==P_QUIT_STATE)
				continue;
			else
				$this->_states[$state]=$j++;
		}
		$this->_states[P_RET_STATE] = $this->_ret = $j++;
		$this->_states[P_QUIT_STATE] = $this->_quit = $j++;
		foreach ($this->lang->states as $state => $state_array)
		{
			$state = $this->_states[$state];
			$this->_flags[$state] = $state_array[XL_FLAGS];
			$this->_data[$state] = $state_array[XL_DATA];
			$this->_class[$state] = $state_array[XL_CLASS];
			if(is_array($state_array[XL_DIAGR]))
			{
				$i=0;
				foreach ($state_array[XL_DIAGR] as $delimiter => $trans_array )
				{
					$this->_delim[$state][$i++]=$delimiter;
					$trans_array[XL_DSTATE]=$this->_states[$trans_array[XL_DSTATE]];
					$this->_trans[$state][$delimiter]=$trans_array;
				}
			}
			else
			{
				$this->_delim[$state]=null;
				$this->_trans[$state]=null;
			}
		}
		$this->lang->initial_state=$this->_states[$this->lang->initial_state];
		//echo "states ";	print_r($this->_states);echo "delim ";print_r($this->_delim);echo "trans ";	print_r($this->_trans);	echo "data ";print_r($this->_data);

	}

	// highlight string
	//
	function highlight_string ($output_mode, $text, $offset = 0)
	{
		// initialization
		if($this->error)
		{
			$this->print_error("There are some errors, return = null.");
			return null;
		}
		if(is_object($output_mode))
		{
			$this->output = &$output_mode;
		}
		else
		{
			$_out  = $output_mode."_output";
			if(!file_exists(FSHL_OUTMODULE."$_out.php"))
			{
				$this->print_error("Output module <b>'$_out.php'</b> not found.");
				return null;
			}
			require_once (FSHL_OUTMODULE."$_out.php");
			$this->output = new $_out;
		}
		$this->text = &$text;
		$this->textlen = strlen($text);
		$this->textpos = $offset;
		$this->out = null;

		// start parser
		$this->parse_string($this->lang->initial_state);
		if(!is_object($output_mode))
		{
			$this->out.=$this->output->template_end();
		}
		return $this->out;
	}

	function get_position()	{ return $this->textpos; }

	function get_out() { return $this->out;	}

	function is_error() { return $this->error;	}

// ---------------------------------------------------------------------------------
// LOW LEVEL functions
//
function print_error($text)
{
	if(DEBUG_REPORT) 
		print ("shl-parser: <b>$this->langname:</b> $text\n");
	$this->error++;
}

// main parser function
//
function parse_string ($state)
{
	while( ($word = $this->getword($this->_delim[$state])) != null )
	{
		if(is_array($word))	//podla typu vystupu getword() vieme, ci bol delimiter.
		{
			$newstate=$this->_trans[$state][$word[0]][XL_DSTATE];	//nacitaj novy stav
			if(DEBUG_STATE) $this->out.="[$newstate]";
			if($newstate == $this->_ret) 
			{
				// rozhodujeme, ci sa slovo spracuje uz teraz, alebo az po navrate z rekurzie
				// =0 - aplikuje sa stary stav na toto slovo,
				// =1 - slovo sa vrati spet do streamu.
				if($this->_trans[$state][$word[0]][XL_DTYPE])
				{
					if(DEBUG_STATE) $this->out.="[retb]";
					$this->textpos-=strlen($word[1]);
				}
				else
				{
					if(DEBUG_STATE) $this->out.="[ret]";
					$this->out.=$this->output->template($word[1],$this->_class[$state]);
				}
				return;
			}
			// rozhodujeme, ci sa zmena stavu aplikuje na toto, alebo az dalsie slovo
			// =0 - aplikuje sa na toto slovo, 
			// =1 - aplikuje sa dalsie slovo.
			if($this->_trans[$state][$word[0]][XL_DTYPE])			//if ...[XL_DTYPE] == 1
				$this->out.=$this->output->template($word[1],$this->_class[$state]);
			else
				$this->out.=$this->output->template($word[1],$this->_class[$newstate]);
			
			if($this->_flags[$newstate] & PF_NEWLANG)
			{
				if($newstate == $this->_quit)
				{
					if(DEBUG_STATE) $this->out.="[quit]";
					return;
				}
				if(DEBUG_STATE) 
				{
					$newlangnam=$this->_data[$newstate];
					$this->out.="[new:$newlangnam]";
				}
				$newparser = new shlParser($this->_data[$newstate], $this->options);
				$this->out.=$newparser->highlight_string($this->output, $this->text, $this->textpos);
				$this->textpos=$newparser->textpos;
				if($this->_trans[$newstate]!=null)		//ak existuju prechody, stav nie je regularny a aplikuje sa zmena stavu
					$state=$newstate;
				continue;
			}
			if($this->_flags[$newstate] & PF_RECURSION)	//ak je flag rekurzia, volaj stav rekurentne
			{
				if($state!=$newstate)		//zabranenie vnoreniu rekurzie do rekurzie
				{
					$this->parse_string($newstate);
					continue;
				}
			}
			$state=$newstate;		//inak len zmen stav
		}
		else
		{
			// Sem sa dostaneme, ak nacitane slovo nebolo delimiter.
			// $word nie je array, tym padom s nim pracujeme ako obycajne.
			if($this->_flags[$state] & PF_KEYWORD)
			{
				if (isset($this->lang->keywords[1][$word]))
					$this->out.=$this->output->template($word,$this->lang->keywords[0].$this->lang->keywords[1][$word]);
				else
					$this->out.=$this->output->template($word,$this->_class[$state]);
			}
			else
			{
				$this->out.=$this->output->template($word,$this->_class[$state]);
			}
			if(DEBUG_STATE) $this->out.=",".$state.",";
		}
	} //END while()
}

// delimiter test
//
function isdelimiter (&$delim)
{
	foreach ($delim as $del)
	{
		//if (in_array($del,&$this->groups))	// faster version (PHP 4.0.6)
		if (in_array($del,$this->groups))
		{
			$ch = $this->text[$this->textpos];
			switch($del)
			{
				// I think strchr is nice cheat at this time, because many
				// people have older PHP's without "ctype_..." functions...
				case "SPACE":
					if(strchr(" \t\n\r",$ch))
						return array($del,$ch);
					break;

				case "!SPACE":
					if(!strchr(" \t\n\r",$ch))
						return array($del,$ch);
					break;

				case "NUMBER":
					if(strchr("0123456789",$ch))
						return array($del,$ch);
					break;

				case "!NUMBER":
					if(!strchr("0123456789",$ch))
						return array($del,$ch);
					break;

				// Next strings are based on character probability in czech language
				// (Hitchhiker's guide to the galaxy - czech translation)
				case "ALPHA":
					if(stristr("eaoinltsrvdukzmcpyhjbfgxwq",$ch))
						return array($del,$ch);
					break;

				case "!ALPHA":
					if(!stristr("eaoinltsrvdukzmcpyhjbfgxwq",$ch))
						return array($del,$ch);
					break;

				case "ALNUM":
					if(stristr("eaoinltsrvdukzmcpyhjbfgxwq0123456789",$ch))
						return array($del,$ch);
					break;

				case "!ALNUM":
					if(!stristr("eaoinltsrvdukzmcpyhjbfgxwq0123456789",$ch))
						return array($del,$ch);
					break;

				case "HEXNUM":
					if(stristr("0123456789abcdef",$ch))
						return array($del,$ch);
					break;

				case "!HEXNUM":
					if(!stristr("0123456789abcdef",$ch))
						return array($del,$ch);
					break;
					
				case "_ALL": return array($del,$ch);
				
				case "_COUNTAB":
					if($ch=="\n" || $ch=="\t")
						return array($del,$ch);
					break;

				// Special group delimiters

				case "PHP_DELIM":
					if(strchr(" \t\n\r;,:(){}[]!=%&|+-*/",$ch))
						return array($del,$ch);
					break;
			}
			continue;
		}
		// delimiter is not group delimiter
		
		//if($del==substr(&$this->text,$this->textpos,strlen($del)))	// faster version (!!this is time critical part!!)
		if($del==substr($this->text,$this->textpos,strlen($del)))
			return array($del,$del);
	}
	return false;
}


// get word from string
//
function getword (&$delim)
{
	$result=null;
	if ($this->textpos < $this->textlen)
	{
		$del=$this->isdelimiter($delim);
		if ($del!=false)
		{
			$this->textpos+=strlen($del[1]);
			return $del;
		}
		else
			while (($this->textpos < $this->textlen) && !$this->isdelimiter($delim))
				$result.=$this->text[$this->textpos++];
	}
	return $result;
}

} // END class Parser
?>