/* Functions that emulate UNIX catgets */

/* Copyright (C) 1999,2000,2001 Jim Hall <jhall@freedos.org> */
/* Kitten version 2003 by Tom Ehlert, heavily modified by Eric Auer 2003 */
/* Port to SPHINX/Sheker C-- by CyberRax <cyberrax@yahoo.com> '2004 */
/*
  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library 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
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
/*
   Usage:   KITTENOPEN(catalog)
	    KITTENGETS(set,message,defaultmsg)
	    KITTENCLOSE()

   Definitions affecting this code:

      KITTENBUFLEN - size of buffer for message catalog. Default value is
		     4096 bytes.
      KITTEN_NUM   - maximum number of messages. By default 64.

   These variables, if used, must be defined *before* including this
   headerfile.

   See KITTEN.TXT for a more complete description.
*/

? ifndef _KITTEN_
? define _KITTEN_		20040710


? compilerversion 0.239	

? warning			FALSE


? ifndef _NUMBERS_
  ? include "NUMBERS.H--"
? endif

? ifndef _STRING2_
  ? include "STRING2.H--"
? endif
? ifdef _STRING2_ < 20040705
  ? print error "STRING2.H-- too old, release 20040705 or newer is needed   :(\n"
? endif

? ifndef _STRING_
  ? include "STRING.H--"
? endif

? ifndef _BYTES_
  ? include "BYTES.H--"
? endif
? ifdef _BYTES_ < 20040706
  ? print error "BYTES.H-- too old, release 20040706 or newer is needed   :(\n"
? endif

? ifndef _DOS_
  ? include "DOS.H--"
? endif


? ifndef	KITTEN_NUM
  ? define	KITTEN_NUM	size64
? endif

? ifndef	KITTENBUFLEN
  ? define	KITTENBUFLEN	size4kB
? endif


? ifdef codesize
  ? define	RETURN_I	RETURN
  ? define	WHILE_I		WHILE
  ? define	BREAK_I		BREAK
  ? define	IF_I		IF
? else
  ? define	RETURN_I	return
  ? define	WHILE_I		while
  ? define	BREAK_I		break
  ? define	IF_I		if
? endif


struct KITTENSTRING
{
   byte SetNum;
   byte MsgNum;
   word Msg;
};


byte KITTENLANG[THREE];
KITTENSTRING KITTENPOINTS[KITTEN_NUM+ONE];
byte KITTENCONTENT[KITTENBUFLEN+ONE], KITTENFILE[size128];

: byte KITTEN_CATALOG = NONE;


inline word KITTENGETS (BL, BH, CX)
/* Returns a string from message catalog buffer.
   Note:   is meant to be used after KITTENOPEN()
   Input:  BL = set number
	   BH = message number
	   CX = default message
   Output: AX = (new) message
	   BX, CX, DX, SI destroyed
   Uses:   LOAD_AX(), AX2CX() and DX2AX() from BYTES.H--
	   0 - 2 bytes of stack
   Size:   min. 25 bytes
*/
{
SI = #KITTENPOINTS;
do {
   @LOAD_AX();				// Get set and message numbers...
   @DX2AX();				// ...into DX
   @LOAD_AX();				// Get address of string
   IF (AX) && (DX == BX)		// If addr. present and numbers match
      RETURN AX;			// ...return addr.
   } while (DX) || (AX);		// Loop while there's data
@AX2CX();				// Else return the default string
}



inline void PROCESSESCCHARS (SI)
/* Processes strings, converting \n, \t, \v, \b, \r, \f, \\, \ddd, \xdd to
      actual chars.
   Used internally by KITTENREAD()
   Input:  DS:SI = string
	   'insertSTR2WORD' definied to insert not call STR2WORD()
	   'insertCHARMULTIPLY' definied to insert CHARMULTIPLY()
   Output: nothing
	   AX, BX, CX, DX, SI, DI destroyed
	   ES = DS
   Uses:   LOAD_AL() and STORE_AL() from BYTES.H--
	   STR2WORD() and CHARMULTIPLY() from STRING2.H--
	   4 - 12 bytes of stack
   Size:   min. 215 bytes
*/
{
ES = DS;
DI = SI;

// Cycle through copying characters, except when a '\' is encountered.
WHILE_I (DSBYTE[SI])
   {
   IF_I (@LOAD_AL() == '\\')
      {
      // What follows slash?
      @LOAD_AL();
      SWITCH (AL)
	 {
	 // A single slash
	 CASE '\\':
	   ? ifdef codesize
	     GOTO STORE_OUR_AL;
	   ? else
	     @STORE_AL();
	     BREAK_I;
	   ? endif

	 // A newline (linefeed) or carriage return
	 CASE 'n':
	 CASE 'r':
	    DSWORD[DI] = 0x0A0D;
	    ? ifdef speed
	      DI += TWO;
	      BREAK_I;
	    ? else
	      DI++;
	      GOTO END_SWITCH;
	    ? endif

	 // A horizontal tab
	 CASE 't':
	    ? ifdef codesize
	      AL = '\t';
	      GOTO STORE_OUR_AL;
	    ? else
	      DSBYTE[DI] = '\t';
	      DI++;
	      BREAK;
	    ? endif

	 // A vertical tab
	 CASE 'v':
	    ? ifdef codesize
	      AL = '\v';
	      GOTO STORE_OUR_AL;
	    ? else
	      DSBYTE[DI] = '\v';
	      DI++;
	      BREAK;
	    ? endif

	 // A backspace
	 CASE 'b':
	    ? ifdef codesize
	      AL = '\b';
	      GOTO STORE_OUR_AL;
	    ? else
	      DSBYTE[DI] = '\b';
	      DI++;
	      BREAK;
	    ? endif

	 // Alert
	 CASE 'a':
	    ? ifdef codesize
	      AL = '\a';
	      GOTO STORE_OUR_AL;
	    ? else
	      DSBYTE[DI] = '\a';
	      DI++;
	      BREAK;
	    ? endif

	 // Formfeed
	 CASE 'f':
	    ? ifdef codesize
	      AL = '\f';
	      GOTO STORE_OUR_AL;
	    ? else
	      DSBYTE[DI] = '\f';
	      DI++;
	      BREAK;
	    ? endif

	 // C style hex numbers, in the form '\x??'
	 CASE 'x':
	    ? ifdef codesize
	      AH = NULL;
	      CL = SIXTEEN;
	      GOTO CONVERT_NUMBER;
	    ? else
	      $PUSH SI,DI
	      STR2WORD(SI,SIXTEEN,TWO);
	      $POP DI,SI

	      // Succesfully converted
	      IF (!AX)
		 {
		 DSBYTE[DI] = DL;
		 SI += TWO;
		 }
	      // Otherwise just store the 'x' without the slash
	      ELSE
		 DSBYTE[DI] = 'x';
	      DI++;
	      BREAK;
	    ? endif


	 // Assume first that we're dealing with C style dec numbers ('\???')
	 default:
	    ? ifdef codesize
	      AH = ONE;
	    CONVERT_NUMBER:
	    ? endif

	    $PUSH AX,SI,DI

	    ? ifdef codesize
	      DI = TWO;
	      IF (AH)
		 {
		 SI--;
	         DI++;
		 CL = TEN;
		 }
	    ? else
	      DI = THREE;
	      SI--;
	      CL = TEN;
	    ? endif

	    ? ifdef insertSTR2WORD
	      @
	    ? endif
	    STR2WORD(SI,CL,DI);
	    $POP DI,SI,BX
	    IF (!AX)
	       {
	       DSBYTE[DI] = DL;
	       SI += TWO;
	       }
	    ELSE
	       DSBYTE[DI] = BL;
	   END_SWITCH:
	    DI++;
         }
      }
   ELSE
      {
     STORE_OUR_AL:
      @STORE_AL();
      }
   }

// Make sure we'll get an ASCIZ string
DSBYTE[DI] = NULL;
}



: byte KITTENREAD (DX)
/* Loads message catalog into memory.
   Used by KITTENOPEN()
   Input:  DS:DX = filename (of message catalog)
	   'insertFOPEN' definied to insert FOPEN() instead of calling
	   'insertFREAD' definied to insert FREAD()
	   'insertFCLOSE' definied to insert FCLOSE()
	   'insertSTR2WORD' definied to insert STR2WORD()
	   'insertCHARMULTIPLY' definied to insert CHARMULTIPLY()
   Output: AL <> 0 if successful
	   AL = 0 if error occoured
	   BX, CX, DX, SI, DI destroyed
   Uses:   PROCESSESCCHARS() from KITTEN.H--
	   FOPEN(), FREAD() and FCLOSE() from internal library
	   LOAD_AL(), STORE(), STORE_AL(), BX2AX(), DX2AX() and SI2AX()
	      from BYTES.H--
	   STRCHR() from STRING.H--
	   ASCIZ2WORD(), STR2WORD() and CHARMULTIPLY() from STRING2.H--
	   min. 8 bytes of heap
	   12 - 16 bytes of stack
   Size:   min. 514 bytes
*/
word KITTENpointer, KITTENnextlinepointer, KITTENmsg;
word KITTENPOINTmarker;
{
IF (! ? ifdef insertFOPEN @ ? endif FOPEN(NULL,,,DX))
					// If file opening fails...
   RETURN AL;				// ...return BAD
@BX2AX();				// Filehandle into BX
					// Clear index and message buffers
STORE(#KITTENPOINTS,NULL,KITTEN_NUM+ONE*FOUR+KITTENBUFLEN+ONE);

? ifdef insertFREAD
  @
? endif
FREAD(,BX,KITTENBUFLEN,#KITTENCONTENT);	// Read file into buffer
@DX2AX();				// Number of read bytes into DX
? ifdef insertFCLOSE
  @
? endif
FCLOSE(,BX);				// Close file

IF (!DX) || (DX == CX)			// If read failed or file's too big...
   RETURN BAD;				// ...complain

KITTENpointer = #KITTENCONTENT;
KITTENnextlinepointer = #KITTENCONTENT;
KITTENPOINTmarker = #KITTENPOINTS;

POINTER_FILL_LOOP:
IF (!STRCHR(KITTENpointer,'\l'))	// No more lines to process...
   RETURN 0xFF;				// ...which means that we're done

// Address of '\l' was returned by STRCHR() in AX, put it into BX
@BX2AX();
DSBYTE[BX] = NULL;			// Terminate line
BX--;					// Guess: \r before \n
IF (DSBYTE[BX] <> '\r')			// If not...
   BX++;				// ...go back
ELSE
   {
   DSBYTE[BX] = NULL;			// Terminate here already
   BX++;
   }
BX++;
KITTENnextlinepointer = BX;		// That's where the next line begins

// Skip lines which don't start with a digit or don't include a ':'
BX = KITTENpointer;
IF_I (DSBYTE[BX] >= '0') && (DSBYTE[BX] <= '9') && (STRCHR(BX,':'))
   {
   @BX2AX();
   DSBYTE[BX] = NULL;
   BX++;
   KITTENmsg = BX;

   IF_I (STRCHR(KITTENpointer,'.'))
      {
      @SI2AX();
      DSBYTE[SI] = NULL;		// Terminate at '.'
      SI++;
      IF_I (!ASCIZ2WORD(SI,TEN))		// If conversion successful...
	 {
	 $PUSH DX			// ...save message number
	 ASCIZ2WORD(KITTENpointer,TEN); // And convert set number
	 $POP CX			// Get MsgNum back
	 IF_I (!AX)			// If also set num was OK...
	    {
	    BX = KITTENPOINTmarker;	// ...save both into POINTS buffer
	    BX.KITTENSTRING.MsgNum = CL;
	    BX.KITTENSTRING.SetNum = DL;

	    $PUSH BX			// Save place in buffer
	    PROCESSESCCHARS(KITTENmsg);
	    $POP BX

	    // Handle an empty string
	    SI = KITTENmsg;
	    IF (!DSBYTE[SI])
	       SI = NULL;
	    BX.KITTENSTRING.Msg = SI;

	    KITTENPOINTmarker += FOUR;
	    }
	 }
      }
   }
KITTENpointer = KITTENnextlinepointer;
GOTO POINTER_FILL_LOOP;
}



inline void KITTENSTRCAT (SI)
/* Appends an ASCIZ string to another one.
   Simple macro to get smaller or faster code by STRCAT()
   Input:  DS:SI = string
	   'insertSTRCAT' definied to insert STRCAT() instead of calling
   Output: nothing
	   AL, SI, DI destoyed
	   ES = DS
   Uses:   STRCAT() from STRING.H--
	   0 - 2 bytes of stack
   Size:   min. 18 bytes
*/
{
? ifdef codesize
  ? ifdef insertSTRCAT
    @
  ? endif
? else
  @
? endif
STRCAT(,,,,#KITTENFILE);
}



inline byte KITTENOPEN (DX)
/* Opens message catalog
   Input:  DS:DX = catalog name
	   'insertADVANCEDSTRCPYFAR' definied to insert ADVANCEDSTRCPYFAR()
	      instead of calling
	   'insertADVANCEDSTRCPYNEAR' definied to insert ADVANCEDSTRCPYNEAR()
	   'insertFOPEN' definied to insert FOPEN()
	   'insertFREAD' definied to insert FREAD()
	   'insertFCLOSE' definied to insert FCLOSE()
	   'insertSTRCAT' definied to insert STRCAT()
	   'insertSTR2WORD' definied to insert STR2WORD()
	   'insertCHARMULTIPLY' definied to insert CHARMULTIPLY()
   Output: AL <> 0 if catalog successfully opened and parsed
	   AL = 0 if error occoured (open or parse error or a catalog already
		  open)
	   BX, CX, DX, SI, DI, ES destroyed
   Uses:   LOAD_AL(), LOAD_AX(), STORE_AX() and STORE() from BYTES.H--
	   STRCAT() and STRCHR() from STRING.H--
	   ENVSTR() from DOS.H--
	   ADVANCEDSTRCPYFROMFAR() and ADVANCEDSTRCPYNEAR() from STRING2.H--
	   KITTENREAD(), KITTENSTRCAT() and PROCESSESCCHARS() from KITTEN.H--
	   min. 9 bytes of heap
	   12 - 18 bytes of stack
   Size:   min. 907 bytes
*/
word KittenName, NLSPATHseg, NLSPATHofs, NLSmarker;
byte NLSpart;
{
// Quit if a catalog already opened
IF (KITTEN_CATALOG)
   {
   ? ifdef codesize
     GOTO RETURN_BAD;
   ? else
     RETURN_I BAD;
   ? endif
   }

KittenName = DX;

// Clear LANG and KITTENPOINTS buffers
STORE(#KITTENLANG,NULL,KITTEN_NUM+ONE*FOUR+THREE);

// If KittenName contains '\' then it's a filename
// So try to open/parse it. If this fails whine.
IF (STRCHR(DX,'\\')) && (KITTEN_CATALOG = KITTENREAD(DX))
   return AL;

// If LANG variable not set then complain
ENVSTR("LANG");
IF (!ESBYTE[DI])
   {
  RETURN_BAD:
   return BAD;
   }

// Copy content of LANG
BX = DS;
DS = ES;
ES = BX;
SI = DI;
DI = #KITTENLANG;
// DS:SI = LANG
// ES:DI = buffer

@LOAD_AX();
@STORE_AX();
@LOAD_AL();

// If AH empty then the string was only 1 character long
IF (!AH)
   {
   ? ifdef codesize
     GOTO RETURN_FF;
   ? else
     RETURN_I -1;
   ? endif
   }
// If the string is longer than 2 chars and the third (now in AL) is other
//   than '-' then we've got a non-standard value in LANG
IF (AL) && (AL <> '-')
   {
  RETURN_FF:
   return -1;
   }

DS = BX;

ENVSTR("NLSPATH");
IF (!ESBYTE[DI])
   {
   ? ifdef codesize
     GOTO RETURN_BAD;
   ? else
     RETURN_I BAD;
   ? endif
   }

NLSPATHseg = ES;
NLSPATHofs = DI;

do {
   // Copy dir into buffer
   ? ifdef insertADVANCEDSTRCPYFAR
     @
   ? endif
   ADVANCEDSTRCPYFAR(DS,#KITTENFILE,NLSPATHseg,NLSPATHofs,';');

   // SI is expected to hold the adress of first byte after ';'
   // DI on the other hand is #KITTENFILE+STRLEN(#KITTENFILE)+1
   // DL has the terminator, ie 1 if string ended with ';', 0 otherwise
   NLSPATHofs = SI;
   DI--;
   NLSmarker = DI;
   NLSpart = DL;

   // Rule #1: %NLSPATH%\%LANG%\cat

   // If last char of dir variable is not '\' then append that.
   IF (DSBYTE[DI-ONE] <> '\\')
      KITTENSTRCAT("\\");
   KITTENSTRCAT(#KITTENLANG);
   KITTENSTRCAT("\\");
   KITTENSTRCAT(KittenName);
   IF (KITTEN_CATALOG = KITTENREAD(#KITTENFILE))
      RETURN_I AL;

   // Rule #2: %NLSPATH%\cat.%LANG%

   BX = NLSmarker;
   DSBYTE[BX] = NULL;
   IF (DSBYTE[BX-ONE] <> '\\')
      KITTENSTRCAT("\\");
   KITTENSTRCAT(KittenName);
   KITTENSTRCAT(".");
   KITTENSTRCAT(#KITTENLANG);
   IF (KITTEN_CATALOG = KITTENREAD(#KITTENFILE))
      RETURN AL;
   } while (NLSpart);

RETURN BAD;
}



inline void KITTENCLOSE ()
/* Closes message catalog.
   Input:  nothing
   Output: nothing
   Size:   min. 5 bytes
*/
{
KITTEN_CATALOG = EMPTY;
}


? endif


/* end of KITTEN.H-- */