/*	input.c
 *  (c) Copyright 1991 by Ben Eng, All Rights Reserved
 *
 */

#include <ctype.h>
#include <clib/exec_protos.h>

#include "make.h"
#include "depend.h"

int line_number;

static struct List Mstack =
{
	(struct Node *)&Mstack.lh_Tail,	/* lh_Head */
	(struct Node *)NULL,			/* lh_Tail */
	(struct Node *)&Mstack.lh_Head,	/* lh_TailPred */
	(UBYTE)NT_USER,
	(UBYTE)0
};

#define STATE_IF_T	1
#define STATE_IF_F	2	/* skip lines mode */
#define STATE_EL_T	3	/* skip lines mode */
#define STATE_EL_F	4

static struct mstate {
	struct Node node;
	int state;
};

static struct drctvs {
	char *directive;	/* the name of the directive */
	int (*call)(char *);	/* function call */
};

/*	binary search to find the function */
struct drctvs *
find_drctvs( struct drctvs *array, int array_size, char *name )
{
	int first = 0L;
	int last = array_size - 1;
	int mid;
	int diff, len;

	/* binary search */
	while( first <= last ) {
		mid = (first+last) / 2;
		len = strlen( array[ mid ].directive );
		diff = strncmp( name, array[ mid ].directive, len );
		if( !diff ) return( &array[ mid ] ); /* found */
		if( first == last ) break; /* not found */
		if( diff < 0 ) last = mid - 1;
		else first = mid + 1;
	}
	return( NULL ); /* not found */
}

static int
push_state( struct List *stack, long state )
{
	struct mstate *new = (struct mstate *)malloc( sizeof(struct mstate));
	if( !new ) return( 1 ); /* no memory */
	new->node.ln_Type = NT_USER;
	new->state = state;
	AddHead( stack, &new->node );
	return( 0 );
}

static int
pop_state( struct List *stack )
{
	struct mstate *new = (struct mstate *)RemHead( stack );
	int state;
	if( !new ) return( 0 ); /* no state */
	state = new->state;
	free( new );
	return( state );
}

static void
clear_stack( struct List *stack )
{
	struct Node *node, *succ;

	for( node = stack->lh_Head; node->ln_Succ; node = succ ) {
		succ = node->ln_Succ;
		free( node );
	}
	NewList( stack );
}

/*	returns true if we are still within a conditional */
int
get_directive_state( void )
{
	struct mstate *first = (struct mstate *)Mstack.lh_Head;
	if( !first->node.ln_Succ ) return( 0 );
	return( first->state );
}

/* conditions return 0 for false, 1 for true, -1 for error */
static int
do_condeq( char *string, int negate )
{
	char *exp1 = NULL, *exp2 = NULL;
	char *lparen = string, *comma, *rparen;
	int retval = -1; /* default to error */

	exp1 = (char*)malloc( Param.MaxLine );
	exp2 = (char*)malloc( Param.MaxLine );
	if( !exp1 || !exp2 ) goto death;

	while( *lparen && *lparen != '(' ) lparen++;
	if( *lparen != '(' ) goto death;
	comma = lparen + 1;
	while( *comma && *comma != ',' ) comma++;
	if( *comma != ',' ) goto death;
	rparen = comma + 1;
	while( *rparen && *rparen != ')' ) rparen++;
	if( *rparen != ')' ) goto death;

	*comma = *rparen = (char)0;
	if( expand_macros( exp1, lparen + 1, Param.MaxLine ) ||
		expand_macros( exp2, comma + 1, Param.MaxLine )) goto death;

	if( negate )
		retval = strcmp( exp1, exp2 ) ? 1 : 0; /* set condition code */
	else
		retval = strcmp( exp1, exp2 ) ? 0 : 1; /* set condition code */

death:
	if( exp1 ) free( exp1 );
	if( exp2 ) free( exp2 );
	return( retval );
}

static int
do_conddef( char *string, int negate )
{
	char *lparen = string, *rparen;
	char *exp1 = NULL;
	int retval = -1; /* default to error */

	exp1 = (char *)malloc( Param.MaxLine );
	if( !exp1 ) goto death;

	while( *lparen && *lparen != '(' ) lparen++;
	if( *lparen != '(' ) goto death;
	rparen = lparen + 1;
	while( *rparen && *rparen != ')' ) rparen++;
	if( *rparen != ')' ) goto death;

	*rparen = (char)0;
	retval = find_macro( lparen + 1 ) ? 1 : 0;
	if( negate ) retval = retval ? 0 : 1;
death:
	if( exp1 ) free( exp1 );
	return( retval );
}

static int
cdrctv_eq( char *string )
{
	return( do_condeq( string, 0 ));
}
static int
cdrctv_neq( char *string )
{
	return( do_condeq( string, 1 ));
}

static int
cdrctv_def( char *string )
{
	return( do_conddef( string, 0 ));
}
static int
cdrctv_ndef( char *string )
{
	return( do_conddef( string, 1 ));
}

/* keep it sorted for binary search */
#define MAX_CDRCTVS 4
static struct drctvs carray[ MAX_CDRCTVS ] = {
	{ "def", 	cdrctv_def	},
	{ "eq",  	cdrctv_eq	},
	{ "ndef",	cdrctv_ndef	},
	{ "neq", 	cdrctv_neq	}
};

static int
drctv_else( char *string )
{
	int state = pop_state( &Mstack );
	int newstate = 0;

	if( state != STATE_IF_T && state != STATE_IF_F ) {
		logfile( "ERROR:  else with no matching conditional\n" );
		return( 1 );
	}
	newstate = (state == STATE_IF_T) ? STATE_EL_T : STATE_EL_F;
	debugprintf( 4, ("else changes state from %d to %d\n",
		state, newstate ));
	push_state( &Mstack, newstate );
	return( 0 );
}

static int
drctv_endif( char *string )
{
	int state = pop_state( &Mstack );

	if( !state ) {
		logfile( "ERROR:  endif with no matching conditional\n" );
		return( 1 );
	}
	return( 0 );
}

static int
drctv_if( char *string )
{
	struct drctvs *cdrctv;
	int condition;

	while( isspace( *string )) string++;
	if( !*string ) {
		logfile( "No condition given\n" );
		return( 1 );
	}

	if( cdrctv = find_drctvs( carray, MAX_CDRCTVS, string )) {
		debugprintf( 4, ("condition %s\n", string ));
		condition = (*cdrctv->call)( string + strlen( cdrctv->directive ));
		debugprintf( 4, ("condition returns %d\n", condition ));
		if( condition < 0 ) {
			logprintf( "Error in condition: %s\n", string );
			return( 1 );
		}
		else if( condition ) { /* execute the true code */
			push_state( &Mstack, STATE_IF_T );
			return( 0 );
		}
		else {	/* execute the false code */
			push_state( &Mstack, STATE_IF_F );
			return( 0 );
		}
	}
	logprintf( "Unrecognized condition: %s\n", string );
	return( 1 );
}

static int
drctv_pragma( char *string )
{
	int i, arguments, retval = 1;
	char **argv = NULL;

	while( isspace( *string )) string++;
	if( arguments = count_args( string ) + 1) {
		if( argv = (char **)malloc( (arguments + 1) * sizeof(char *)) ) {
			for( i = 1; i < arguments; i++ ) {
				argv[ i ] = find_word( string, i );
			}
			argv[ 0 ] = version_string + 7;
			argv[ arguments ] = NULL;
			retval = parse_parameters( arguments, argv );
			free( argv );
		}
	}
	debugprintf( 3,( "Pragma(%d,%s)\n", arguments, string ));
	return( retval );
}

/* keep it sorted for binary search */
#define MAX_MDRCTVS 4
static struct drctvs darray[ MAX_MDRCTVS ] = {
	{ "else",	drctv_else	},
	{ "endif",	drctv_endif	},
	{ "if",		drctv_if	},
	{ "pragma",	drctv_pragma }
};

/*	get a non-comment line
 */
static int
get_ncline( char *buf, int sz, FILE *in )
{
	char *inbuf, *cptr;
	int total, len;

	do {
		if( feof( in )) return( 1 );
		if( !fgets( buf, sz, in )) return( 1);
		line_number++;
		total = strlen( buf );
		inbuf = buf;
		while( cptr = strrchr( inbuf, '\\' ) ){
			if( cptr[1] != '\n' ) break;
			total -= 2; /* subtract backslash newline */
			inbuf = cptr;
			if( total >= sz ) break;
			if( feof( in )) return( 1 );
			if( !fgets( inbuf, sz - total, in )) return( 1 );
			line_number++;
			total += strlen( inbuf );
		}
	} while( *buf == '#' );
	strip_trailspace( buf );
	return( 0 );
}

/*	get the next valid line from a Makefile
 */
int
getline( char *buf, int sz, FILE *in )
{
	struct drctvs *found = NULL;
	int st, state = 0;
	do {
		if( state && !found )
			debugprintf( 4, ("skipped[state=%d] %s\n", state, buf ));
		st = get_ncline( buf, sz, in );
		if( !st ) {
			if( found = find_drctvs( darray, MAX_MDRCTVS, buf )) {
				if( st = (*found->call)( buf + strlen( found->directive )))
					clear_stack( &Mstack );
			}
		}
		state = get_directive_state();
	} while( !st && (state == STATE_IF_F || state == STATE_EL_T || found ));

	return( st );
}

