/*
    FlexCat.c:	The flexible catalog creator			V 1.5

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program 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.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.



    Ok, this is nothing special. It grabs a catalog translation and a
    catalog description file and produces catalogs and the source to
    handle 'em. What is it else than lots of other programs?

    The difference is, that YOU determine what source FlexCat produces.
    Another file is scanned by FlexCat to produce code. This file contains
    some c-string like special characters (%v for version for example)
    You can edit this file and modify it as you want. So FlexCat can produce
    C source as well as Assembler, Oberon, Modula 2, E, ...


    Command line syntax:

	FlexCat CDFILE/A,CTFILE,NEWCTFILE/K,CATALOG/K,SOURCES/M

    CDFILE    - the name of the catalog description file; see CatComp for
		syntax
    CTFILE    - the name of a catalog translation file; see CatComp for
		syntax
    NEWCTFILE - the name of a catalog transcription file to be created
		(will be a copy of CDFILE except that the strings remain
		empty and will be put as comments behind the string
		definition)
    CATALOG   - the name of the catalog to be created; default is the
		basename of the CDFILE and .catalog appended
    SOURCES   - the sourcefiles to be created. These arguments take a special
		form: sourcefile=templatefile where sourcefile is the
		name of the file to create and templatefile is the name of
		a template file containing some special patterns. The
		template file will be scanned and written to the source
		file with these special patterns replaced by certain
		strings. Possible patterns are:

		    %b	    basename of CDFILE (for example "file", if
			    CDFILE is "file.cd")
		    %f0     the filename of CDFILE
		    %f1     same as %f0, except that a trailing
			    ending will be removed
		    %f2     same as %f0, except that leading pathname
			    components will be removed
		    %f3     same as %f0, except that both ending and
			    pathname components will be removed
		    %fv     flexcat version
		    %l	    language (of CDFILE)
		    %n	    number of catalog strings
		    %o0     the name of the sourcefile, that is currently
			    created; (What the heck do the guys need this
			    for? :-)
		    %o1     same as %00, except that a trailing
			    ending will be removed
		    %o2     same as %o0, except that leading pathname
			    components will be removed
		    %o3     same as %o0, except that both ending and
			    pathname components will be removed
		    %v	    version
		    %%	    the percent sign itself

		The following patterns indicate, that the line should be
		repeated for each catalog string.
		    %i	    the string's ID descriptor
		    %nd     the string's ID (decimal), n is the number of
			    bytes to write (will be filled up with zeros);
			    default is to suppress leading zeros, if n
			    is omitted
		    %nx     like %hd, except that output is in hex
		    %nc     like %hd, except that output is in octal
		    %e	    the string's number (counting from 0)
		    %s	    the string itself
		    %(...)  indicates that anything between the brackets
			    should appear for any line except the last
			    (Oberon and Modula need a "," to separate array
			    entries, but the last entry must not be
			    terminated by a ",". So you would terminate an
			    array entry by a %(,).)

    Example: Let the string description be

    MSG_HELLO (5//)
    Hello!

    Then the special characters produce
	%i = MSG_HELLO
	%d = 5
	%s = "Hello!"

    Additionally the following characters may appear (as well as in the
    catalog description and catalog translation file)
	\b    = Backspace (Ascii 8)
	\c    = Control sequence introducer (Ascii 155)
	\e    = Escape (Ascii 27)
	\f    = Form feed (Ascii 12)
	\g    = Display beep (Ascii 7)
	\n    = line feed, newline (Ascii 10)
	\r    = carriage return (Ascii 13)
	\t    = Tab (Ascii 9)
	\v    = Vertical tab (Ascii 11)
	\)    = the character ')' (Needed inside %(..))
	\\    = the backslash itself
	\xHH  = Ascii code HH (where HH are hex digits)
	\OOO  = Ascii code OOO (where OOO are octal digits)

    A backslash at the end of a line indicates that the line should be
    concatenated with the next one.


    Computer:	Amiga 1200			Compiler: Dice, 3.01

    Author:	V 1.0	    31.06.1993		Jochen Wiedmann
						Am Eisteich 9
					  72555 Metzingen
						Tel. 07123 / 14881

			    Internet: wiedmann@uni-tuebingen.de

		V1.01	    Fixed a bug: The length of the source string
			    was used to check for the stringlen instead of
			    the real stringlen.

		V1.1	    Fixed two bugs: FlexCat didn't notice, if an
			    ID was defined twice (C-Compiler did later.) and
			    using language strings like français did not work
			    because of the the accented char. Introduced E
			    support. (Thanks Lionel Vintenat)

		V1.2	    Fixed a bug in the E source generator: " was
			    converted into \" and ' was not converted.

		V1.3	    Fixed a bug that caused FlexCat to hang if a
			    catalog translation was updated and a string
			    was missing in the catalog description.

			    Fixed a bug that caused FlexCat to create
			    invalid catalogs probably. (Seems like the
			    locale.library expects a completely different
			    behaviour in padding version and language
			    strings and the real catalog strings.)

			    Added %e to source descriptions.

		V1.4	    Added the Environment variable FLEXCAT_SDDIR.
			    Uses AutoC_c.sd and AutoC_h.sd now and is thus
			    compilable by Dice and SAS/C only.

			    Fixed a problem in stringtype Oberon:
			    Binary characters should be like \000 and
			    not \0.

			    Fixed a problem in stringtype E:
			    \e was written as \033.

			    Added %f* (source description filename) and %o*
			    (source filename) to source descriptions.

		V1.5	    Added WARNCTGAPS option.
			    Added #chunk to catalog translations.
*/


#define VERSION 1
#define REVISION 5
#define VERS "FlexCat 1.5"
#define VSTRING "FlexCat 1.5 (07.11.94)"
#define VERSTAG "\0$VER: FlexCat 1.5 (07.11.94)"



#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "FlexCat_cat.h"

#if ((defined(_DCC) && defined(AMIGA))       ||     \
     (defined(__SASC) && defined(_AMIGA)))      &&  \
    !defined(__amigados)
#define __amigados
#endif

#if defined(__amigados)
#include <exec/types.h>
#if defined(_DCC) || defined(__SASC) || defined(__GNUC__)
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/utility.h>
#else
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/utility_protos.h>
#endif

#ifdef tolower
#undef tolower
#endif
#define tolower ToLower
#define stricmp(s,t) Stricmp((STRPTR) (s), (STRPTR) (t))
#define strnicmp(s,t,l) Strnicmp((STRPTR) (s), (STRPTR) (t), l)

#endif


#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE (!FALSE)
#endif


#define MAXPATHLEN 512
#define FLEXCAT_SDDIR "FLEXCAT_SDDIR"
#if defined(__amigados)
#define DEFAULT_FLEXCAT_SDDIR "PROGDIR:lib"
#else
#define DEFAULT_FLEXCAT_SDDIR "lib"
#endif



enum StringTypes {
    TYPE_C,	    /*	Produce C strings		    */
    TYPE_ASSEMBLER, /*	Produce Assembler strings	    */
    TYPE_OBERON,    /*	Produce Oberon strings		    */
    TYPE_E,	    /*	Produce E strings. (Oops, thought   */
		    /*	it allows only 32 bit integers? ;-) */
    TYPE_NONE	    /*	Simple strings			    */
};


enum OutputModes {
    OutputMode_None,	/*  Nothing written yet 		*/
    OutputMode_Bin,	/*  Last character written was binary	*/
    OutputMode_Ascii	/*  Last character written was Ascii	*/
};

struct CatString
{ struct CatString *Next;
  char *CD_Str;
  char *CT_Str;
  char *ID_Str;
  int MinLen, MaxLen, ID, Nr;
};

struct CDLine
{ struct CDLine *Next;
  char *Line;
};

struct CatalogChunk
{ struct CatalogChunk *Next;
  ULONG ID;
  char *ChunkStr;
};

struct CatString *FirstCatString = NULL;  /*  First catalog string	*/
struct CDLine *FirstCDLine = NULL;  /*	First catalog description line	*/
struct CatalogChunk *FirstChunk = NULL; /*  List of catalog chunks	*/

char *BaseName = "";                /*  Basename of catalog description */
char *Language = "english";         /*  Language of catalog description */
int CatVersion = 0;		    /*	Version of catalog to be opened */
int LengthBytes = 0;		    /*	Number of bytes to preceed a	*/
				    /*	created string and containing	*/
				    /*	its length.			*/
char *CatLanguage = NULL;	    /*	Language of catalog translation */
char *CatVersionString = NULL;	    /*	version string of catalog	*/
				    /*	translation			*/
int CodeSet = 0;		    /*	Codeset of catalog translation	*/
int NumStrings = 0;		    /*	Number of catalog strings	*/
int LongStrings = TRUE; 	    /*	Generate long or short strings	*/


char *ScanFile; 		    /*	File currently scanned		*/
int ScanLine;			    /*	Line currently scanned		*/

int GlobalReturnCode = 0;	    /*	Will be 5, if warnings appear	*/
int WarnCTGaps = FALSE; 	    /*	Warn missing symbols in CT	*/
				    /*	file.				*/





const UBYTE VersTag[] = VERSTAG;
const UBYTE VString[] = VSTRING "  by  Jochen Wiedmann";




/*
    This shows an error message and terminates
*/
struct RDArgs *Args;
void ShowError(const UBYTE (*msg), ...)

{ char **ptr = (char **) &msg;

  fprintf(stderr, ptr[0], ptr[1], ptr[2], ptr[3], ptr[4]);
  putc('\n', stderr);
  exit(10);
}



/*
    This shows the message: Memory error.
*/
void MemError(void)

{ ShowError(GetString(msgMemoryError), NULL);
}




/*
    This shows a warning
*/
void ShowWarn(const UBYTE (*msg), ...)

{ char **ptr = (char **) &msg;

  fprintf(stderr, (char *) GetString(msgWarning), ScanFile, ScanLine);
  fprintf(stderr, ptr[0], ptr[1], ptr[2], ptr[3], ptr[4]);
  putc('\n', stderr);
  GlobalReturnCode = 5;
}




/*
    This allocates a string
*/
char *AllocString(const char *str)

{ char *ptr;

  if (!(ptr = malloc(strlen(str)+1)))
  { MemError();
  }
  strcpy(ptr, str);
  return(ptr);
}




/*
    This adds a new catalog chunk to the list of catalog
    chunks.
*/
char *AddCatalogChunk(char *ID, const char *string)

{ struct CatalogChunk *cc, **ccptr;

  if (!(cc = malloc(sizeof(*cc))))
  { MemError();
  }
  cc->Next = NULL;
  cc->ID = *((ULONG *) ID);
  cc->ChunkStr = AllocString(string);

  /*
      Put the new chunk to the end of the chunk list.
  */
  for (ccptr = &FirstChunk;  *ccptr != NULL;  ccptr = &(*ccptr)->Next)
  {
  }
  *ccptr = cc;
  return(cc->ChunkStr);
}





/*
    This translates a hex character.
*/
int gethex(int c)

{
  if (c >= '0'  &&  c <= '9')
  { return(c - '0');
  }
  else if (c >= 'a'  &&  c <= 'f')
  { return(c - 'a' + 10);
  }
  else if (c >= 'A'  &&  c <= 'F')
  { return(c - 'A' + 10);
  }
  ShowWarn(GetString(msgExpectedHex));
  return(0);
}




/*
    This translates an octal digit.
*/
int getoctal(int c)

{
  if (c >= '0'  &&  c <= '7')
  { return(c - '0');
  }
  ShowWarn(GetString(msgExpectedOctal));
  return(0);
}




/*
    Reading a line is somewhat complicated in order to allow lines of any
    length.

    Inputs: fp		 - the file, where the input comes from
	    AllowComment - TRUE, if a leading semicolon should force to
			   interpret the line as a comment line
*/
#define BUFSIZE 4096
char *ReadLine(FILE *fp, int AllowComment)

{ char *OldLine, *NewLine = NULL;
  int c = '\0';
  int Len = 0, LineLen = 0;
  int FirstChar = TRUE;
  int BackslashSeen = FALSE;

  while (c != EOF)
  { if (Len+10 > LineLen)
    { OldLine = NewLine;
      if (!(NewLine = malloc(LineLen+BUFSIZE)))
      { MemError();
      }
      strncpy(NewLine, OldLine, LineLen);
      if (OldLine)
      { free(OldLine);
      }
      LineLen += BUFSIZE;
    }

    c = getc(fp);
    if (FirstChar)
    { if (c == EOF)
      { free(NewLine);
	return(NULL);
      }
      FirstChar = FALSE;
    }

    switch(c)
    { case '\r':
	break;
      case '\n':
	++ScanLine;
	if (BackslashSeen)
	{ NewLine[Len++] = c;
	  BackslashSeen = FALSE;
	  break;
	}
	c = EOF;
      case EOF:
	break;
      case '\\':
	BackslashSeen = TRUE;
	NewLine[Len++] = c;
	break;
      default:
	BackslashSeen = FALSE;
	NewLine[Len++] = c;
    }
  }
  NewLine[Len] = '\0';

  return(NewLine);
}




/*
    This removes trailing blanks.
*/
void OverSpace(char **strptr)

{ int c;

  while ((c = **strptr) == ' '  ||  c == '\t')
  { (*strptr)++;
  }
}








/*
    ReadChar scans an input line translating the backslash characters.

    Inputs: strptr  - a pointer to a stringpointer; the latter points to the
		      next character to be read and points behind the read
		      bytes after executing ReadChar
	    dest    - a pointer to a buffer, where the read bytes should be
		      stored

    Result: number of bytes that are written to dest (between 0 and 2)
*/
int ReadChar(char **strptr, char *dest)

{ char c;
  int i;

  switch(c = *((*strptr)++))
  { case '\\':
      switch(c = tolower((int) *((*strptr)++)))
      { case '\n':
	  return(0);
	case 'b':
	  *dest = '\b';
	  break;
	case 'c':
	  *dest = '\233';
	  break;
	case 'e':
	  *dest = '\033';
	  break;
	case 'f':
	  *dest = '\f';
	  break;
	case 'g':
	  *dest = '\007';
	  break;
	case 'n':
	  *dest = '\n';
	  break;
	case 'r':
	  *dest = '\r';
	  break;
	case 't':
	  *dest = '\t';
	  break;
	case 'v':
	  *dest = '\013';
	  break;
	case 'x':
	  *dest = gethex((int) **strptr);
	  (*strptr)++;
	  if (((c = **strptr) >= '0'  &&  c <= '9')  ||
	      (c >= 'a'  &&  c <= 'f')  ||  (c >= 'A'  &&  c <= 'F'))
	  { *dest = (*dest << 4) + gethex((int) c);
	    (*strptr)++;
	  }
	  break;
	case '0':
	case '1':
	case '2':
	case '3':
	case '4':
	case '5':
	case '6':
	case '7':
	  *dest = getoctal((int) **strptr);
	  (*strptr)++;
	  for (i = 0;  i < 2;  i++)
	  { if ((c = **strptr) >= '0'  &&  c <= '7')
	    { *dest = (*dest << 3) + getoctal((int) c);
	      (*strptr)++;
	    }
	  }
	  break;
	case ')':
	case '\\':
	  *(dest++) = '\\';
	  *dest = c;
	  return(2);
	default:
	  *dest = c;
      }
      break;
    default:
      *dest = c;
  }
  return(1);
}




/*
    This scans the catalog description file.

    Inputs: cdfile  - name of the catalog description file

    Result: TRUE, if successfull, FALSE otherwise
*/
int ScanCDFile(char *cdfile)

{ FILE *fp;
  struct CDLine *cdline, **cdptr = &FirstCDLine;
  struct CatString *cs, **csptr = &FirstCatString;
  char *line, *newline;
  char *ptr;
  int NextID = 0, len;
  int Result = TRUE;

  ScanFile = cdfile;
  ScanLine = 0;

  if (!(fp = fopen(cdfile, "r")))
  { ShowError(GetString(msgNoCatalogDescription), cdfile);
  }

  /*
      Get the basename
  */
  if ((ptr = strchr(cdfile, ':')))
  { cdfile = ptr+1;
  }
  if ((ptr = strrchr(cdfile, '/')))
  { cdfile = ptr+1;
  }
  if ((ptr = strrchr(cdfile, '.')))
  { len = ptr-cdfile;
  }
  else
  { len = strlen(cdfile);
  }
  if (!(BaseName = malloc(len+1)))
  { MemError();
  }
  strncpy(BaseName, cdfile, len);
  BaseName[len] = '\0';

  while(!feof(fp)  &&  (line = newline = ReadLine(fp, TRUE)))
  { if (!(cdline = malloc(sizeof(*cdline))))
    { MemError();
    }
    *cdptr = cdline;
    cdptr = &cdline->Next;
    cdline->Next = NULL;
    cdline->Line = line = AllocString(newline);
    free(newline);

    if (*line == ';')
    { continue;
    }
    if (*line == '#')
    { int CheckExtra = TRUE;

      if (strnicmp(line+1, "language", 8) == 0)
      { char *ptr;

	line += 9;
	OverSpace(&line);
	Language = AllocString(line);
	for (ptr = Language;  *ptr;  ptr++)
	{ *ptr = tolower((int) *ptr);
	}
	CheckExtra = FALSE;
      }
      else if (strnicmp(line+1, "version", 7) == 0)
      { CatVersion = strtol(line+8, &line, 10);
      }
      else if (strnicmp(line+1, "lengthbytes", 11) == 0)
      { line += 12;
	if ((LengthBytes = strtol(line, &line, 10))
			 > sizeof(long))
	{ ShowWarn(GetString(msgNoLengthBytes), sizeof(long));
	  LengthBytes = sizeof(long);
	}
      }
      else if (strnicmp(line+1, "basename", 8) == 0)
      { line += 9;
	OverSpace(&line);
	free(BaseName);
	BaseName = AllocString(line);
	CheckExtra = FALSE;
      }
      else
      { ShowWarn(GetString(msgUnknownCDCommand));
	Result = FALSE;
	CheckExtra = FALSE;
      }
      if (CheckExtra)
      { OverSpace(&line);
	if (*line)
	{ ShowWarn(GetString(msgExtraCharacters));
	  Result = FALSE;
	}
      }
    }
    else
    { char *idstr;

      if (*line == ' '  ||  *line == '\t')
      { ShowWarn(GetString(msgUnexpectedBlanks));
	Result = FALSE;
	OverSpace(&line);
      }

      idstr = line;
      while ((*line >= 'a'  &&  *line <= 'z')  ||
	     (*line >= 'A'  &&  *line <= 'Z')  ||
	     (*line >= '0'  &&  *line <= '9')  ||
	     *line == '_')
      { ++line;
      }

      if (idstr == line)
      { ShowWarn(GetString(msgNoIdentifier));
	Result = FALSE;
      }
      else
      { int found;

	if (!(cs = malloc(sizeof(*cs))))
	{ MemError();
	}

	do
	{ struct CatString *scs;

	  found = TRUE;
	  for (scs = FirstCatString;  scs != NULL;  scs = scs->Next)
	  { if (scs->ID == NextID)
	    { found = FALSE;
	      ++NextID;
	      break;
	    }
	  }
	}
	while (!found);

	cs->Next = NULL;
	cs->ID = NextID;
	cs->MinLen = 0;
	cs->MaxLen = -1;
	cs->CD_Str = "";
	cs->CT_Str = NULL;

	if (!(cs->ID_Str = malloc((line-idstr)+1)))
	{ MemError();
	}
	strncpy(cs->ID_Str, idstr, line-idstr);
	cs->ID_Str[line-idstr] = '\0';

	OverSpace(&line);

	if (*line != '(')
	{ ShowWarn(GetString(msgNoLeadingBracket));
	  Result = FALSE;
	}
	else
	{ char *oldstr;
	  struct CatString *scs;
	  char bytes[10];
	  int bytesread, reallen;

	  ++line;
	  OverSpace(&line);
	  if (*line != '/')
	  {
	    cs->ID = NextID = strtol(line, &line, 10);
	    OverSpace(&line);
	  }

	  for(scs = FirstCatString;  scs != NULL;  scs = scs->Next)
	  { if (scs->ID == cs->ID)
	    { ShowWarn(GetString(msgDoubleID));
	      Result = FALSE;
	    }
	    if (strcmp(cs->ID_Str, scs->ID_Str)  ==  0)
	    { ShowWarn(GetString(msgDoubleIdentifier));
	      Result = FALSE;
	    }
	  }

	  if (*line != '/')
	  { ShowWarn(GetString(msgNoMinLen));
	    Result = FALSE;
	  }
	  else
	  { ++line;
	    OverSpace(&line);
	    if (*line != '/')
	    { cs->MinLen = strtol(line, &line, 10);
	      OverSpace(&line);
	    }
	    if (*line != '/')
	    { ShowWarn(GetString(msgNoMaxLen));
	      Result = FALSE;
	    }
	    else
	    { ++line;
	      OverSpace(&line);
	      if (*line != ')')
	      { cs->MaxLen = strtol(line, &line, 10);
		OverSpace(&line);
	      }
	      if (*line != ')')
	      { ShowWarn(GetString(msgNoTrailingBracket));
		Result = FALSE;
	      }
	      else
	      { ++line;
		OverSpace(&line);
		if (*line)
		{ ShowWarn(GetString(msgExtraCharacters));
		}
	      }
	    }
	  }
	if (!(newline = ReadLine(fp, FALSE)))
	{ ShowWarn(GetString(msgNoString));
	  Result = FALSE;
	  cs->CD_Str = "";
	}
	else
	{ cs->CD_Str = AllocString(newline);
	  free(newline);
	}

	/*
	    Get stringlen
	*/
	oldstr = cs->CD_Str;
	reallen = 0;
	while (*oldstr)
	{ bytesread = ReadChar(&oldstr, bytes);
	  if (bytesread == 2)
	  { bytesread--;
	  }
	  reallen += bytesread;
	}

	if (cs->MinLen > 0  &&  reallen < cs->MinLen)
	{ ShowWarn(GetString(msgShortString));
	}
	if (cs->MaxLen > 0  &&  reallen > cs->MaxLen)
	{ ShowWarn(GetString(msgLongString));
	}

	cs->Nr = NumStrings;

	*csptr = cs;
	csptr = &cs->Next;
	++NumStrings;
	}
      }
    }
  }
  fclose(fp);
  return(Result);
}




/*
    This scans a catalog translation file.

    Inputs: ctfile	- name of the translation file to scan.

    Result: TRUE, if successfull, FALSE otherwise.
*/
int ScanCTFile(char *ctfile)

{ FILE *fp;
  char *newline, *line, *idstr, *newidstr, *newstr;
  struct CatString *cs;
  int Result = TRUE;

  ScanFile = ctfile;
  ScanLine = 0;

  if (!(fp = fopen(ctfile, "r")))
  { ShowError(GetString(msgNoCatalogTranslation), ctfile);
  }


  while (!feof(fp)  &&  (line = newline = ReadLine(fp, TRUE)))
  { switch(*line)
    { case ';':
	break;
      case '#':
	if (*(++line) != '#')
	{ ShowWarn(GetString(msgNoCTCommand));
	}
	++line;
	OverSpace(&line);
	if (strnicmp(line, "version", 7) == 0)
	{ if (CatVersionString)
	  { ShowWarn(GetString(msgDoubleCTVersion));
	  }
	  line += 7;
	  OverSpace(&line);

	  CatVersionString = AddCatalogChunk("FVER", line);
	}
	else if (strnicmp(line, "codeset", 7) == 0)
	{ line += 7;
	  CodeSet = strtol(line, &line, 10);
	  OverSpace(&line);
	  if (*line)
	  { ShowWarn(GetString(msgExtraCharacters));
	  }
	}
	else if (strnicmp(line, "language", 8) == 0)
	{ char *ptr;

	  if (CatLanguage)
	  { ShowWarn(GetString(msgDoubleCTLanguage));
	  }
	  line += 8;
	  OverSpace(&line);
	  CatLanguage = AddCatalogChunk("LANG", line);
	  for (ptr = CatLanguage;  *ptr;  ptr++)
	  { *ptr = tolower((int) *ptr);
	  }
	}
	else if (strnicmp(line, "chunk", 5) == 0)
	{ char *ID;

	  line += 5;
	  OverSpace(&line);
	  ID = line;
	  line += sizeof(ULONG);
	  OverSpace(&line);

	  AddCatalogChunk(ID, AllocString(line));
	}
	else
	{ ShowWarn(GetString(msgUnknownCTCommand));
	}
	break;
      default:
	if (*line == ' '  ||  *line == '\t')
	{ ShowWarn(GetString(msgUnexpectedBlanks));
	  OverSpace(&line);
	}
	idstr = line;
	while ((*line >= 'a'  &&  *line <= 'z')  ||
	       (*line >= 'A'  &&  *line <= 'Z')  ||
	       (*line >= '0'  &&  *line <= '9')  ||
	       *line == '_')
	{ ++line;
	}
	if (idstr == line)
	{ ShowWarn(GetString(msgNoIdentifier));
	  break;
	}
	if (!(newidstr = malloc(line-idstr+1)))
	{ MemError();
	}
	strncpy(newidstr, idstr, line-idstr);
	newidstr[line-idstr] = '\0';
	OverSpace(&line);
	if (*line)
	{ ShowWarn(GetString(msgExtraCharacters));
	}
	if ((newstr = ReadLine(fp, FALSE)))
	{ for(cs = FirstCatString;  cs != NULL;  cs = cs->Next)
	  { if (strcmp(cs->ID_Str, newidstr) == 0)
	    { break;
	    }
	  }
	  if (cs == NULL)
	  { ShowWarn(GetString(msgUnknownIdentifier), newidstr);
	  }
	  else
	  { char *oldstr;
	    char bytes[10];
	    int bytesread, reallen;

	    if (cs->CT_Str)
	    { ShowWarn(GetString(msgDoubleIdentifier));
	      Result = FALSE;
	      free (cs->CT_Str);
	    }
	    cs->CT_Str = AllocString(newstr);

	    /*
		Get stringlen
	    */
	    oldstr = cs->CT_Str;
	    reallen = 0;
	    while (*oldstr)
	    { bytesread = ReadChar(&oldstr, bytes);
	      if (bytesread == 2)
	      { bytesread--;
	      }
	      reallen += bytesread;
	    }

	    if (cs->MinLen > 0  &&  reallen < cs->MinLen)
	    { ShowWarn(GetString(msgShortString));
	    }
	    if (cs->MaxLen > 0  &&  reallen > cs->MaxLen)
	    { ShowWarn(GetString(msgLongString));
	    }
	  }
	  free(newstr);
	}
	else
	{ ShowWarn(GetString(msgNoString));
	  cs->CT_Str = "";
	}
	free(newidstr);
    }
    free(newline);
  }

  fclose(fp);

  if (WarnCTGaps)
  { for (cs = FirstCatString;  cs != NULL;  cs = cs->Next)
    { if (cs->CT_Str == NULL)
      { ShowWarn(GetString(msgCTGap), cs->ID_Str);
      }
    }
  }

  return(Result);
}





/*
    CatPuts prints a string to a catalog. (The string is preceded by a
    long integer containing its length and probably padded up to word
    boundary or longword boundary, depending on the argument padbytes.)
    The arguments countnul should be TRUE if the NUL byte at the end of
    the string should be counted.
*/
int CatPuts(FILE *fp, char *str, int padbytes, int countnul)

{ unsigned long reallen, virtuallen, chunklen;
  int bytesread;
  char *oldstr;
  char bytes[10];

  /*
      Get Length of string.
  */
  oldstr = str;
  reallen = 0;
  while (*oldstr)
  { bytesread = ReadChar(&oldstr, bytes);
    if (bytesread == 2)
    { bytesread--;
    }
    reallen += bytesread;
  }
  virtuallen = chunklen = reallen + LengthBytes;
  if (countnul  ||  chunklen % padbytes == 0)
  { virtuallen++;
  }

  fwrite(&virtuallen, sizeof(virtuallen), 1, fp);
  if (LengthBytes)
  { fwrite(((char *) &reallen)+sizeof(reallen)-LengthBytes,
	   LengthBytes, 1, fp);
  }
  while(*str)
  { bytesread = ReadChar(&str, bytes);
    if (bytesread)
    { fwrite(bytes+bytesread-1, 1, 1, fp);
    }
  }

  do
  { putc('\0', fp);
  }
  while(++chunklen % padbytes);

  return((int) chunklen+4);
}




/*
    This puts a string chunk into the catalog
*/
int PutCatalogChunk(FILE *fp, struct CatalogChunk *cc)
{ fwrite(&cc->ID, sizeof(cc->ID), 1, fp);
  return(4 + CatPuts(fp, cc->ChunkStr, 2, TRUE));
}

/*
    This creates a catalog.
*/
void CreateCat(char *CatFile)

{ FILE *fp;
  int CatLen, HeadLen;
  struct CatString *cs;
  struct CatalogChunk *cc;
  int i;

  if (!CatVersionString)
  { ShowError(GetString(msgNoCTVersion));
  }
  if (!CatLanguage)
  { ShowError(GetString(msgNoCTLanguage));
  }

  if (!(fp = fopen(CatFile, "w")))
  { ShowError(GetString(msgNoCatalog), CatFile);
  }

  fputs("FORM0000CTLG", fp);
  CatLen = 4;
  for (cc = FirstChunk;  cc != NULL;  cc = cc->Next)
  { CatLen += PutCatalogChunk(fp, cc);
  }
  i = 32;
  fputs("CSET", fp);
  fwrite(&i, sizeof(i), 1, fp);
  while(i-- > 0)
  { putc('\0', fp);
  }
  CatLen += 48;
  fprintf(fp, "STRS0000");
  HeadLen = CatLen;

  for (cs = FirstCatString;  cs != NULL;  cs = cs->Next)
  { if (strcmp(cs->CT_Str, cs->CD_Str))
    { fwrite(&cs->ID, sizeof(cs->ID), 1, fp);
      CatLen += 4 + CatPuts(fp, cs->CT_Str, 4, FALSE);
    }
  }

  fseek(fp, 4, SEEK_SET);
  fwrite(&CatLen, sizeof(CatLen), 1, fp);
  fseek(fp, HeadLen-4, SEEK_CUR);
  CatLen -= HeadLen;
  fwrite(&CatLen, sizeof(CatLen), 1, fp);
  fclose(fp);
}




/*
    This creates a new catalog translation file.
*/
void CreateCTFile(char *NewCTFile)

{ FILE *fp;
  struct CDLine *cd;
  struct CatString *cs;
  struct CatalogChunk *cc;
  char *line;

  if (!(fp = fopen(NewCTFile, "w")))
  { ShowError(GetString(msgNoNewCTFile));
  }

  fprintf(fp, "## version %s\n## language %s\n## codeset %d\n",
	  CatVersionString, CatLanguage, CodeSet);
  for (cc = FirstChunk;  cc != NULL;  cc = cc->Next)
  { if (cc->ChunkStr != CatVersionString  &&
	cc->ChunkStr != CatLanguage)
    { fprintf(fp, "## chunk ");
      fwrite((char *) &cc->ID, sizeof(cc->ID), 1, fp);
      fprintf(fp, " %s\n", cc->ChunkStr);
    }
  }

  for(cd = FirstCDLine, cs = FirstCatString;
      cd != NULL;
      cd = cd->Next)
  { switch(*cd->Line)
    { case '#':
	break;
      case ';':
	fprintf(fp, "%s\n", cd->Line);
	break;
      default:
	if(cs)
	{ fprintf(fp, "%s\n", cs->ID_Str);
	  fprintf(fp, "%s\n", cs->CT_Str ? cs->CT_Str : "");
	  putc(';', fp);
	  for (line = cs->CD_Str;  *line;  ++line)
	  { putc((int) *line, fp);
	    if(*line == '\n')
	    { putc(';', fp);
	    }
	  }
	  putc('\n', fp);
	  cs = cs->Next;
	}
    }
  }

  fclose(fp);
}




/*
    InitCatStringOutput gets called before writing a catalog string as
    source.

    Inputs: fp	 = file pointer to the output file
	    type = one of   TYPE_C	    create C strings
			    TYPE_ASSEMBLER  create Assembler strings
			    TYPE_OBERON     create Oberon strings
			    TYPE_E	    create E strings
			    TYPE_NONE	    create simple strings
*/
int OutputMode = OutputMode_None;
int OutputType = TYPE_C;
FILE *OutputFile;
int OutputLen;
void InitCatStringOutput(FILE *fp)

{
  OutputLen = 0;
  OutputFile = fp;
  OutputMode = OutputMode_None;
  switch(OutputType)
  { case TYPE_C:
    case TYPE_OBERON:
      putc('\"', fp);
      OutputMode = OutputMode_Ascii;
      break;
    case TYPE_E:
      putc('\'', fp);
    case TYPE_ASSEMBLER:
    case TYPE_NONE:
      break;
  }
}



/*
    SeparateCatStringOutput gets called to split a catalog into separate
    lines.
*/
void SeparateCatStringOutput(void)

{
    switch(OutputType)
    { case TYPE_C:
	if (!LongStrings)
	{ fputs("\"\\\n\t\"", OutputFile);
	}
	break;
      case TYPE_E:
	if (!LongStrings)
	{ fputs("\' +\n\t\'", OutputFile);
	}
	break;
      case TYPE_OBERON:
	if (!LongStrings)
	{ fputs("\"\n\t\"", OutputFile);
	}
	break;
      case TYPE_ASSEMBLER:
	if (!LongStrings)
	{ if (OutputMode == OutputMode_Ascii)
	  { putc('\'', OutputFile);
	  }
	  putc('\n', OutputFile);
	  OutputMode = OutputMode_None;
	}
	break;
      case TYPE_NONE:
	break;
    }
}




/*
    WriteBinChar writes one binary character into the source file
*/
void WriteBinChar(int c)

{
  switch(OutputType)
  { case TYPE_C:
    case TYPE_E:
    case TYPE_OBERON:
      switch(c)
      { case '\b':
	  fputs("\\b", OutputFile);
	  break;
	case '\n':
	  fputs("\\n", OutputFile);
	  break;
	case '\r':
	  fputs("\\r", OutputFile);
	  break;
	case '\t':
	  fputs("\\t", OutputFile);
	  break;
	case '\f':
	  fputs("\\f", OutputFile);
	  break;
	case '\0':
	  fputs("\\000", OutputFile);
	  break;
	default:
	  if(OutputType == TYPE_E  &&  c == '\033')
	  { fputs("\\e", OutputFile);
	  }
	  else
	  { fprintf(OutputFile, "\\%c%c%c", ((c >> 6) & 3) + '0',
		    ((c >> 3) & 7) + '0', (c & 7) + '0');
	  }
	  break;
      }
      ++OutputLen;
      OutputMode = OutputMode_Bin;
      break;
    case TYPE_ASSEMBLER:
      switch(OutputMode)
      { case OutputMode_None:
	  fprintf(OutputFile, "\tdc.b\t$%02x", c & 0xff);
	  break;
	case OutputMode_Ascii:
	  putc('\'', OutputFile);
	case OutputMode_Bin:
	  fprintf(OutputFile, ",$%02x", c & 0xff);
	  break;
      }
      ++OutputLen;
      OutputMode = OutputMode_Bin;
      break;
    case TYPE_NONE:
      ShowWarn(GetString(msgNoBinChars));
      break;
  }
}




/*
    WriteAsciiChar writes one ascii character into the source file.
*/
void WriteAsciiChar(int c)

{
  switch(OutputType)
  { case TYPE_C:
    case TYPE_OBERON:
      switch(c)
      { case '\"':
	  fputs("\\\"", OutputFile);
	  break;
	default:
	  putc(c, OutputFile);
	  break;
      }
      ++OutputLen;
      OutputMode = OutputMode_Ascii;
      break;
    case TYPE_E:
      switch(c)
      { case '\'':
	  fputs("''", OutputFile);
	  break;
	default:
	  putc(c, OutputFile);
	  break;
      }
      ++OutputLen;
      OutputMode = OutputMode_Ascii;
      break;
    case TYPE_ASSEMBLER:
      if (c == '\'')
      { WriteBinChar(c);
      }
      else
      { switch (OutputMode)
	{ case OutputMode_None:
	    fprintf(OutputFile, "\tdc.b\t\'%c", c);
	    break;
	  case OutputMode_Ascii:
	    putc(c, OutputFile);
	    break;
	  case OutputMode_Bin:
	    fprintf(OutputFile, ",\'%c", c);
	    break;
	}
	++OutputLen;
	OutputMode = OutputMode_Ascii;
      }
      break;
    case TYPE_NONE:
      putc(c, OutputFile);
      break;
  }
}




/*
    TerminateCatStringOutput finishs the output of a catalog string.
*/
void TerminateCatStringOutput(void)

{
  switch(OutputType)
  { case TYPE_C:
    case TYPE_OBERON:
      putc('\"', OutputFile);
      break;
    case TYPE_E:
      putc('\'', OutputFile);
      break;
    case TYPE_ASSEMBLER:
      switch(OutputMode)
      { case OutputMode_Ascii:
	  putc('\'', OutputFile);
	case OutputMode_Bin:
	  break;
	case OutputMode_None:
	  break;
      }
    case TYPE_NONE:
      break;
  }
}




/*
    This writes a sourcestring.
*/
void WriteString(FILE *fpout, char *str, long Len)

{ char bytes[10];
  int bytesread;
  int needseparate = FALSE;

  InitCatStringOutput(fpout);
  if (Len >= 0)
  { int i;

    for(i = LengthBytes;  i >= 1;  i--)
    { WriteBinChar((int) ((char *) &Len)[sizeof(Len)-i]);
    }
  }

  while (*str)
  { bytesread = ReadChar(&str, bytes);
    if (bytesread)
    { unsigned char c;

      if (needseparate)
      { SeparateCatStringOutput();
	needseparate = FALSE;
      }

      c = bytes[bytesread-1];
      if ((c >= 0x20  &&  c < 0x7f)  ||  c >= 0xa0)
      { WriteAsciiChar((int) c);
      }
      else
      { WriteBinChar((int) c);
      }
    }
    else
    { needseparate = TRUE;
    }
  }
  TerminateCatStringOutput();
}




/*
    This function creates a copy of a filename, removes an
    optional ending and pathname components, if desired.

    Inputs: filename - the filename to copy
	    howto - a set of bits
			bit 0: 1 = remove ending, 0 = leave it
			bit 1: 1 = remove pathname, 0 = leave it

    Result: The copy of the filename
*/
char *AllocFileName(char *filename, int howto)

{ char *tempstr, *ptr;

  if (!(tempstr = strdup(filename)))
  { MemError();
    exit(10);
  }

  /*  Remove pathname components, if desired	*/
  if (howto & 2)
  { if ((ptr = strchr(tempstr, ':')))
    { tempstr = ptr+1;
    }
    if ((ptr = strrchr(tempstr, '/')))
    { tempstr = ptr+1;
    }
  }

  /*  Remove ending, if desired.		*/
  if (howto & 1)
  { if ((ptr = strrchr(tempstr, '.')))
    { *ptr = '\0';
    }
  }

  return(tempstr);
}





/*
    This function adds a pathname and a filename to a full
    filename.

    Inputs: pathname - the leading pathname
	    filename - the filename

    Result: The new filename
*/
char *AddFileName(char *pathname, char *filename)

{ char *buffer;
  int size = strlen(pathname) + strlen(filename) + 2;

  if (!(buffer = malloc(size)))
  { MemError();
    exit(10);
  }

#if defined(__amigados)
  strcpy(buffer, pathname);
  AddPart((STRPTR) buffer, (STRPTR) filename, size);
#else
  sprintf(buffer, "%s/%s", pathname, filename);
#endif

  return(buffer);
}




/*
    Finally the source creation.
*/
void CreateSourceFile(char *SourceFile, char *TemplateFile,
		      char *CDFile)

{ FILE *fpin, *fpout;
  char *line;
  char *OrigTemplateFile = TemplateFile;

  /*
    Open the source file. This may be found in various places
  */
  if (!(fpin = fopen(TemplateFile, "r")))
  { char *sddir;

    if ((sddir = getenv(FLEXCAT_SDDIR)))
    { TemplateFile = AddFileName(sddir, OrigTemplateFile);
      fpin = fopen(TemplateFile, "r");
    }
  }

  if (!fpin)
  { TemplateFile = AddFileName(DEFAULT_FLEXCAT_SDDIR, OrigTemplateFile);
    fpin = fopen(TemplateFile, "r");
  }

  if (!fpin)
    {
      ShowError(GetString(msgNoSourceDescription), OrigTemplateFile);
      return;
    }

  if (!(fpout = fopen(SourceFile, "w")))
  {
    ShowError(GetString(msgNoSource), SourceFile);
    return;
  }

  while(!feof(fpin)  &&  (line = ReadLine(fpin, FALSE)))
  { struct CatString *cs;
    int NeedRepeat;
    char bytes[10];
    int bytesread;

    cs = FirstCatString;
    do
    { char *currentline = line;
      NeedRepeat = FALSE;

      if (*currentline == '#'  &&  *(++currentline) == '#')
      { ++currentline;
	OverSpace(&currentline);
	if (strnicmp(currentline, "stringtype", 10) == 0)
	{ currentline += 10;
	  OverSpace(&currentline);
	  if (strnicmp(currentline, "c", 1) == 0)
	  { OutputType = TYPE_C;
	    ++currentline;
	  }
	  else if (strnicmp(currentline, "assembler", 9) == 0)
	  { OutputType = TYPE_ASSEMBLER;
	    currentline += 9;
	  }
	  else if (strnicmp(currentline, "oberon", 6) == 0)
	  { OutputType = TYPE_OBERON;
	    currentline += 6;
	  }
	  else if (strnicmp(currentline, "e", 1)  ==  0)
	  { OutputType = TYPE_E;
	    ++currentline;
	  }
	  else if (strnicmp(currentline, "none", 4)  ==  0)
	  { OutputType = TYPE_NONE;
	    currentline += 4;
	  }
	  else
	  { ShowWarn(GetString(msgUnknownStringType));
	    currentline += strlen(currentline);
	  }
	  OverSpace(&currentline);
	  if (*currentline)
	  { ShowWarn(GetString(msgExtraCharacters));
	  }
	  continue;
	}
	else if (strnicmp(currentline, "shortstrings", 12) == 0)
	{ currentline += 12;
	  LongStrings = FALSE;
	  OverSpace(&currentline);
	  if (*currentline)
	  { ShowWarn(GetString(msgExtraCharacters));
	  }
	  continue;
	}
      }

      currentline = line;
      while(*currentline)
      { bytesread = ReadChar(&currentline, bytes);
	if (bytesread)
	{ if (*bytes == '%')
	  { char c;

	    switch(c = *(currentline++))
	    { case 'b':
		fputs(BaseName, fpout);
		break;
	      case 'n':
		fprintf(fpout, "%d", NumStrings);
		break;
	      case 'v':
		fprintf(fpout, "%d", CatVersion);
		break;
	      case 'l':
		WriteString(fpout, Language, -1);
		break;
	      case 'f':
		{ char *tempstr;

		  if ((c = *currentline++) == 'v')
		  { fputs(VERS, fpout);
		  }
		  else
		  { tempstr = AllocFileName(CDFile, c - '0');
		    fputs(tempstr, fpout);
		  }
		}
		break;
	      case 'o':
		{ char *tempstr;

		  tempstr = AllocFileName(SourceFile, *currentline++ - '0');
		  fputs(tempstr, fpout);
		}
		break;
	      case 'i':
		NeedRepeat = TRUE;
		if (cs) fputs(cs->ID_Str, fpout);
		break;
	      case 'd':
	      case 'x':
	      case 'c':
	      case '0':
	      case '1':
	      case '2':
	      case '3':
	      case '4':
	      case '5':
	      case '6':
	      case '7':
	      case '8':
	      case '9':
		{ int len = 0;
		  char buffer[20];

		  while(c >= '0'  &&  c <= '9')
		  { len = (c - '0') + len * 10;
		    c = *currentline++;
		  }

		  if (c == 'c')
		  { c = 'o';
		  }

		  if (len)
		  { sprintf(buffer, "%%0%d%c", len, c);
		  }
		  else
		  { sprintf(buffer, "%%%c", c);
		  }

		  if (cs) fprintf(fpout, buffer, cs->ID);

		  NeedRepeat = TRUE;
		}
		break;
	      case 'e':
		NeedRepeat = TRUE;
		if (cs) fprintf(fpout, "%d", cs->Nr);
		break;
	      case 's':
		NeedRepeat = TRUE;
		if (cs)
		{ char *idstr;
		  unsigned long len = 0;

		  if (LengthBytes)
		  { idstr = cs->CD_Str;
		    while(*idstr)
		    { bytesread = ReadChar(&idstr, bytes);
		      if (bytesread)
		      { ++len;
		      }
		    }
		  }
		  WriteString(fpout, cs->CD_Str, LengthBytes ? len : -1);
		}
		break;
	      case '(':
		NeedRepeat = TRUE;
		while(*currentline  &&  *currentline != ')')
		{ bytesread = ReadChar(&currentline, bytes);
		  if (bytesread  &&  cs  &&  cs->Next)
		  { putc((int) bytes[bytesread-1], fpout);
		  }
		}
		if (!*currentline)
		{ ShowWarn(GetString(msgNoTerminateBracket));
		}
		else
		{ ++currentline;
		}
		break;
	      default:
		{ int c = *currentline++;

		  putc(c, fpout);
		}
	    }
	  }
	  else
	  { putc((int) bytes[bytesread-1], fpout);
	  }
	}
      }
      putc('\n', fpout);
    }
    while(NeedRepeat  &&  cs  &&  (cs = cs->Next));

    free(line);
  }

  fclose(fpin);
  fclose(fpout);
}




/*
    The Usage function describes the programs calling syntax.
*/
void Usage(void)

{
  fputs((char *) GetString(msgUsage), stderr);
  fprintf(stderr, "\n\n%s\n", VString);
  exit(5);
}

/*
    Finally the main function. Does nothing special except for scanning
    the arguments.
*/
int main (int argc, char *argv [])

{ char *cdfile, *ctfile, *newctfile, *catalog;
  char *source, *template;
  int i;

  if (argc == 0)    /*  Aztec's entry point for workbench programs  */
  { exit(5);
  }

  cdfile = ctfile = newctfile = catalog = NULL;

  if (argc == 1)
  { Usage();
  }

  for (i = 1;  i < argc;  i++)
  { if (strnicmp (argv[i], "catalog=", 8) == 0)
    { catalog = argv[i] + 8;
    }
    else if (stricmp (argv[i], "catalog") == 0)
    { if (i+1 == argc)
      { Usage();
      }
      catalog = argv[++i];
    }
    else if (strnicmp (argv[i], "newctfile=", 10) == 0)
    { newctfile = argv[i] + 10;
    }
    else if (strnicmp (argv[i], "newctfile", 10) == 0)
    { if (i+1 == argc)
      { Usage();
      }
      newctfile = argv[++i];
    }
    else if (stricmp(argv[i], "warnctgaps") == 0)
    { WarnCTGaps = TRUE;
    }
    else if (cdfile == NULL)
    { if (stricmp(argv[i], "?") == 0  ||  stricmp(argv[i], "-h") == 0  ||
	  stricmp(argv[i], "help") == 0)
      { Usage();
      }
      if (!ScanCDFile(cdfile = argv[i]))
      { exit(10);
      }
    }
    else if (strchr(argv[i], '='))
    { source = AllocString(argv[i]);
      *(template = strchr(source, '=')) = '\0';
      ++template;

      CreateSourceFile(source, template, cdfile);
    }
    else
    { if (ctfile)
      { Usage();
      }
      ctfile = argv[i];
    }
  }

  if (ctfile)
  { if(!ScanCTFile(ctfile))
    { exit(10);
    }
  }
  if (catalog)
  { if (!ctfile)
    { fprintf(stderr, (char *) GetString(msgNoCTArgument));
      Usage();
    }
    CreateCat(catalog);
  }
  if (newctfile)
  { CreateCTFile(newctfile);
  }

  exit(GlobalReturnCode);
}




/*
    Dice's entry point for workbench programs
*/
#if defined(__amigados)  &&  defined(_DCC)
void wbmain(struct WBStartup *wbmsg)

{ exit(5);
}
#endif
