/*

args_4: Command line arguments parsing library
Yauheni A. Akhotnikau (C) 2002-2003
eao197@yahoo.com
-------------------------------------------------

Permission is granted to anyone to use this software for any purpose on any
computer system, and to redistribute it freely, subject to the following
restrictions:

1. This software 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.

2. The origin of this software must not be misrepresented, either by
   explicit claim or by omission.

3. Altered versions must be plainly marked as such, and must not be
   misrepresented as being the original software.

-------------------------------------------------

*/
/*!
	Args-4
*/

/*!
	\file
	\brief	Основные классы проекта args-4.
*/

#ifndef _ARGS_4__ARGS_HPP_
#define _ARGS_4__ARGS_HPP_

#include <cstdio>
#include <cstddef>


// В Visual C++ 6.0 заголовочные файлы cstdio и cstddef не
// вводят пространство имен std. А компилятор Borland C++
// вводит, и для доступа даже к size_t нужно использовать
// пространство имен std.
namespace std {}
using namespace std;

namespace args_4
{

/*
** Коды ошибок разбора аргументов.
*/

//! Нет ошибок.
const int c_ok = 0;

//! Достигнут конец входного потока.
const int c_eof = 501;

//! Обнаружен неизвестный аргумент.
const int c_unknown_cmd = 502;

//! Не найден обязательный аргумент.
const int c_mandatory_cmd_missed = 503;

//! Обнаружен перевод каретки в незакрытой строке.
/*!
	Комбинация:
\verbatim
"some string starts...
end finished here"
\endverbatim
	является недопустимой. Правильный вариант:
\verbatim
"some string starts...\
end finished here"
\endverbatim
*/
const int c_new_line_in_string = 504;

//! Пустая сторока.
const int c_empty_string = 505;

//! Найдена открывающая кавычка в слове.
/*!
	Комбинация <tt>wor"d"</tt> является недопустимой.
	Нужно <tt>wor "d"</tt>.
*/
const int c_quote_in_word = 506;

//! Ошибочная комбинация после обратного слеша.
const int c_invalid_back_slash_seq = 507;

//! Аргумент уже определен.
const int c_cmd_already_defined = 508;

//! Отсутвует значение для аргумента.
const int c_value_missed = 509;

//! Недопустимое значение.
const int c_invalid_value = 510;

//! Нет аргумента.
const int c_no_cmd = 511;

//! Ошибка ввода-вывода.
const int c_io_error = 512;

/*
** error_stream_t
*/

//! Базовый класс для потока ошибок.
/*!
	Библиотека args-4 спроектирована так, чтобы
	можно было использовать различные потоки
	вывода ошибок (stdsn, ostream и т.д.). Для того,
	чтобы можно было заставить средства args-4
	отображать ошибки в какой-либо специфический
	поток (например, в буфер памяти, или в визуальный
	орган управления типа multiline-edit) все
	потоки вывода ошибок должны быть производными
	от класса error_stream_t.

	Данный класс может использоваться в качестве null-потока,
	т.е. потока, который не осуществляет какого-либо
	вывода.
*/
class	error_stream_t {
	public :
		error_stream_t();
		virtual ~error_stream_t();

		//! Осуществить вывод сообщения об ошибке.
		/*!
			В данном классе ничего не делает.
		*/
		virtual error_stream_t &
		out( const char * str );

	private :
		error_stream_t(
			const error_stream_t & );
		error_stream_t &
		operator=( const error_stream_t & );
};

/*
** word_container_t
*/

/*!
	\brief Базовый класс парсера входного потока.

	Средства библиотки args-4 рассматривают входной
	поток как последовательность слов (последовательности
	непробельных символов).

	В качестве входного потока можно использовать
	различные средства (stdin, istream, FILE *, pipe,
	строки, буфера в памяти и т.д.). Для этого необходимо
	чтобы для каждого типа потока был реализован класс,
	производный от word_container_t.

	В дествительности, класс word_container_t уже
	содержит всю функциональность по составлению
	слов из символов и определению ошибок. Производным
	классам необходимо только преопределить метод
	word_container_t::getch().
*/
class	word_container_t {
	public :
		word_container_t();
		virtual	~word_container_t( void );

		//! Выделить следующие слово из потока.
		/*!
			\return указатель на C-строку
			со значением извлеченного слова,
			если все прошло успешно. Возвращенный
			указатель действителен до следующего
			обращения к next(). В случае ошибки
			возвращается 0.
		*/
		virtual	const char *
		next(
			//! Приемник кода ошибки, если метод
			//! завершился неудачно.
			int & error_code,
			//! Поток для вывода сообщений о возникающих
			//! ошибках. Может быть равным 0 -- в этом
			//! случае печать сообщений об ошибках не
			//! выполняется.
			error_stream_t * error_stream );

		//! Возвратить последнее извлеченное слово
		//! обратно в поток.
		/*!
			Последующий вызов next() возвратит это
			слово.

			Предназначен для реализации т.н. lock-ahead
			техники разбора.
		*/
		void
		back();

	protected :
		//! Буфер со значением последнего извлеченного слова.
		char *	m_word;
		//! Емкость буфера %m_word.
		size_t	m_word_capacity;
		//! Текущая длина строки в %m_word.
		size_t	m_word_len;

		//! Текущий поток ошибок.
		/*! Определен только в момент работы метода next(). */
		error_stream_t *	m_error_stream;

		//! Признак того, что последнее извлеченное
		//! методом %next() слово возвращено методом %back().
		bool	m_prev_word_returned;

		/*! Если необходимо, расширяет m_word и добавляет
			ch к m_word. */
		void
		expand( char ch );

		//! Извлечь следующий символ.
		/*! Если нет больше доступных символов,
			то должно быть возвращено c_eof.

			\attention На унаследовавших MS-DOS
			платформах символ '\r' не должен
			возвращаться.
		*/
		virtual int
		getch(
			//! Приемник значения очередного извлеченного
			//! символа.
			char & ch );

		/*! \return Текст сообщения об ошибке с кодом
			c_new_line_in_string. */
		const char *
		err_new_line_in_string(
			int & error_flag );

		/*! \return Текст сообщения об ошибке с кодом
			c_empty_string. */
		const char *
		err_empty_string(
			int & error_flag );

		/*! \return Текст сообщения об ошибке с кодом
			c_quote_in_word. */
		const char *
		err_quote_in_word(
			int & error_flag );

		/*! \return Текст сообщения об ошибке с кодом
			c_invalid_back_slash_seq. */
		const char *
		err_invalid_back_slash_seq(
			int & error_flag );

	private :
		word_container_t(
			const word_container_t & );
		word_container_t &
		operator=(
			const word_container_t & );
};

/*
** words_from_str_t
*/
/*!
	\brief Парсер C-строки.

	Реализация word_container_t для случая C-строки.
*/
class	words_from_str_t :
	public word_container_t {
	public :
		//! Инициализирующий конструктор.
		words_from_str_t(
			//! Строка для разбора.
			const char * str );
		virtual ~words_from_str_t( void );

	protected :
		//! Указатель на текущую позицию в
		//! разбираемой строке.
		const char * m_cur;

		/*! Если *m_cur != 0, то возвращает *m_cur и
			передвигает m_cur. В противном случае
			считается, что входной поток исчерпан. */
		virtual int
		getch( char & ch );
};

/*
** words_from_file_t
*/
/*!
	\brief Парсер файла.

	Реализация word_container_t для случая текстового
	файла. Работа с файлом осуществляется с использованием
	средств stdio.
*/
class	words_from_file_t :
	public word_container_t {
	public :
		//! Инициализирующий конструктор.
		/*! Осуществляет попытку открытия файла в
			режиме read-only. */
		words_from_file_t(
			//! Имя разбираемого файла.
			const char * file_name );
		virtual ~words_from_file_t( void );

		//! Определить результат последней операции
		//! ввода вывода.
		/*! Возвращается значение errno. Может использоваться
			для проверки того, был ли открыт файл в
			конструкторе. */
		int
		query_last_io_result() const;

		//! Определить имя разбираемого файла.
		const char *
		query_file_name() const;

		//! Определить номер текущей строки в файле.
		unsigned int
		query_line() const;

	protected :
		//! Буфер с именем файла.
		/*! Создается динамически и получает значение
			параметра file_name из конструкторе. */
		char *	m_file_name;
		//! Файл.
		FILE *	m_file;
		//! Буфер ввода.
		char *	m_buf;
		//! Количество байт, реально загруженных в %m_buf.
		size_t	m_size;
		//! Текущая позиция в %m_buf.
		size_t	m_pos;
		//! Текущая строка в файле.
		unsigned int	m_line;

		//! Результат последней операции ввода-вывода.
		int	m_errno;

		//! Извлечение очередного символа из файла.
		virtual int
		getch( char & ch );

		//! Низкоуровневый метод извлечения символа из файла.
		int
		lo_get_ch( char & ch );

		//! Низкоуровневый метод возвращения последнего
		//! прочитанного символа обратно в буфер ввода.
		/*! Используется для реализации lock-ahead
			разбора. */
		void
		lo_back_ch();
};

/*
** words_from_file_with_simple_comment_t
*/
/*!
	\brief Класс разбора файла с учетом простых комментариев.

	Класс words_from_file_t не предоставляет возможности
	использования комментариев в разбираемом файле, т.к.
	предназначен для разбора т.н. response-файлов.
	Тем не менее, средства args-4 можно использовать для
	рабора простых конфигурационных файлов. В этом случае
	желательно предоставить возможность использовать в
	файлах комментарии.

	Реализует эту возможность класс
	%words_from_file_with_simple_comment_t. Разбираемый
	файл может содержать однострочные комментарии,
	начинающиеся одним символом, например:
\verbatim
#
# Some server socket config
#
ip-addr www.some-site.com
port 5000

#
# Client authentification config
#
client-auth-method	password
\endverbatim

	\attention Воспринимаются только комментарии, перед
	символом открытия
	которых нет никаких непробельных символов. Следующее
	использование символа '#' не открывает комментария,
	т.е. символ '#' может являться частью слова:
\verbatim
mxx-cmd-line	mxxc -f somefile.#not-comment
\endverbatim
*/
class	words_from_file_with_simple_comment_t:
	public words_from_file_t
{
	public :
		//! Инициализирующий конструктор.
		/*! Осуществляет попытку открытия файла в
			режиме read-only. */
		words_from_file_with_simple_comment_t(
			//! Имя файла для разбора.
			const char * file_name,
			//! Единственный символ, который
			//! открывает комментарий.
			char comment_char );
		//! Инициализирующий конструктор.
		/*! Осуществляет попытку открытия файла в
			режиме read-only. */
		words_from_file_with_simple_comment_t(
			//! Имя файла для разбора.
			const char * file_name,
			//! Множество символов, каждый из которых
			//! может открывать комментарий.
			const char * comment_char_set );
		virtual ~words_from_file_with_simple_comment_t();

	protected :
		//! Состояние разбора.
		enum parse_state_t {
			//! Обнаружена новая строка.
			e_new_line,
			//! Разбирается комментарий.
			e_comment,
			//! Нормальный разбор.
			e_normal
		};

		//! Текущее состояние разбора.
		parse_state_t	m_state;
		//! Буфер со множеством символов, которые
		//! могут открывать комментарий.
		char *	m_comment_char_set;

		//! Извлечение очередного символа.
		/*! Комментарии изымаются */
		virtual int
		getch( char & ch );
};

/*
** words_from_argv_t
*/

/*!
	\brief Разбор значений argv.

	Реализация word_container_t для аргументов
	командной строки, доступных через argc, argv.

	\attention Самый первый элемент в argv игнорируется.
*/
class	words_from_argv_t :
	public word_container_t {
	public :
		//! Инициализирующий конструктор.
		words_from_argv_t(
			//! Количество значений в argv.
			int argc,
			//! Значения аргументов.
			char ** argv );
		virtual ~words_from_argv_t();

		//! Возвращает очередной элемент из argv.
		virtual
		const char *	next(
			int & error_code,
			error_stream_t * error_stream );

		//! Возвращает значение argc.
		int
		query_argc() const;

	protected :
		//! Значение argc.
		int	m_argc;
		//! Значение argv.
		char **	m_argv;
		//! Номер текущего элемента в argv.
		int	m_cur;
};

/*
** cmd_t
*/

/*!
	\brief Базовый класс, реализующий понятие 'аргумент'.

	Каждый аргумент имеет имя и, может различное количество
	обязательных или необязательных параметров (значений).
*/
class	cmd_t {
	public :
		cmd_t();
		//! Инициализирующий конструктор.
		cmd_t(
			//! Имя аргумента. Копия этого имени сохраняется
			//! в объекте %cmd_t.
			const char * name );
		virtual ~cmd_t();

		//! Сравнить указаное имя с именем аргумента.
		/*! Используется для определения того, что очередное
			извлеченное из входного потока имя является
			именем аргумента.

			Эта реализация просто сравнивает m_name и
			значение \c name.

			\return true, если \c name является именем
			аргумента.
		*/
		virtual	bool
		compare_name(
			//! Имя для сравнения.
			const char * name ) const;

		//! Обработать параметры аргумента.
		/*! После того как будет определен объект %cmd_t,
			соответствующий извлеченному из входного
			потока имени, этому объекту дается возможность
			извлечь из входного потока все свои
			параметры. Для этого у объекта %cmd_t
			вызывается данный метод.

			Объект %cmd_t должен считывать из входного
			потока свои параметры до тех пор пока:
			- не произойдет ошибка разбора, или
			- не закончится входной поток, или
			- аргумент не выберет все свои аргументы.

			Может произойти ситуация, когда аргумент
			извлечет из входного потока все свои
			обязательные параметры (если таковые имеются),
			а вместо необязательных параметров обнаруживает
			неизвестное для себя слово. Это может говорить
			о том, что необязательные параметры не заданы,
			а во входном потоке начинается новый аргумент.
			В этом случае во входной поток необходимо
			возвратить прочитанное слово и прекратить разбор
			параметров.

			Если же вместо необязательного параметра будет
			обнаружен конец входного потока, то аргумент
			должен возвратить c_ok.

			Данный метод кроме извлечения параметров должен
			заниматься из проверкой.

			\return c_ok, если разбор параметров прошел
			успешно. Остальные коды при возникновении ошибок.
		*/
		virtual int
		process_params(
			//! Входной поток.
			word_container_t & words,
			//! Необязательный поток ошибок.
			//! Может иметь значение 0.
			error_stream_t * error_stream );

		//! Определить имя аргумента.
		/*! \return Значение m_name. */
		const char *
		query_name() const;

		//! Обязательный ли аргумент?
		/*! Вызывается после окончания разбора
			входного потока для того, чтобы определить,
			является ли аргумент обязательным. Если
			аргумент обязателен, но не определен, то
			разбор считается не выполненым.

			По умолчанию, аргумент не обязательный. */
		virtual bool
		is_mandatory() const;

		//! Определен ли аргумент.
		/*! Если аргумент был извлечен из входного
			потока с корректными параметрами, то он
			должен считаться определенным.
		*/
		virtual bool
		is_defined() const;

	protected :
		//! Буфер, в который копируется имя аргумента.
		char *	m_name;

		//! Извлечения следующего обязательного слова
		//! из входного потока.
		/*! Предназначен для упрощения извлечения из
			входного потока обязательных параметров
			аргумента в методе process_params().

			\return Указатель на значение следующего
			обязательного слова. Указатель дествителен
			до следующего обращения к методам
			word_container_t::next() или
			cmd_t::next_mandatory_word().
			Возвращается 0, если следующее обязательное
			слово извлечь не удалось.
		*/
		const char *
		next_mandatory_word(
			//! Входной поток.
			word_container_t & words,
			//! Необязательный поток ошибок.
			//! Может иметь значение 0.
			error_stream_t * error_stream,
			//! Приемник кода ошибки, если
			//! извлечь следующее обязательное слово
			//! не удалось.
			int & error_code );
};

/*
** cmd_parser_t
*/

/*!
	\brief Разборщик входного потока на аргументы.

	Берет на себя функции просмотра входного потока,
	последовательного извлечения из него слов, поиска
	по слову соответствующего аргумента, обработки
	параметров аргумента. После завершения разбора
	проверяет определенность всех обязательных
	аргументов.
*/
class	cmd_parser_t {
	public :
		//! Инициализирующий конструктор.
		cmd_parser_t(
			//! Разрешен ли возврат неопознаных
			//! слов обратно во входной поток.
			bool is_word_backing_enabled = false );
		~cmd_parser_t();

		//! Добавить очередной аргумент к списку
		//! разбираемых аргументов.
		void
		add_cmd( cmd_t * pcmd );

		//! Добавить очередные аргументы к списку
		//! разбираемых аргументов.
		/*! Последний аргумент должен быть нулевым
			указателем. */
		void
		add_cmds( cmd_t * p1, ... );

		//! Осуществить разбор.
		int
		parse(
			//! Входной поток.
			word_container_t & words,
			//! Необязательный поток ошибок.
			//! Может иметь значение 0.
			error_stream_t * error_stream );

	protected :
		//! Список разбираемых аргументов.
		cmd_t **	m_list_cmd;
		//! Количество определенных элементов
		//! в %m_list_cmd.
		size_t	m_list_len;
		//! Размерность %m_list_cmd.
		size_t	m_list_capacity;
		//! Разрешен ли возврат нераспознанных
		//! слов обратно во входной поток.
		bool	m_is_word_backing_enabled;

		//! Поток ошибок.
		/*! Определен только во время работы метода parse.

			Может иметь значение 0. */
		error_stream_t *	m_error_stream;

		//! Поиск обработчика аргумента по имени.
		cmd_t *
		find_cmd_by_name(
			//! Имя искомого аргумента.
			const char * name );

		//! Попытка найти и обработать аргумент
		//! с указанным именем.
		int
		process_cmd(
			//! Имя искомого аргумента.
			const char * name,
			//! Входной поток.
			word_container_t & words );

		//! Проверка того, что все обязательные
		//! аргументы определены.
		int
		check_mandatory_args();
};

/*
** cmd_str_value_t
*/
/*!
	\brief Аргумент с единственным обязательным
	параметром - словом.

	Значение параметра сохраняется как C-строка.
*/
class	cmd_str_value_t :
	public cmd_t
{
	public :
		//! Инициализирующий конструктор.
		cmd_str_value_t(
			//! Имя аргумента.
			const char * name,
			//! true, если аргумент обязательный.
			bool mandatory );
		virtual ~cmd_str_value_t();

		//! Извлечение из входного потока единственного
		//! обязательного параметра.
		/*! Если параметр успешно извлечен, то
			аргумент считается успешно определенным.

			Повторное извлечение из входного потока
			уже определенного аргумента воспринимается
			как ошибка.
		*/
		virtual int
		process_params(
			word_container_t & words,
			error_stream_t * error_stream );

		/*! Возвращается значение m_is_mandatory. */
		virtual bool
		is_mandatory() const;

		/*! Возвращает значение m_is_defined. */
		virtual bool
		is_defined() const;

		/*! Возвращает параметр аргумента. */
		const char *
		query_value() const;

	protected :
		/*! Значение параметра аргумента. */
		char *	m_value;
		/*! true, если аргумент обязательный. */
		bool	m_is_mandatory;
		/*! true, если аргумент определен. */
		bool	m_is_defined;
};

/*
** cmd_uint_value_t
*/
/*!
	\brief Аргумент с единственным обязательным
	параметром - беззнаковым целым числом.

	Значение параметра может задаваться в
	соответствии с C-форматами целого числа
	(в десятичном, восьмиричном или шестнадцатиричном
	виде).
*/
class	cmd_uint_value_t :
	public cmd_t
{
	public :
		//! Инициализирующий конструктор.
		cmd_uint_value_t(
			//! Название аргумента.
			const char * name,
			//! true, если аргумент обязательный.
			bool mandatory );
		virtual ~cmd_uint_value_t();

		//! Извлечение из входного потока единственного
		//! обязательного параметра.
		/*! Если параметр успешно извлечен, то
			аргумент считается успешно определенным.

			Повторное извлечение из входного потока
			уже определенного аргумента воспринимается
			как ошибка.
		*/
		virtual int
		process_params(
			word_container_t & words,
			error_stream_t * error_stream );

		/*! Возвращается значение m_is_mandatory. */
		virtual bool
		is_mandatory() const;

		/*! Возвращает значение m_is_defined. */
		virtual bool
		is_defined() const;

		/*! Возвращает параметр аргумента. */
		unsigned int
		query_value() const;

	protected :
		/*! Значение параметра аргумента. */
		unsigned int	m_value;
		/*! true, если аргумент обязательный. */
		bool	m_is_mandatory;
		/*! true, если аргумент определен. */
		bool	m_is_defined;
};

/*
** cmd_on_off_switch_t
*/
/*!
	\brief Аргумент без параметров - переключатель.
*/
class	cmd_on_off_switch_t :
	public cmd_t
{
	public :
		//! Инициализирующий конструктор.
		cmd_on_off_switch_t(
			//! Название аргумента.
			const char * name,
			//! true, если аргумент обязательный.
			bool mandatory );
		virtual ~cmd_on_off_switch_t( void );

		//! Сразу устанавливает признак того,
		//! что аргумент определен.
		/*!
			Повторное извлечение из входного потока
			уже определенного аргумента воспринимается
			как ошибка.
		*/
		virtual int
		process_params(
			word_container_t & words,
			error_stream_t * error_stream );

		/*! Возвращается значение m_is_mandatory. */
		virtual bool
		is_mandatory() const;

		/*! Возвращает значение m_is_defined. */
		virtual bool
		is_defined() const;

	protected :
		/*! true, если аргумент обязательный. */
		bool	m_is_mandatory;
		/*! true, если аргумент определен. */
		bool	m_is_defined;
};

/*
** cmd_strvector_value_t
*/
/*!
	\brief Аргумент с одиним обязательным параметром - словом,
	который может быть указан несколько раз.

	Предназначен, например, для случаев:
	<tt>-file p1 -file p2 ... -file p3</tt>. Все параметры
	аргумента объединяеются в один вектор. Для доступа к
	значениям аргумента предназначены методы
	query_value_count(), query_value().
*/
class	cmd_strvector_value_t :
	public cmd_t
{
	public :
		//! Инициализирующий конструктор.
		cmd_strvector_value_t(
			//! Название аргумента.
			const char * name,
			//! true, если аргумент обязательный.
			bool mandatory );
		virtual ~cmd_strvector_value_t();

		//! Извлечение из входного потока единственного
		//! обязательного параметра.
		/*!
			Извлеченный параметр добавляется в конец
			вектора уже извлеченных значений.

			Если параметр успешно извлечен, то
			аргумент считается успешно определенным.
		*/
		virtual int
		process_params(
			word_container_t & words,
			error_stream_t * error_stream );

		/*! Возвращается значение m_is_mandatory. */
		virtual bool
		is_mandatory() const;

		/*! \return true, если аргумент был указан
			хотя бы один раз. */
		virtual bool
		is_defined() const;

		//! Возвращает количество параметров.
		size_t
		query_value_count() const;

		//! Возвращается значение параметра по индексу.
		/*!
			Параметры нумеруются с 0.
		*/
		const char *	query_value(
			size_t index ) const;

	protected :
		/*! Вектор извлеченных значений. */
		char **	m_values;
		/*! Количество занятых элементов в m_values. */
		size_t	m_vector_len;
		/*! Размерность m_values. */
		size_t	m_vector_capacity;
		/*! true, если аргумент обязательный. */
		bool	m_is_mandatory;
};

/*
** cmd_response_file_t
*/
/*!
	\brief Аргумент, который является именем (набором
	имен) response-файлов.

	В реализации этого класса использовано предположение,
	что имена response-файлов задается в виде:
	<tt><символ><имя-файла></tt>, Например,
	<tt>@filelist.txt</tt>.
*/
class	cmd_response_file_t :
	public cmd_strvector_value_t
{
	public :
		//! Инициализирующий конструктор.
		cmd_response_file_t(
			//! Символ, который указывает, что
			//! значение является именем response-файла.
			char first_char = '@' );
		~cmd_response_file_t();

		/*! \return true, если первый символ \c name
			совпадает с m_first_char. */
		virtual	bool
		compare_name( const char * name ) const;

		/*! Заносит имя response-файла (без первого
			управляющего символа) в вектор значений
			параметров аргумента.
			
			Для извлечения имени используется тот факт,
			что если выполнить words.back(), а затем
			words.next(), то будет возвращено имя
			аргумента. */
		virtual int
		process_params(
			word_container_t & words,
			error_stream_t * error_stream );

	protected :
		/*! Символ, который определяет, что значение
			является именем response-файла. */
		char	m_first_char;
};

} /* namespace args_4 */

#endif
