/*

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 <cls_2/h/_parser.hpp>
#include <cls_2/h/lex.hpp>

namespace cls_2 {

//
// syn_state_trans_t
//

/*
	Transition from one syntax state to another
*/
struct	syn_state_trans_t {
	// Next state
	syn_state_t *	m_state;
	// Token Id
	int	m_tok;
	// Token procedure
	pfn_token_proc_t	m_on_token;
	// Is next token must be extracted?
	bool	m_is_get_next_token;
};

//
// syn_state_t
//

/*
	Syntax state
*/
struct	syn_state_t {
	pfn_state_proc_t	m_on_enter;
	pfn_state_proc_t	m_on_exit;
	// Cannot be NULL
	const syn_state_trans_t *	m_trans;
	// In case if there is no acceptable transition
	int	m_error_msg_id;
	// State name (for debug purposes)
	const char *	m_state_name;
};

//
// file_scope_tag_t
//

file_scope_tag_t::file_scope_tag_t() :
	tag_t( "file-scope_tag" ) {
}

file_scope_tag_t::~file_scope_tag_t() {
}

int
file_scope_tag_t::on_tok_nonspace(
	parser_context_t & context,
	const char * tok_value ) {
	context.show_error( context.find_msg(
		c_msg_tag_expected ) );

	return c_syntax_error;
}

int
file_scope_tag_t::on_tok_string(
	parser_context_t & context,
	const char * tok_value ) {
	context.show_error( context.find_msg(
		c_msg_tag_expected ) );

	return c_syntax_error;
}

//
// _parser_t
//
const size_t	c_tag_stack_capacity = 64;
const size_t	c_cnt_capacity = 256;

//
// Syntax states
//
extern syn_state_t	g_state_start;
	extern syn_state_trans_t	g_state_start_trans[];

extern syn_state_t	g_state_finish;
	extern syn_state_trans_t	g_state_finish_trans[];

extern syn_state_t	g_state_tag_content;
	extern syn_state_trans_t	g_state_tag_content_trans[];

extern syn_state_t	g_state_wait_tag_name;
	extern syn_state_trans_t	g_state_wait_tag_name_trans[];

extern syn_state_t	g_state_fill_tag_name;
	extern syn_state_trans_t	g_state_fill_tag_name_trans[];

extern syn_state_t	g_state_tag_complete;
	extern syn_state_trans_t	g_state_tag_complete_trans[];

extern syn_state_t	g_state_tag_cnt_space;
	extern syn_state_trans_t	g_state_tag_cnt_space_trans[];

extern syn_state_t	g_state_tag_cnt_nonspace;
	extern syn_state_trans_t	g_state_tag_cnt_nonspace_trans[];

extern syn_state_t	g_state_tag_cnt_string;
	extern syn_state_trans_t	g_state_tag_cnt_string_trans[];

//
// Implementation
//
syn_state_t	g_state_start = {
	0,
	0,
	g_state_start_trans,
	c_msg_tag_expected,
	"state_start"
};
	syn_state_trans_t	g_state_start_trans[] = {
		{	&g_state_start, c_tok_space, 0, true }
		,{	&g_state_finish, c_tok_eof, 0, false }
		,{	&g_state_tag_content, c_tok_open_block, 0, false }
		,{ 0, 0, 0, false }
	};

syn_state_t	g_state_finish = {
	&_parser_t::on_enter_finish,
	0,
	g_state_finish_trans,
	0,
	"state_finish"
};
	syn_state_trans_t	g_state_finish_trans[] = {
		{ 0, 0, 0, false }
	};

syn_state_t	g_state_tag_content = {
	0,
	0,
	g_state_tag_content_trans,
	c_msg_syntax_error,
	"state_tag_content"
};
	syn_state_trans_t	g_state_tag_content_trans[] = {
		{	&g_state_wait_tag_name, c_tok_open_block, 0, true }
		,{	&g_state_tag_complete, c_tok_close_block, 0, false }
		,{	&g_state_tag_cnt_space, c_tok_space, 0, false }
		,{	&g_state_tag_cnt_nonspace, c_tok_nonspace, 0, false }
		,{	&g_state_tag_cnt_string, c_tok_dquote, 0, true }
		,{	&g_state_finish, c_tok_eof, 0, false }
		,{ 0, 0, 0, false }
	};

syn_state_t	g_state_wait_tag_name = {
	&_parser_t::on_enter_wait_tag_name,
	0,
	g_state_wait_tag_name_trans,
	c_msg_tag_name_expected,
	"state_wait_tag_name"
};
	syn_state_trans_t	g_state_wait_tag_name_trans[] = {
		{	&g_state_fill_tag_name, c_tok_nonspace,
			&_parser_t::on_token_default, true }
		,{ 0, 0, 0, false }
	};

syn_state_t	g_state_fill_tag_name = {
	0,
	&_parser_t::on_exit_fill_tag_name,
	g_state_fill_tag_name_trans,
	c_msg_fill_tag_name_error,
	"state_fill_tag_name"
};
	syn_state_trans_t	g_state_fill_tag_name_trans[] = {
		{	&g_state_fill_tag_name, c_tok_nonspace,
			&_parser_t::on_token_default, true }
		,{	&g_state_tag_content, c_tok_space, 0, true }
		,{	&g_state_tag_complete, c_tok_close_block, 0, false }
		,{ 0, 0, 0, false }
	};

syn_state_t	g_state_tag_complete = {
	&_parser_t::on_enter_tag_complete,
	&_parser_t::on_exit_tag_complete,
	g_state_tag_complete_trans,
	c_msg_syntax_error,
	"state_tag_complete"
};
	syn_state_trans_t	g_state_tag_complete_trans[] = {
		{	&g_state_tag_content, c_tok_close_block, 0, true }
		,{ 0, 0, 0, false }
	};

syn_state_t	g_state_tag_cnt_space = {
	&_parser_t::on_enter_tag_cnt_space,
	&_parser_t::on_exit_tag_cnt_space,
	g_state_tag_cnt_space_trans,
	c_msg_syntax_error,
	"state_tag_cnt_space"
};
	syn_state_trans_t	g_state_tag_cnt_space_trans[] = {
		{	&g_state_tag_cnt_space, c_tok_space,
			&_parser_t::on_token_default, true }
		,{	&g_state_tag_content, c_tok_any, 0, false }
		,{ 0, 0, 0, false }
	};

syn_state_t	g_state_tag_cnt_nonspace = {
	&_parser_t::on_enter_tag_cnt_nonspace,
	&_parser_t::on_exit_tag_cnt_nonspace,
	g_state_tag_cnt_nonspace_trans,
	c_msg_syntax_error,
	"state_tag_cnt_nonspace"
};
	syn_state_trans_t	g_state_tag_cnt_nonspace_trans[] = {
		{	&g_state_tag_cnt_nonspace, c_tok_nonspace,
			&_parser_t::on_token_default, true }
		,{	&g_state_tag_content, c_tok_any, 0, false }
		,{ 0, 0, 0, false }
	};

syn_state_t	g_state_tag_cnt_string = {
	&_parser_t::on_enter_tag_cnt_string,
	&_parser_t::on_exit_tag_cnt_string,
	g_state_tag_cnt_string_trans,
	c_msg_enclosed_string,
	"state_tag_cnt_string"
};
	syn_state_trans_t	g_state_tag_cnt_string_trans[] = {
		{	&g_state_tag_cnt_string, c_tok_nonspace,
			&_parser_t::on_token_default, true }
		,{	&g_state_tag_cnt_string, c_tok_space,
			&_parser_t::on_token_default, true }
		,{	&g_state_tag_content, c_tok_dquote, 0, true }
		,{ 0, 0, 0, false }
	};

//
// Real parser implementation
//
_parser_t::_parser_t() :
	m_tag_stack_size( 0 ),
	m_cnt_size( 0 ) {

	m_tag_stack.set(
		new tag_ptr_t[ c_tag_stack_capacity ],
		c_tag_stack_capacity );

	m_cnt.set(
		new char[ c_cnt_capacity ],
		c_cnt_capacity );
}

_parser_t::~_parser_t() {
}

int
_parser_t::parse( parser_context_t & context ) {
	if( !context.query_input_stream() )
		return c_no_input_stream;

	cleanup();

	lex_t lex( *( context.query_input_stream() ) );

	// Bottom tag in the stack must be 'root' tag
	push_tag( &m_root_tag );

	syn_state_t * cur_state = &g_state_start;
	int ret_code = c_ok;
	int token;
	bool is_get_next_token = true;
	char ch;

	while( c_ok == ret_code ) {
		if( is_get_next_token )
			ret_code = lex.get_tok( context, token, ch );

		if( c_ok == ret_code ) {
			ret_code = process_token( context, cur_state,
				token, ch, is_get_next_token );

			if( cur_state == &g_state_finish &&
				c_ok == ret_code ) {
				break;
			}
		}
	}

	return ret_code;
}

void
_parser_t::tag_add( tag_t & tag ) {
	m_root_tag.tag_add( tag );
}

void
_parser_t::tag_remove( tag_t & tag ) {
	m_root_tag.tag_remove( tag );
}

size_t
_parser_t::tag_stack_size() const {
	if( m_tag_stack_size )
		// Root tag is not included
		return m_tag_stack_size - 1;
	return 0;
}

const tag_t *
_parser_t::tag_stack_at( size_t index ) const {
	if( m_tag_stack_size > 1 ) {
		assert( index + 1 < m_tag_stack_size );

		return m_tag_stack.query()[ index + 1 ];
	}

	return 0;
}

void
_parser_t::cleanup() {
	m_tag_stack_size = 0;

	cnt_reset();
}

void
_parser_t::cnt_reset() {
	m_cnt.query()[ m_cnt_size = 0 ] = 0;
}

void
_parser_t::cnt_append( char ch ) {
	if( m_cnt_size + 1 >= m_cnt.size() ) {
		size_t new_capacity = m_cnt.size() << 1;
		auto_ptr_3::vect_ptr_t< char > new_cnt(
			new char[ new_capacity ],
			new_capacity );

		memcpy( new_cnt.query(),
			m_cnt.query(),
			m_cnt.size() );

		m_cnt.swap( new_cnt );
	}

	m_cnt.query()[ m_cnt_size++ ] = ch;
	m_cnt.query()[ m_cnt_size ] = 0;
}

void
_parser_t::push_tag( tag_t * tag ) {
	if( m_tag_stack_size >= m_tag_stack.size() ) {
		size_t new_capacity = m_tag_stack.size() << 1;
		auto_ptr_3::vect_ptr_t< tag_ptr_t > new_stack(
			new tag_ptr_t[ new_capacity ],
			new_capacity );

		memcpy( new_stack.query(),
			m_tag_stack.query(),
			m_tag_stack.size() * sizeof( tag_ptr_t ) );

		m_tag_stack.swap( new_stack );
	}

	m_tag_stack.query()[ m_tag_stack_size++ ] = tag;
}

void
_parser_t::pop_tag() {
	if( m_tag_stack_size ) {
		--m_tag_stack_size;
	}
}

tag_t *
_parser_t::top_tag() {
	if( m_tag_stack_size ) {
		return m_tag_stack.query()[ m_tag_stack_size - 1 ];
	}

	return 0;
}

int
_parser_t::process_token(
	parser_context_t & context,
	syn_state_t * & cur_state,
	int token,
	char ch,
	bool & is_get_next_token ) {

	// We must find transition
	const syn_state_trans_t * trans = cur_state->m_trans;
	for( ; trans->m_state; ++trans ) {
		if( token == trans->m_tok ||
			c_tok_any == trans->m_tok ) {
			// Transition found
			return process_trans( context, trans,
				cur_state, ch, is_get_next_token );
		}
	}

	context.show_error( context.find_msg(
		cur_state->m_error_msg_id ) );

	return c_syntax_error;
}

int
_parser_t::process_trans(
	parser_context_t & context,
	const syn_state_trans_t * trans,
	syn_state_t * & cur_state,
	char ch,
	bool & is_get_next_token ) {

	is_get_next_token = trans->m_is_get_next_token;
	if( trans->m_on_token ) {
		( this->*( trans->m_on_token ) )( context, ch );
	}

	if( cur_state != trans->m_state ) {
		// We must go in to new state
		int result = c_ok;
		if( cur_state->m_on_exit )
			result = ( this->*( cur_state->m_on_exit ) )( context );

		if( c_ok == result && trans->m_state->m_on_enter )
			result = ( this->*( trans->m_state->m_on_enter ) )( context );

		if( c_ok == result )
			cur_state = trans->m_state;
		else
			return result;
	}

	return c_ok;
}

int
_parser_t::start_new_tag(
	parser_context_t & context,
	tag_t * tag ) {
	int result = tag->on_start( context );
	if( c_ok == result )
		push_tag( tag );

	return result;
}

void
_parser_t::on_token_default(
	parser_context_t & context,
	char ch ) {
	cnt_append( ch );
}

int
_parser_t::on_enter_finish(
	parser_context_t & context ) {

	// Only one tag must be in the stack
	// at this point - 'root' tag
	if( m_tag_stack_size > 1 ) {
		context.show_error( context.find_msg(
			c_msg_unexpected_eof ) );
		return c_syntax_error;
	}
	else if( !m_tag_stack_size ) {
		context.show_error( context.find_msg(
			c_msg_missmatched_close_block_count ) );
		return c_syntax_error;
	}

	pop_tag();
	return c_ok;
}

int
_parser_t::on_enter_wait_tag_name(
	parser_context_t & context ) {

	cnt_reset();

	return c_ok;
}

int
_parser_t::on_exit_fill_tag_name(
	parser_context_t & context ) {

	// We must find tag with name from 'm_cnt'
	tag_t * tag = top_tag();
	tag_t ** children_tags = tag->query_tags();

	if( children_tags ) {
		for( ; *children_tags; ++children_tags ) {
			if( (*children_tags)->compare_name(
				m_cnt.query() ) ) {
				// New tag found
				return start_new_tag( context,
					*children_tags );
			}
		}
	}

	// Name of tag unknown
	context.show_error( m_cnt.query() );
	context.show_error( context.find_msg(
		c_msg_unknown_tag ) );

	return c_semantic_error;
}

int
_parser_t::on_enter_tag_complete(
	parser_context_t & context ) {

	tag_t * tag = top_tag();
	return tag->on_finish( context );
}

int
_parser_t::on_exit_tag_complete(
	parser_context_t & context ) {

	tag_t * tag = top_tag();
	pop_tag();

	tag_t * parent_tag = top_tag();
	if( parent_tag )
		return parent_tag->on_tag( context, *tag );

	// Missmatched count of close block
	context.show_error( context.find_msg(
		c_msg_missmatched_close_block_count ) );

	return c_syntax_error;
}

int
_parser_t::on_enter_tag_cnt_space(
	parser_context_t & context ) {

	cnt_reset();

	return c_ok;
}

int
_parser_t::on_exit_tag_cnt_space(
	parser_context_t & context ) {

	tag_t * tag = top_tag();
	return tag->on_tok_space( context,
		m_cnt.query() );
}

int
_parser_t::on_enter_tag_cnt_nonspace(
	parser_context_t & context ) {

	cnt_reset();

	return c_ok;
}

int
_parser_t::on_exit_tag_cnt_nonspace(
	parser_context_t & context ) {

	tag_t * tag = top_tag();
	return tag->on_tok_nonspace( context,
		m_cnt.query() );
}

int
_parser_t::on_enter_tag_cnt_string(
	parser_context_t & context ) {

	cnt_reset();

	return c_ok;
}

int
_parser_t::on_exit_tag_cnt_string(
	parser_context_t & context ) {

	tag_t * tag = top_tag();
	return tag->on_tok_string( context,
		m_cnt.query() );
}

} /* namespace cls_2 */
