<?php
/*
**	Copyright (C) 1995-2021 by the ITools Authors.
**	This file is part of ITools - the Internet Tools Library
**
**	ITools 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 3 of the License, or
**	(at your option) any later version.
**
**	ITools 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, see <http://www.gnu.org/licenses/>.
*/

class it_syntaxconverter
{
	var $mode;	# Either 'old', 'new', 'test'
	var $changes = 0;

	var $whitespace = array
	(
		T_WHITESPACE => true,
		T_COMMENT => true,
	);

	var $arrayoptional = array
	(
		T_VARIABLE => true,
		T_STRING => true,
	);

#
# Constructor
#
function __construct($string, $mode = "old")
{
	if (defined('T_ML_COMMENT'))
		$this->whitespace[T_ML_COMMENT] = true;		# PHP4

	if (defined('T_DOC_COMMENT'))
		$this->whitespace[T_DOC_COMMENT] = true;	# PHP5

	$this->input = $string;
	$this->mode = $mode;
	$this->tokens = @token_get_all($this->input);
	$this->count = count($this->tokens);
	$this->position = 0;
	$this->old =  $this->parse();
	$this->new =  $this->convert($this->old);
	$this->output = $this->text($this->new);
}

#
# Build parse tree from source string
#
function parse()
{
	$result = array();

	while ($this->position < $this->count)
	{
		$token = $this->tokens[$this->position++];

		if (is_string($token))
		{
			if ($token == '(')
				$result[] = array('LIST', $this->parse_list());
			else
				$result[] = array('TEXT', $token);
		}
		else
			$result[] = $token;
	}

	return $result;
}

function parse_list()
{
	$result = array();

	while ($this->position < $this->count)
	{
		list($token, $element) = $this->parse_element();
		$result[] = $element;

		if ($token == ')')
			break;
	}

	return $result;
}

function parse_element()
{
	$result = array();

	while ($this->position < $this->count)
	{
		$token = $this->tokens[$this->position++];

		if (is_string($token))
		{
			if ($token == '(')
				$result[] = array('LIST', $this->parse_list());
			else if (($token == ',') || ($token == ')'))
				break;
			else
				$result[] = array('TEXT', $token);
		}
		else
			$result[] = $token;
	}

	return array($token, $result);
}


#
# Modify parse tree to old syntax
#
function convert($tokens)
{
	$result = array();
	$op = '';

	foreach ($tokens as $token)
	{
		list($id, $elements) = $token;

		if ($id == 'LIST')
		{
			if (empty($this->arrayoptional[$op]))
			{
				$list = array();

				foreach ($elements as $element)
					$list[] = $this->convert($element);

				$result[] = array($id, $list);
			}
			else
				$result[] = array('LIST', $this->convert_list($elements));
		}
		else
			$result[] = $token;

		if (empty($this->whitespace[$id]))
			$op = $id;
	}

	return $result;
}

function convert_list($elements)
{
	if ($this->mode != 'new')
	{
		$result = $this->convert_list_namedparameters($elements);
		$result = $this->convert_list_trailingcomma($result);
	}
	else
		$result = $this->convert_list_arrayparameters($elements);

	return $result;
}

#
# Remove array() around named parameter lists
#
function convert_list_arrayparameters($elements)
{
	$result = array();
	$locked = false;

	foreach ($elements as $element)
	{
		$element = $this->convert($element);

		if ($locked)
		{
			$result[] = $element;
			$locked = false;
		}
		else
		{
			$elementlist = $this->convert_pure_assoc_array($element);

			foreach ($elementlist as $element)
				$result[] = $element;

			$locked = count($elementlist) != 1;
		}
	}

	return $result;
}

function convert_pure_assoc_array($tokens)
{
	$result = array();
	$element = array();
	$array = false;
	$locked = false;

	foreach ($tokens as $token)
	{
		list($id, $elements) = $token;

		if ($array)
		{
			if (($id == 'LIST') && $this->only_double_arrow($elements))
			{
				foreach ($elements as $listelements)
				{
					$listelements = $this->convert($listelements);

					foreach ($listelements as $listelement)
						$element[] = $listelement;

					$result[] = $element;
					$element = array();
				}

				# Continue with last element
				$element = array_pop($result);
			}
			else
			{
				$element[] = $array;
				$element[] = $token;
			}

			$array = false;
			$locked = true;
		}
		else if (!$locked && ($id == T_ARRAY))
		{
			$array = $token;
		}
		else if (empty($this->whitespace[$id]))
		{
			if ($locked)
			{
				# Encountered something after array, abort
				$result = array();
				$element = $tokens;
				break;
			}
			else
			{
				$element[] = $token;
				$locked = true;
			}
		}
		else
			$element[] = $token;
	}

	if ($element)
		$result[] = $element;

	return $result;
}

function only_double_arrow($elements)
{
	$result = $elements;	# Has to contain elements, empty is false

	foreach ($elements as $element)
	{
		if (!$this->has_double_arrow($element))
		{
			$result = false;
			break;
		}
	}

	return $result;
}

#
# Add array() around named parameter lists
#
function convert_list_namedparameters($elements)
{
	$namedparams = array();

	foreach ($elements as $element)
	{
		$element = $this->convert($element);

		if (!$this->has_double_arrow($element))
		{
			if ($namedparams)
			{
				$result[] = $this->make_array($namedparams);
				$namedparams = array();
			}

			$result[] = $element;
		}
		else
			$namedparams[] = $element;
	}

	if ($namedparams)
		$result[] = $this->make_array($namedparams);

	return $result;
}

#
# Remove trailing comma by adding content to previous element
#
function convert_list_trailingcomma($elements)
{
	$result = $elements;
	$last = count($result) - 1;

	if (($last > 0) && $this->is_empty($result[$last]))
	{
		$this->changes++;
		$element = array_pop($result);
		$last--;
		foreach ($element as $token)
			$result[$last][] = $token;
	}

	return $result;
}

function has_double_arrow($element)
{
	$result = false;

	foreach ($element as $token)
	{
		list($id, $text) = $token;

		if ($id == T_DOUBLE_ARROW)
		{
			$result = true;
			break;
		}
	}

	return $result;
}

function make_array($namedparams)
{
	$result = array();
	$body = array();

	$this->changes++;
	$namedparam = $namedparams[0];

	foreach ($namedparam as $token)
	{
		list($id, $text) = $token;

		if ($body || (($id != 'LIST') && empty($this->whitespace[$id])))
			$body[] = $token;
		else
			$result[] = $token;
	}

	$namedparams[0] = $body;
	$result[] = array(T_ARRAY, "array");
	$result[] = array('LIST', $namedparams);

	return $result;
}

function is_empty($element)
{
	$result = true;

	foreach ($element as $token)
	{
		list($id, $text) = $token;

		if (empty($this->whitespace[$id]))
		{
			$result = false;
			break;
		}
	}

	return $result;
}


#
# Convert parse tree back to string
#
function text($tokens)
{
	$result = "";

	foreach ($tokens as $token)
	{
		list($id, $data) = $token;

		if ($id == 'LIST')
		{
			$list = array();

			foreach ($data as $element)
				$list[] .= $this->text($element);

			$result .= "(" . implode(",", $list) . ")";
		}
		else
			$result .= $data;
	}

	return $result;
}

}