/*

cls_2: Curl Like Syntax
Yauheni A. Akhotnikau (C) 2001-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.

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

*/
#include <assert.h>
#include <string.h>
#include <ctype.h>

#include <cls_2/h/cls.hpp>
#include <cls_2/h/lex.hpp>
#include <cls_2/h/_check_digits.hpp>

namespace cls_2 {

static bool
is_space( char ch ) {
	return ( ' ' == ch || '\t' == ch || '\r' == ch ||
		'\n' == ch );
}

const char	c_eol_char = '\n';

//
// lex_t
//

lex_t::lex_t(
	input_stream_t & stream ) :
	m_stream( stream ) {
}

lex_t::~lex_t() {
}

int
lex_t::get_tok(
	parser_context_t & context,
	int & tok,
	char & ch ) {
	int ret_code = m_stream.get( ch );
	if( input_stream_t::e_eof == ret_code ) {
		tok = c_tok_eof;
		return c_ok;
	}
	if( input_stream_t::e_ok == ret_code ) {
		if( is_space( ch ) )
			tok = c_tok_space;
		else if( '\"' == ch )
			tok = c_tok_dquote;
		else if( '{' == ch )
			tok = c_tok_open_block;
		else if( '}' == ch )
			tok = c_tok_close_block;
		else if( '\\' == ch ) {
			return parse_back_slash_seq( context, tok, ch );
		}
		else if( '|' == ch ) {
			return parse_vert_bar( context, tok, ch );
		}
		else
			tok = c_tok_nonspace;

		return c_ok;
	}

	return c_io_error;
}

int
lex_t::parse_back_slash_seq(
	parser_context_t & context,
	int & tok,
	char & ch ) {
	char next_ch;
	int ret_code = m_stream.get( next_ch );
	if( input_stream_t::e_eof == ret_code )
	{
		context.show_error( context.find_msg(
			c_msg_unfinished_back_slash_seq ) );
		return c_lex_error;
	}
	if( input_stream_t::e_ok == ret_code )
	{
		if( '{' == next_ch || '}' == next_ch ||
			'\\' == next_ch || '|' == next_ch ||
			'\"' == next_ch )
		{
			tok = c_tok_nonspace;
			ch = next_ch;
		}
		else if( 'n' == next_ch )
		{
			tok = c_tok_space;
			ch = '\n';
		}
		else if( 't' == next_ch )
		{
			tok = c_tok_space;
			ch = '\t';
		}
		else if( 'r' == next_ch )
		{
			tok = c_tok_space;
			ch = '\r';
		}
		else if( 'b' == next_ch || 'B' == next_ch )
			return parse_spec_char_representation(
				context, tok, ch, next_ch, 8 );
		else if( 'o' == next_ch || 'O' == next_ch )
			return parse_spec_char_representation(
				context, tok, ch, next_ch, 3 );
		else if( 'x' == next_ch || 'X' == next_ch )
			return parse_spec_char_representation(
				context, tok, ch, next_ch, 2 );
		else {
			// Leave back slash sequence unparsed
			m_stream.back();
		}

		return c_ok;
	}
	else
		return c_io_error;
}

int
lex_t::parse_vert_bar(
	parser_context_t & context,
	int & tok,
	char & ch ) {
	char next_ch;
	int ret_code = m_stream.get( next_ch );
	if( input_stream_t::e_ok == ret_code ) {
		if( '|' == next_ch ) {
			// Single line comment
			tok = c_tok_space;
			ch = c_eol_char;

			return skip_single_line_comment( context );
		}
		else if( '#' == next_ch ) {
			tok = c_tok_space;
			ch = ' ';


			return skip_start_stop_comment( context );
		}

		// Leave vertical bar sequence unparsed
		m_stream.back();
		return c_ok;
	}
	else if( input_stream_t::e_eof == ret_code ) {
		tok = c_tok_nonspace;
		return c_ok;
	}
	else
		return c_io_error;
}

int
lex_t::skip_single_line_comment(
	parser_context_t & context ) {
	char ch;
	int ret_code;
	while( input_stream_t::e_ok ==
		( ret_code = m_stream.get( ch ) ) )
		if( c_eol_char == ch )
			return c_ok;

	return ( input_stream_t::e_eof == ret_code ?
		c_ok : c_io_error );
}

int
lex_t::skip_start_stop_comment(
	parser_context_t & context ) {
	char ch;
	int ret_code;

	while( input_stream_t::e_ok ==
		( ret_code = m_stream.get( ch ) ) ) {
		if( '#' == ch ) {
			if( input_stream_t::e_ok == (
				ret_code = m_stream.get( ch ) ) ) {
				if( '|' == ch ) {
					return c_ok;
				}
				else
					m_stream.back();
			}
			else
				break;
		}
	}

	context.show_error( context.find_msg(
		c_msg_unclosed_comment ) );
	return c_lex_error;
}

int
lex_t::parse_spec_char_representation(
	parser_context_t & context,
	int & tok,
	char & ch,
	char base_mark,
	unsigned int char_count )
{
	// Количество символов не может быть больше 8 -- для
	// двоичной системы счисления.
	assert( 8 >= char_count );

	// Здесь будет формироваться значение для разбора.
	char repres[ 12 ];
	size_t pos = 0;
	repres[ pos++ ] = '\\';
	repres[ pos++ ] = base_mark;

	// Считываем оставшиеся символы и формируем представление
	// для разбора.
	for( size_t i = 0; i != char_count; ++i )
	{
		char next;
		int ret_code = m_stream.get( next );
		if( input_stream_t::e_ok == ret_code )
		{
			repres[ pos++ ] = next;
		}
		else
		{
			// Конец входного потока достигнут слишком рано.
			context.show_error( context.find_msg(
				c_msg_unfinished_back_slash_seq ) );
			return c_lex_error;
		}
	}
	repres[ pos ] = 0;

	// Пытаемся разобрать значение.
	unsigned long long_value;
	if( parse_digits( repres, long_value, '\\' ) &&
		long_value <= 0xff )
	{
		// Это корректное значение символа.
		ch = (char) long_value;
		if( is_space( ch ) )
			tok = c_tok_space;
		else
			tok = c_tok_nonspace;

		return c_ok;
	}

	// Последовательность была некорректной.
	context.show_error( repres );
	context.show_error( ": " );
	context.show_error( context.find_msg(
		c_msg_invalid_back_slash_seq ) );
	return c_lex_error;
}

} /* namespace cls_2 */
