/*
 * The routine encode_string_to_label translates a string to a
 * valid identifier. Essentially, alpha-numeric characters
 * appear un-translated in the output, while other characters
 * appear as a hex encoding. A reserved character (usually '$')
 * is used to "shift" in and out of hex mode.
 * The routine decode_label_to_string does the inverse translation.
 */

/*
 * A package named 'foo' uses the label '_$PKfoo'.
 * A keyword ':foo' uses the label '_$KWfoo'.
 * A symbol 'pack:foo' ('foo' in package 'pack') use the label '_$SYpack$$foo'.
 */

#define SymHexDelim '_' /* '$' or '.' or '_' */
#include <ctype.h>
#if SymHexDelim=='_'
#define SymOkChar(ch) isalnum(ch)
#define InitOkChar(ch) isalpha(ch)
#else
#define SymOkChar(ch) (isalnum(ch) || (ch) == '_')
#define InitOkChar(ch) (isalpha(ch) || (ch) == '_')
#endif

char HexToChar[17] = "0123456789ABCDEF";

/* Encode string 'str' (with given 'length') as an "alphanumeric" label.
 * Write encoding into 'buf'.
 * Return the number of written characters.
 * The number of characters written is bounded by (2*length+2). ????
 */

int encode_string_to_label(register char *str, int length, char *buf)
{
    register char *p = buf;
    int hex_mode = 0;
    int first_one = 1;
    if (length <= 0) {
	hex_mode = 1;
	*p++ = SymHexDelim;
    }
    else {
	for ( ; --length >= 0; ) {
	    unsigned char ch = (unsigned char)*str++;
	    if (first_one ? (first_one = 0, InitOkChar(ch)) : SymOkChar(ch)) {
		/* Avoid shifting out and back into hex mode for a single
		 * character. That might have made the output 2.5 the
		 * length of the input in the worst case. */
		if (!hex_mode || length == 0 || SymOkChar(str[0])) {
		    if (hex_mode) { *p++ = SymHexDelim; hex_mode = 0; }
		    *p++ = ch;
		    continue;
		}
	    }
	    if (!hex_mode) {
		*p++ = SymHexDelim;
		if (ch == SymHexDelim) { *p++ = SymHexDelim; continue; }
		else hex_mode = 1;
	    }
	    if (ch == '-') *p++ = '_';
	    else {
		*p++ = HexToChar[ch >> 4];
		*p++ = HexToChar[ch & 15];
	    }
	}
    }
    /* Always end up in non-hex mode. That way we can use
       'SynHexDelim SymHexDelim' to mean: end of sub-string. */
    if (hex_mode) {
	*p++ = SymHexDelim;
    }
    *p = '\0';
    return p - buf;
}

/* Do the inverse of 'encode_string_to_label'. */

int decode_label_to_string(register char *str, char *buf)
{
    register char *p = buf;
    int hex_mode = 0;
    char *last_hex_delim = 0; /* points just AFTER most recent SymHexDelim */
    for ( ; ; ) {
	unsigned char ch = (unsigned char)*str++;
	if (ch == SymHexDelim) {
	    if (!hex_mode && str[0] == SymHexDelim)
		*p++ = *str++;
#if 0
	    if (last_hex_delim == str - 1)
		break;
	    last_hex_delim = str;
#endif
	    else
		hex_mode = 1 - hex_mode;
	}
	else if (!SymOkChar(ch))
	    break;
	else if (hex_mode == 0)
	    *p++ = ch;
	else if (ch == '_')
	    *p++ = '-';
	else {
	    int val;
	    if (ch >= 'A' && ch <= 'F') val = 10 + ch - 'A';
	    else if (ch >= '0' && ch <= '9') val = ch - '0';
	    else break;
	    val <<= 4;
	    ch = (unsigned char)*str++;
	    if (ch >= 'A' && ch <= 'F') val += 10 + ch - 'A';
	    else if (ch >= '0' && ch <= '9') val += ch - '0';
	    else return -1;
	    *p++ = val;
	}
    }
    *p = '\0';
    return p - buf;
}
