
/******** rep.c by Dimitri C. Keletsekis *****************************

***	This code compiles into a small executable re-entrant CLI
***	command for replacing strings in files. It can be compiled
***	with SAS C 6.50+.  The SCOPTIONS file is included. Just do
***	<sc rep link> to produce it.
***
***	It's may not be the best, it may not be the fastest, but it
***	works..
***
***	It uses the NOSTARTUP compiler option which means that it
***	compiles without *any* start-up code from the compiler,
***	resulting in a smaller, sexier executable.

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

#define __USE_SYSBASE;
#include <exec/exec.h>
#include <exec/types.h>
#include <exec/execbase.h>
#include <exec/memory.h>
#include <dos/dosextens.h>
#include <dos/rdargs.h>
#include <dos/dostags.h>

#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <dos.h>

#include <proto/dos.h>
#include <proto/exec.h>

#define THISPROC	 ((struct Process *)(SysBase->ThisTask))
#define Result2(x) THISPROC->pr_Result2 = x

static const char VERSION[] = "\0$VER: Rep 1.4 (D.Keletsekis 2/2/99)";

// prototypes
LONG rep (void);
LONG parse (UBYTE *);

LONG rep(void)
{
	struct ExecBase *SysBase = (*((struct ExecBase **) 4));
	struct DosLibrary  *DOSBase=NULL;
	struct RDArgs *rdargs = NULL;
	UBYTE *file, *repstr, *dest;
	UBYTE upstr[256], *str;			// string buffer for case insensitive
	LONG rc;
	long args[5];
	struct FileInfoBlock *fib=NULL;
	UBYTE *buff=NULL;
	BPTR fp;
	LONG buffsize, sz;
	int err = 0, c;
	register UBYTE *p, *d, *t, *u;
	UBYTE *endfile=0;
	BOOL INSENSITIVE = 0;
	LONG lstr, lrep;   // string lengths

	rc = 10;   /* return code */

	if (!(DOSBase = (struct DosLibrary *)OpenLibrary("dos.library", 36L)))
	{	Result2(ERROR_INVALID_RESIDENT_LIBRARY);
		goto endprog;
	}

	memset((char *)args, 0, sizeof(args));
	rdargs = ReadArgs("FILE/A,STRING/A,NEWSTRING/A,NEWFILE,I=INSENSITIVE/S", args, NULL);
	if (!rdargs)
	{	PrintFault (IoErr(), "REP by dck@hol.gr\nERROR");
		goto endprog;
	}

	file   = (UBYTE *)args[0];
	str    = (UBYTE *)args[1];
	repstr = (UBYTE *)args[2];
	if (args[3]) dest = (UBYTE *)args[3]; else dest = file;
	if (args[4]) INSENSITIVE = 1;

	// parse strings for hex, decimals etc
	if (((lstr = parse (str)) == -1) || ((lrep = parse (repstr)) == -1))
	{  PutStr ("Error parsing string\n");
	   goto endprog;
	}
	// max size of upstr buffer
	if (lstr > 250)
	{  PutStr ("String too long\n");
	   goto endprog;
	}

	/*------------------------------ read file ------------------------*/

	if (!(fp = Open (file, MODE_OLDFILE))) 
	{  PutStr ("Could not open file\n");
	   goto endprog;
	}

	if (fib = (struct FileInfoBlock *)AllocVec(sizeof(struct FileInfoBlock), MEMF_CLEAR))
	{   if (ExamineFH(fp, fib))
	    {   buffsize = fib->fib_Size;
	        if (buff = (unsigned char *)AllocVec(buffsize + 32, MEMF_CLEAR))
	        {   sz = Read (fp, buff, buffsize);
	            if (sz != buffsize) ++err;
	            endfile = &buff[sz];
	        }
	    }
	    FreeVec (fib);
	}
	Close (fp);

	if (err)
	{  PutStr ("Error reading file\n");
	   goto endprog;
	}
	else if (!buff)
	{  PutStr ("No memory!\n");
	   goto endprog;
	}

	// ------------------------- prepare.. -------------------

	if (!(fp = Open (dest, MODE_NEWFILE)))
	{  PutStr ("Error writing file\n");
	   goto endprog;
	}
	p = buff;

	// copy to upstr buffer - not with strcpy since there may be NULLs
	for (c=0; c<lstr; ++c) upstr[c] = str[c];
	upstr[c] = 0;

	if (INSENSITIVE)
	{  d = str;
	   u = upstr;               // make lower/upper case buffers
	   for (c=0; c<lstr; ++c)
	   {  if ((*d >= 'A') && (*d < 'a'))  *d = ((*d) + ('a' - 'A'));
	      if ((*u >= 'a') && (*u <= 'z')) *u = ((*u) - ('a' - 'A'));
	      ++d; ++u;
	   }
	}

	// -------------------- DO THE CHACHA ------------------

	while (p < endfile)
	{
	   if ((*p == *str) || (*p == *upstr))
	   {   t = p;
	       d = str;
	       u = upstr;
	       for (c = 0; (c < lstr) && ((*d == *t) || (*u == *t)); ++c)
	       {   ++t; ++d; ++u;  }
	       if (c == lstr)           // found it
	       {   --t;
	           p = t;
	           for (c = 0; c < lrep; ++c)
	           {   if (EOF == (int)(FPutC (fp, repstr[c]))) goto endflag;
	           }
	       }
	       else
	           if (EOF == (int)(FPutC (fp, *p))) goto endflag;
	    }
	    else
	       if (EOF == (int)(FPutC (fp, *p))) goto endflag;

	    ++p;
	}
	rc = 0;
	goto endnoflag;  /* i.e. skip error report */

	endflag:
	PutStr ("Error writing file\n");

	endnoflag:
	Close (fp);

	endprog :
	if (rdargs)  FreeArgs (rdargs);
	if (buff)    FreeVec (buff);
	if (DOSBase) CloseLibrary((struct Library *)DOSBase);
	return (rc);
}

// --------------------------------------------------------------
// Parse a string, overwriting it.
// Return length (may contain NULLs) 
// Return -1 for error
// --------------------------------------------------------------

LONG parse (UBYTE *str)
{
	LONG  len = 0, c;
	int   x;
	UBYTE *p, *s;
	UBYTE hx[5];

	if (!str) return (-1);
	if (!str[0]) return (0);
	s = p = str;

	while (*p)
	{  
	    if (*p == '\\')   // start of special characters
	    {   ++p;
	        switch (*p)
	        {   case 'n' : *s = '\n'; ++s; ++p; break;
	            case 'r' : *s = '\r'; ++s; ++p; break;
	            case 't' : *s = '\t'; ++s; ++p; break;
	            case '\"': *s = '\"'; ++s; ++p; break;
	            case '\'': *s = '\''; ++s; ++p; break;
	            case '\\': *s = '\\'; ++s; ++p; break;

	            case 'X' :
	            case 'x' : // hex
	                 ++p;
	                 while (*p && (*p != '\\'))
	                 {   hx[0] = *p; ++p;
	                     hx[1] = *p; ++p; hx[2] = 0;
	                     if ((stch_i(hx, &x)) != 2)
	                         return (-1);
	                     *s = (UBYTE)x;
	                     ++s;
	                 }
	                 if (*p == '\\') ++p;
	                 break;

	            case '#' : // decimal
	                 ++p; 
	                 c = (*p - '0'); // do first
	                 ++p;
	                 while (*p && (*p!='\\') && (*p>='0') && (*p<='9')) // do rest
	                 {   c *= 10;
	                     c += (LONG)(*p - '0');
	                     ++p;
	                 }
	                 *s = (UBYTE)c; 
	                 ++s;
	                 if (*p == '\\') ++p;
	                 break;

	           default : *s = *p; ++s; ++p; break;
	        };
	    }
	    else
	    {   *s = *p; ++s; ++p;
	    }
	}
	*s = 0;                 // terminating null
	len = s - str;
	return (len);           // return new length
}

