/* **************************************************

	    FLEXPRINTF

************************************************** */

#include <exec/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#undef	Prototype
#define Prototype extern

#define FLEXPRINT_BUFFER 0

#ifndef   STREAM
#define   STREAM void*
#endif

BOOL	     flexprintf (STREAM, BOOL (*)(STREAM, STRPTR), STRPTR, STREAM, STRPTR (*)(STREAM, STRPTR));
BOOL	     fpfputs	(FILE*,  char* );

//#define ERROR_NO_NAME_OF_QUERY   /* error ("%s:\n%s!\n", mod_name, "No name given"); */
//#define ERROR_NO_VALUE_OF_QUERY  /* error ("%s:\n%s %s!\n", mod_name, "No value for variable", memo); */
//#define ERROR_UNTERMINATED_QUERY /* error ("%s:\n%s!\n", mod_name, "Unterminated variable query"); */
//#define ERROR_NO_MEMORY
//#define ERROR_NO_FILE 	   error ("%s:\n%s %s!\n", mod_name, "File not found\n", name);
#define ERROR_NO_NAME_OF_QUERY	 puts("NO_QUERY_NAME");
#define ERROR_NO_VALUE_OF_QUERY  printf("NO_QUERY_RESULT <<%s>>\n", memo + 2);
#define ERROR_UNTERMINATED_QUERY printf("UNTERMINATED_QUERY <<%s>>\n", memo);
#define ERROR_WRITE_FAILED	 puts("WRITE_FAILED");
#define ERROR_NO_MEMORY 	 puts("NO_MEMORY");

#define INC(x) ++(x)
#define DEC(x) --(x)


/* MACROS 18-08-93 by b_noll */
#define is_char(x)     (isalnum(x) || ((x) ==  '_') || ((x) == '.'))
#define is_command(x)  (is_varstart(*(x)) && (*(x) == (x)[1]))
#define is_comment(x)  (*(x) == '#')


/* ---- definitions for "is_varstart" */
#ifdef SLOW_VARSTART
# define is_varstart(x) strchr(query_starts, x)
#else
# define is_varstart(x) (((x) == '$') || ((x) == '%'))
		    /*	((x) == '%') */                  /* <<< use that line to enable only   '%'-variables */
		    /*	((x) == '$') */                  /* <<< use that line to enable only   '$'-variables */
		    /* (((x) == '$') || ((x) == '%')) */ /* <<< use that line to enable '$' or '%'-variables */
#endif


/* ---- definitions for query-recognition */
#define QUERY_START    "%$" /* <<< this line only has affect if SLOW_VARSTART is defined */

/* ---- definitions for bracket-recognition */
#ifdef AMIGA
# define BRACKET_OPEN  "(`[{<\253" /* the last one is '<<' */
# define BRACKET_CLOSE ")']}>\273" /* the last one is '>>' */
#else
# define BRACKET_OPEN  "(`[{<"
# define BRACKET_CLOSE ")']}>"
#endif /* AMIGA */



#ifdef XDME

/* ---- XDME uses a subset of the possibles methods; */
/*	we currently do only allow $(...) and $`...' */
/*	to be recognized and replaced */
# undef SLOW_VARSTART
# undef is_varstart
# undef QUERY_START
# undef BRACKET_OPEN
# undef BRACKET_CLOSE


# define is_varstart(x) ((x) == '$')
# define QUERY_START	"$"
# define BRACKET_OPEN	"(`"
# define BRACKET_CLOSE	")'"

#endif /* XDME */


/* ---- there are currently 2 Versions of flexprintf here;
**	the first - older - version was used over 'bout 1/2 a year
**	and did not show any error; the second one is not yet really
**	tested w/ "XDME", it was used with "Nyktos Ophthalmoi" for
**	'bout 2 months.
**	please do't ask me, why I created 2nd version, there was
**	a reason, but I can't remember ... */

#ifdef OLD


/*****************************************************************************

    NAME
	flexprintf

    PARAMETER
	STREAM outstream;
	;BOOL (*writechar)  (STREAM, char);
	BOOL  (*writestring)(STREAM, STRPTR);
	STRPTR format;
	STREAM instream;
	STRPTR (*getstring)(STREAM, STRPTR);

    RESULT
	success

    RETURN
	BOOL;

    DESCRIPTION
	similar to C_sprintf but slightly different.

	wir parsen ein template, das
	$(...) oder %(...) oder ${...} oder %{...} oder
	$«...» oder %«...» oder $<...> oder %<...> oder
	$[...] oder %[...] oder $`...' oder %`...'
	als signalworte hat;
	was zwischen den Klammern steht, wird an
	getstring uebergeben; dessen Ergebnis
	(wir erwarten IMMER strings) wird dann
	eingesetzt.

	man kann die Funktion auf 2 sehr unterschiediche
	Weisen verwenden:
	    wie sprintf - in diesem Falle ignoriert die
	getfunktion das 2. Argument sondern gibt jedesmal den
	"naechsten" wert zurueck
	    wie hier gedacht - in diesem falle liest die getfunktion
	aus einem baum o.ae. den Wert, der der namen von arg2 traegt.

    NOTES
	STREAM == APTR
	format must not be const!

	not like in normal XDME-scripts there MUST be a form
	of quotes around any variable!
	for XDME "%" and "<...>" "«...»" "[...]" "{...}" were
	disabled.

    BUGS
	we do ignore the result of our writestring
	and we do always return TRUE
	(to fix - change "writestring..."
		  into	 "if (!writestring...) return (FALSE);")

    EXAMPLES

    SEE ALSO

    INTERNALS

    HISTORY
	21-07-93    b_noll  created
	18-08-93    b_noll modified for XDME
	27-09-94    b_noll brackets may be nested now

******************************************************************************/

BOOL flexprintf (STREAM outstream, BOOL (*writestring)(STREAM, STRPTR), STRPTR format, STREAM instream, STRPTR (*getstring)(STREAM, STRPTR))
{
    char	* cptr = format;
    unsigned char curr;
    unsigned char attn = 0;

    for (; (curr = *format) != 0; ++format) {
	if (is_varstart(curr)) {
	    if (attn) *cptr = attn;
	    *format = 0;
	    writestring (outstream, cptr);
	    cptr = format;
	    attn = curr;
	    continue;
	} /* if */

	if (attn) {
	    char scan = 0;

	    switch (curr) {
	 /* some inconsistancies to XDME were commented out */
	 /* case '[': scan = ']'; break; */
	 /* case '«': scan = '»'; break; */
	 /* case '<': scan = '>'; break; */
	 /* case '{': scan = '}'; break; */
	    case '(': scan = ')'; break;
	    case '`': scan = '\''; break;
	    } /* switch */

	    if (scan) {
		char * memo;
		char * rslt;

		++format;
		memo = format;
#if 1 /* nested brackets */
		{
		    unsigned char start = curr;
		    int stack = 0;
		    while ((curr = *format) && (stack || (curr != scan))) {
			if (curr == start)
			    ++stack;
			if (curr == scan)
			    --stack;
			++format;
		    } /* while */
		}
#else
		while ((curr = *format) && (curr != scan)) {
		    ++format;
		} /* while */
#endif

		*format = 0;
		if (*memo) {
		    rslt = getstring (instream, memo);
		    if (rslt) {
			writestring (outstream, rslt);

			*format = scan;
			attn	= 0;
			cptr	= format+1;
			continue;

		    } else {
			ERROR_NO_VALUE
		    } /* if */
		} else {
		    ERROR_NO_NAME
		} /* if */
		*format = scan;
	    } /* if */

	    *cptr = attn;
	    attn  = 0;
	} /* if */
    } /* for */
    writestring (outstream, cptr);
    return (TRUE);

} /* flexprintf */

#else /* !OLD */


/*****************************************************************************

    NAME
	flexprintf

    PARAMETER
	STREAM outstream;
	BOOL (*writestring)(STREAM, STRPTR);
		; we expect writestring to return <>0 on Success and
		; 0 in case of any Failure.
	STRPTR tmplt;
		; Format is destroyed during the scan, we cannot
		; use const-strings in System with mem-protection
	STREAM instream;
	STRPTR (*getstring)(STREAM, STRPTR);

    RESULT
	success

    RETURN
	BOOL;

    DESCRIPTION
	similar to C/sprintf but slightly different in usage and syntax:
	    while sending tmplt through "writestring(outstream,...)",
	we do replace occurancies of [$%][[(`{|<]char-seq[])'}>] with
	the string, that is returned by "getstring(instream, char-seq)";
	the meaning of [$%] can be escaped by duplicating the character.


    NOTES
	* tmplt MUST NOT be const! (see BUGS)
	  for _flexprintf in any case and for flexprintf
	  as long as FLEXPRINT_BUFFER is not defined to 1

	* not like in normal Shell- or XDME-scripts there MUST be a form
	  of quotes around any variable!

	* formatting of replaced variables must be supported by the
	  get-function, if such features are needed.

	* between the brackets of a variable-query there must be at least
	  one char; empty variablenames are not allowed (also if Your getstring
	  knows, what is wanted), and lead to a break.

	* getstring returning NULL means an error has occured; we will break
	  for this reason. Empty strings should be given via "".
	  Furthermore we do not free the strings gotten from getstring(),
	  since we don't know, if they are static or not, so if these strings
	  are dynamically created, You should store them in a static variable.
	  my preferred way looks like the following:
	    char *getvar(void *x, char *n) {
		static char *ptr = NULL;
		if (ptr) free(ptr);
		ptr = ...;
		return ptr;
	    }


	* not terminating a query will cause a break, if there is no other
	  query behind, else the result is not defined.

	* we use different macros to inticate the common errors, which are
	  recognized: ERROR_NO_NAME_OF_QUERY, ERROR_NO_VALUE_OF_QUERY and
	  ERROR_UNTERMINATED_QUERY, ERROR_NO_MEMORY; they are defined to do nothing, so You
	  might want to redefine them to Your needs.

	Please note, that "%$" and "(`[{<«"/")']}>»" may be changed via some
	defines described below:

	* the macro "is_varstart" is used to recognize %/$ - You might want to
	  redefine it according to Your needs (see above for different examples)
	  if defined "SLOW_VARSTART" You can use the define "QUERY_START" to
	  define a list of characters, which may be used as start of a query-
	  indicator.

	* the defines "BRACKET_OPEN" and "BRACKET_CLOSE" can be used to define
	  the different open-brackets and their corresponding close-brackets
	  (see above for examples)

	* to gain more flexibility You may directly call _flexprintf()
	  which has the additional parameters query_starts ("$%"),
	  open_brackets ("([{...") and close_brackets (")]}...")

    BUGS
	* tmplt is temporarily destroyed during the scan. this is done,
	  since we have no information about a necessary buffersize to
	  keep a string in, but we wanted to use string-operations instead
	  of character operations for writestring.
	  (the described behaiviour can be changed (at least for flexprintf,
	  not for _flexprintf) by defining FLEXPRINT_BUFFER to 1. (that toggle
	  makes flexprintf create a copy of the template and send the copy
	  to _flexprintf))

    EXAMPLES

    SEE ALSO

    INTERNALS
	* Funktionsweise:
	  wir parsen ein template, das normalerweise
	  $(...) oder %(...) oder ${...} oder %{...} oder
	  $«...» oder %«...» oder $<...> oder %<...> oder
	  $[...] oder %[...] oder $`...' oder %`...'
	  als signalworte hat; sobald wir auf ein %/$ stossen, wird der
	  bisher geparste und nicht ausgegebene Teil des templates nach
	  writestring geschickt. Falls nach %/$ dann eine der Klammern kommt,
	  wird, was zwischen den Klammern steht, an getstring uebergeben,
	  dessen Ergebnis (wir erwarten IMMER strings) wird dann an
	  writestring geschickt. Um die Bedeutung von %/$ zu loeschen
	  muss das entsprechende Symbol dupliziert werden, nur eines der
	  beiden wird dann ausgegeben. Falls keine bracket folgt, ist eine
	  Verdopplung eigentlich unnoetig, sollte aber dennoch erfolgen.

	* Anwendung:
	  man kann die Funktion auf 2 sehr unterschiediche
	  Weisen verwenden:
	    wie sprintf - in diesem Falle ignoriert die
	  get-funktion das 2. Argument und gibt jedesmal den
	  "naechsten" wert zurueck
	    wie hier gedacht - in diesem falle liest die getfunktion
	  aus einem baum o.ae. den Wert, der der namen von arg2 traegt.


    HISTORY
	21-07-93    b_noll created
	17-08-93    b_noll rewritten without "writechar()"
	18-08-93    b_noll modified for XDME
	01-05-94    b_null major rewrites ...

******************************************************************************/


BOOL _flexprintf (STREAM outstream, BOOL (*writestring)(STREAM, STRPTR), STRPTR tmplt, STREAM instream, STRPTR (*getstring)(STREAM, STRPTR), const STRPTR query_starts, const STRPTR open_brackets, const STRPTR close_brackets) {
    unsigned char *memo = tmplt;
    unsigned char  curr;
    unsigned char  attn = 0;

    for (; (curr = *tmplt) != 0; ++tmplt) {

	/* ---- seems, that we are starting a query, output all chars up to here, mark the pos w/ memo */
#ifdef SLOW_VARSTART
	if (strchr(query_starts, curr))
#else
	if (is_varstart(curr))
#endif
	{

	    /* ---- previous char was the same? - then allow output of one of'em */
	    /*	    that hack was done to allow something like escaping; now we  */
	    /*	    forbid a query by preceding it with two the same varstarts	 */
	    if (curr == attn) {
		memo = tmplt;
		attn = 0;
		continue;
	    } /* if */

	    /* ---- output the tmplt up to this point and     */
	    /*	    mark the position with an additional ptr  */
	    /*	    keep also a copy of varstart to recognize */
	    /*	    duplicates in order to ignore them	      */

	    /* ---- we do temporarily modify tmplt here!!!! */
	    *tmplt = 0;
	    if (!writestring (outstream, memo)) {
		*tmplt = curr;
		ERROR_WRITE_FAILED
		return FALSE;
	    } /* if */
	    *tmplt = curr;

	    memo   = tmplt;
	    attn   = curr;
	    continue;
	} /* if varstart */

	if (attn) {
	    unsigned char scan = 0;

	    attn    = 0;

	    /* ---- check, if the current character is an open-bracket	*/
	    /*	    and get the close-bracket, that is correponding to	*/
	    /*	    this open-bracket					*/
	    {
		int i;
		for (i = 0; open_brackets[i]; ++i) {
		    if (curr == open_brackets[i]) {
			scan = close_brackets[i];
			break;
		    } /* if */
		} /* for */
	    }

	    /* ---- we found a bracket after var_start */
	    if (scan) {
		char * rslt;

		/* ---- skip the open-bracket */
		++tmplt;

		/* ---- search the close-bracket */
#if 1
		{
		    unsigned char start = curr;
		    int stack = 0;
		    while ((curr = *tmplt) && (stack || (curr != scan))) {
			if (curr == start)
			    ++stack;
			if (curr == scan)
			    --stack;
			++tmplt;
		    } /* while */
		}
#else
		while ((curr = *tmplt) && !(curr == scan))
		    ++tmplt;
#endif

		/* ---- make sure we have found a terminating close-bracket */
		if (!curr) {
		    ERROR_UNTERMINATED_QUERY
		    return FALSE;
		} /* if */

		/* ---- make sure we have got at least one char */
		/*	between the backets			*/
		if (tmplt == memo + 2) {
		    ERROR_NO_NAME_OF_QUERY
		    return FALSE;
		} /* if */

		/* ---- query for the variable described between */
		/*	the brackets				 */

		/* ---- here we do temporarily modify tmplt!!! */
		*tmplt = 0;
		rslt = getstring (instream, memo + 2);
		*tmplt = scan;

		/* ---- make sure we got a valid string from getstring() */
		if (!rslt) {
		    ERROR_NO_VALUE_OF_QUERY
		    return FALSE;
		} /* if !rslt */

		/* ---- output the resultstring */
		if (!writestring (outstream, rslt)) {
		    ERROR_WRITE_FAILED
		    return FALSE;
		} /* if */

		memo = tmplt+1;
	    } /* if bracket */
	} /* if attn */
    } /* for */

    if (!writestring (outstream, memo)) {
	ERROR_WRITE_FAILED
	return FALSE;
    } /* if */

    return TRUE;
} /* _flexprintf */


Prototype BOOL flexprintf (STREAM outstream, BOOL (*writestring)(STREAM, STRPTR), STRPTR tmplt, STREAM instream, STRPTR (*getstring)(STREAM, STRPTR));
BOOL flexprintf (STREAM outstream, BOOL (*writestring)(STREAM, STRPTR), STRPTR tmplt, STREAM instream, STRPTR (*getstring)(STREAM, STRPTR)) {
#if defined(FLEXPRINT_BUFFER) && FLEXPRINT_BUFFER
    char *dup;
    BOOL  rslt;

    if (NULL == (dup = strdup(tmplt))) {
	ERROR_NO_MEMORY
	return FALSE;
    } /* if */

    if (!writestring)
	writestring = fpfputs;

    rslt = _flexprintf(outstream, writestring, dup, instream, getstring, QUERY_START, BRACKET_OPEN, BRACKET_CLOSE);
    free (dup);
    return rslt;
#else
    return _flexprintf(outstream, writestring, tmplt, instream, getstring, QUERY_START, BRACKET_OPEN, BRACKET_CLOSE);
#endif
} /* flexprintf */


#endif /* OLD */


Prototype BOOL fpfputs (FILE *out, char* str);
BOOL fpfputs (FILE *out, char* str) {
    if (!*str) return 1;
    if (!out)  out = stdout;
    return (BOOL)!fputs(str, out);
} /* fpfputs */







