/*
 * ascii.c -- quick crossreference for ASCII character aliases
 *
 * Tries to interpret arguments as names or aliases of ascii characters
 * and dumps out *all* the aliases of each. Accepts literal characters,
 * standard mnemonics, C-style backslash escapes, caret notation for control
 * characters, numbers in hex/decimal/octal/binary, English names.
 *
 * The slang names used are selected from the 2.2 version of the USENET ascii
 * pronunciation guide.  Some additional ones were merged in from the Jargon
 * File.
 *
 * For license terms, see the file COPYING.
 * SPDX-License-Identifier: BSD-2-Clause
 */

#ifndef S_SPLINT_S
#include <unistd.h>
#include <ctype.h>
#endif /* S_SPLINT_S */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <getopt.h>

#ifdef __CYGWIN__
#undef stricmp
#endif /* __CYGWIN__ */

typedef char	*string;

static bool terse = false;
static bool help = false;
static bool line = false;

enum { decimal, hex, octal, binary } mode;

void print_table(unsigned short delivery, bool vertical);

static string cnames[128][16] =
{
    {
#include "nametable.h"
    }
};

static int atob(str)
/* ASCII-to-binary conversion */
char	*str;
{
    int	val;

    for (val = 0; *str != '\0'; str++)
    {
	val *= 2;

	if (*str == '1')
	    val++;
        else if (*str != '0')
	    return(-1);
    }
    return(val);
}

void 
showHelp(FILE *out, char *progname) 
{
  fprintf(out,"Usage: %s [-adxohv] [-t] [char-alias...]\n", progname); 
#define P(s)	(void)fputs(s "\n", out);
#include "splashscreen.h"
#undef P

  exit(0);
}

static void process_options(int argc, char *argv[], char * opt_string) 
{
    int op;
    bool vertical = false;

    while( (op=getopt(argc, argv, opt_string)) != -1) {
	switch (op) {
	case 'a':
	    vertical = true;
	    break;
	case 't':
	    terse = true; 
	    break;
	case 's':
	    terse = true; 
	    line = true; 
	    break;
	case 'd':
            print_table(decimal, vertical);
            exit(0);
	case 'x':
            print_table(hex, vertical);
            exit(0);
	case 'o':
            print_table(octal, vertical);
            exit(0);
	case 'b':
            print_table(binary, vertical);
            exit(0);
	case '?':
	case 'h':
	    help = true;
	    break;
	case 'v':
	    printf("ascii %3.2f\n",REVISION);
	    exit(0);
	default :
	    help = true;
	    break;
        } /*switch*/
    }/*while*/
}

static char *btoa(unsigned int val)
/* binary-to-ASCII conversion */
{
#define BITSPERCHAR	8
    char	*rp;
    static char	rep[BITSPERCHAR + 1];

    /* write out external representation at least one char */
    *(rp = rep + BITSPERCHAR) = '\0';
    do {
	*--rp = (char)('0' + (val & 1)); /* Is '1' == '0' + 1 in EBCDIC? */
       val /= 2;
    } while
	(val != 0 && rp > rep);

#ifndef SHORT_BINARY_REPRESENTATION
    while (rp > rep)
       *--rp = '0';
#endif
 
    return(rp);
}

static void speak(unsigned int ch)
/* list all the names for a given character */
{
    char	**ptr = &cnames[ch][0];

    if (terse) {
        (void) printf("%u/%u   %u   0x%02X   0o%o   %s\n", 
		      ch / 16, ch % 16, ch, ch, ch, btoa(ch));
        return;
    }

    (void) printf(
	"ASCII %u/%u is decimal %03u, hex %02x, octal %03o, bits %s: ",
	ch / 16, ch % 16, ch, ch, ch, btoa(ch));

    /* display high-half characters */
    if (ch & 0x80)
    {
	ch &=~ 0x80;
	if (ch == 0x7f)
	    (void) printf("meta-^?\n");
	else if (isprint((int)ch))
	    (void) printf("meta-%c\n", (char)ch);
	else
	    (void) printf("meta-^%c\n", '@' + (ch & 0x1f));
	return;
    }

    if (isprint((int)ch))
	(void) printf("prints as `%s'\n", *ptr++);
    else if (iscntrl((char)ch) || ch == 0x7F)
    {
	if (ch == 0x7f)
	    (void) printf("called ^?");
	else
	    (void) printf("called ^%c", '@' + (ch & 0x1f));
	for (; strlen(*ptr) < 4 && isupper(**ptr); ptr++)
	    (void) printf(", %s", *ptr);
	(void) putchar('\n');
    }

    (void) printf("Official name: %s\n", *ptr++);

    if (*ptr)
    {
	char	*commentary = (char *)NULL;

	if (**ptr == '\\')
	{
	    (void) printf("C escape: '%s'\n", *ptr);
	    ptr++;
	}

	(void) printf("Other names: ");
	for (; *ptr; ptr++)
	    if (**ptr == '#')
		commentary = *ptr;
	    else
		(void) printf("%s%s ", *ptr, 
			      (ptr[1]!=NULL && *ptr[1] != '#') ? "," : "");
	(void) putchar('\n');
	if (commentary)
	    (void) printf("Note: %s\n", commentary+2);
    }

    (void) putchar('\n');
}

static int stricmp(char	*s, char *t)
/* case-blind string compare */
{
    while (*s!='\0' && tolower(*s) == tolower(*t))
	s++, t++;
    return (int)(*t - *s);
}

static void ascii(char *str)
{
    int	ch, hi, lo;
    char **ptr;
    size_t len = strlen(str);

    /* interpret single characters as themselves */
    if (len == 1) { 
	speak((unsigned int)str[0]); 
	/* also interpret single digits as numeric */
	if(!line && strchr("0123456789ABCDEFabcdef",str[0])) {
	    int hval;
	    (void) sscanf(str, "%x", &hval);
	    speak(hval);
	}	return; 
    }

    /* process multiple letters */ 
    if (line == 1) {	
	for (ch = 0; ch < len; ch ++) {
	    speak((unsigned int)str[ch]);
	}
	return;
    }

    /* interpret ^-escapes as control-character notation */
    if (strcmp(str, "^?") == 0)
    { speak((unsigned int)0x7F); return; } 
    else if (str[0] == '^' && len == 2)
    { speak((unsigned int)(str[1] & 0x1f)); return; }

    /* interpret C-style backslash escapes */
    if (*str == '\\' &&  len == 2 && strchr("abfnrtv0", str[1]))
	for (ch = 7; ch < 14; ch++)
	    for (ptr = &cnames[ch][1]; *ptr; ptr++)
		if (**ptr == '\\' && strcmp(str, *ptr) == 0)
		{ speak((unsigned int)ch); return; }

    /* interpret 2 and 3-character ASCII control mnemonics */
    if (len == 2 || len == 3)
    {
	/* first check for standard mnemonics */
	if (stricmp(str, "DEL") == 0)
	{ speak(0x7f); return; }
	if (stricmp(str, "BL") == 0)
	{ speak(' '); return; }
	else if (isalpha(str[0]))
	    for (ch = 0; ch <= 32; ch++)
		if (!stricmp(str,cnames[ch][0]) || !strcmp(str,cnames[ch][1]))
		{ speak(ch); return; }
    }

    /* OK, now try to interpret the string as a numeric */
    if (len > 1 && len < 9)
    {
	int hval, dval, oval, bval, spoken = 0;

	dval = oval = hval = bval = -1;

	/* if it's all numeric it could be in one of three bases */
	if (len <= 2 && strspn(str,"0123456789ABCDEFabcdef") == len)
	    (void) sscanf(str, "%x", &hval);
	if (len <= 3 && strspn(str, "0123456789") == len)
	    (void) sscanf(str, "%d", &dval);
	if (len <= 3 && strspn(str, "01234567") == len)
	    (void) sscanf(str, "%o", &oval);
	if (len <= 9 && strspn(str, "01") == len)
	    bval = atob(str);

	/* accept 0xnn, \xnn, xnn and nnh forms for hex */
	if (hval == -1)
	    if ((str[0]=='0'||str[0]=='\\') && tolower(str[1]) == 'x')
		(void) sscanf(str + 2, "%x", &hval);
	    else if (tolower(str[0]) == 'x')
		(void) sscanf(str + 1, "%x", &hval);
	    else if ((len >= 2) && (len <= 3) &&
		     (strspn(str,"0123456789ABCDEFabcdef") == (len-1)) &&
		     (tolower(str[len - 1]) == 'h'))
		(void) sscanf(str, "%x", &hval);

	/* accept 0onn, \onnn, onnn and \nnn forms for octal */
	if (oval == -1)
	    if ((str[0]=='0'||str[0]=='\\') && tolower(str[1]) == 'o')
		(void) sscanf(str + 2, "%o", &oval);
	    else if (tolower(str[0]) == 'o')
		(void) sscanf(str + 1, "%o", &oval);
	    else if (str[0] == '\\' && strspn(str + 1, "0123456789") == len - 1)
		(void) sscanf(str + 1, "%o", &oval);

	/* accept 0dnnn, \dnnn and dnnn forms for decimal */
	if (dval == -1)
	    if ((str[0]=='0'||str[0]=='\\') && tolower(str[1]) == 'd')
		(void) sscanf(str + 2, "%d", &dval);
	    else if (tolower(str[0]) == 'd')
		(void) sscanf(str + 1, "%d", &dval);

	/* accept 0bnnn, \bnnn and bnnn forms for binary */
	if (bval == -1)
	    if ((str[0]=='0'||str[0]=='\\') && tolower(str[1]) == 'b')
		bval = atob(str + 2);
	    else if (tolower(str[0]) == 'b')
		bval = atob(str + 1);

	/* OK, now output all values */
	if (hval > -1 && hval < 256)
	    speak(hval & 0xff);
	if (dval > -1 && dval < 256)
	    speak(dval & 0xff);
	if (oval > -1 && oval < 256)
	    speak(oval & 0xff);
	if (bval > -1 && bval < 256)
	    speak(bval & 0xff);
	if (!(hval==-1 && dval==-1 && oval==-1 && bval==-1))
	{
	    if (hval > -1 || dval > -1 || oval > -1 || bval > -1)
		return;
	    if (hval < 256 || dval < 256 || oval < 256 || bval < 256)
		return;
	}
    }

    if (sscanf(str, "%d/%d", &hi, &lo) == 2)    /* code table reference?  */
    { speak(hi*16 + lo); return; }
    else if (len > 1 && isalpha(str[0]))	/* try to match long names */
    {
	char	canbuf[BUFSIZ], *ep;
	int	i;

	/* map dashes and other junk to spaces */
	for (i = 0; i <= len; i++)
	    if (str[i] == '-' || isspace(str[i]))
		canbuf[i] = ' ';
	    else
		canbuf[i] = str[i];

	/* strip `sign' or `Sign' suffix */
	ep = canbuf + strlen(canbuf) - 4;
	if (!strcmp(ep, "sign") || !strcmp(ep, "Sign"))
	    *ep = '\0';

	/* remove any trailing whitespace */
	while (canbuf[strlen(canbuf) - 1] == ' ')
	    canbuf[strlen(canbuf) - 1] = '\0';

	/* look through all long names for a match */
	for (ch = 0; ch < 128; ch++)
	    for (ptr = &cnames[ch][1]; *ptr; ptr++)
		if (!stricmp(*ptr, canbuf))
		    speak(ch);
    } /* outer if */
}

static char *bin(int n)
{
    static char rep[9];
    int c, k;

    rep[0] = '\0';
    for (c = 6; c >= 0; c--)
    {
        k = n >> c;

        if (k & 1)
            strncat(rep, "1", sizeof(rep));
        else
            strncat(rep, "0", sizeof(rep));
    }

    return rep;
}

void print_table(unsigned short delivery, bool vertical)
{
   static unsigned short i, j, len;
   char   separator[]= "   "; 
   char   *tail     = separator + 3 ;
   char   *space; 
   char   *name;
   int rows, cols;

   if (vertical)
       cols = 4;
   else
       cols = 8;
   rows = 128 / cols;
   for (i = 0; i < rows; i++) {
           for (j = 0; j < cols; j++) {
                  name= *cnames[i+(j*rows)];
                  len = strlen(name);
                  space = tail - (len % 3) ;
                  switch (delivery) {
                      case decimal:  printf("%5d %1s%1s",i+(j*rows),name,space);
                                     break;
                      case octal  :  printf("  %03o %1s%1s",i+(j*rows),name,space);
                                     break;
                      case hex    :  printf("   %02X %1s%1s",i+(j*rows),name,space);
                                     break;
		      case binary :  printf("   %s %1s%1s",bin(i+(j*rows)),name,space);
                                     break;
                  }
           }
           printf("\n");
   }
       
}

int main(int argc, char **argv)
{
    char      *optstring="abtshvxod" ;


    process_options(argc, argv, optstring) ;

    if (help || argc == optind)
	showHelp(stdout, argv[0]);
    else 
	while (optind < argc)	    
	    ascii(argv[optind++]);
    exit(0);
}

/* ascii.c ends here */

