
#include "yodl.h"

static int
escapechar (char const *str)	/* convert escaped char */
{
  switch (*str)
    {
    case 'a':
      return ('\a');
    case 'b':
      return ('\b');
    case 'f':
      return ('\f');
    case 'n':
      return ('\n');
    case 'r':
      return ('\r');
    case 't':
      return ('\t');
    case 'v':
      return ('\v');
    default:
      return ((unsigned char)*str);
    }
}

static int
string_to_char (char const *str)	/* convert 'charspec' */
{
  if (*str != '\'')
    return (0);

  str++;
  if (!*str)
    return (0);

  if (*str != '\\')
    return ((unsigned char)*str);

  str++;
  if (!*str)
    return (0);

  return (escapechar (str));
}

static char *
string_to_string (char const *str)	/* convert escape string */
{
  char
   *ret = 0;

  while (*str)
    {
      if (*str == '\\')		/* escape sequence */
	{
	  str++;
	  ret = str_addchar (ret, escapechar (str));
	}
      else
	ret = str_addchar (ret, *str);

      if (*str)
	str++;
    }

  return (ret);
}

void
parse_table (char *table, CHARTAB * dest)
{
  char
   *line, **lines = 0, **charp = 0, *newchar, **redefp = 0, *newredef;
  int
    i, chindex, redeflen, nlines = 0, ncharp = 0, nredefp = 0;

  line = strtok (table, "\n");
  while (line)
    {
      lines = strarr_add (lines, &nlines, line);
      line = strtok (0, "\n");
    }

  for (i = 0; i < nlines; i++)
    {
      newchar = strtok (lines[i], "=");
      if (!newchar)
	error_gram (builtin_get (idx_DEFINECHARTABLE),
		    "missing character specifier in line %s", lines[i]);
      while (isspace ((int)*newchar))
	newchar++;
      newredef = strtok (0, "=");
      if (!newredef)
	error_gram (builtin_get (idx_DEFINECHARTABLE),
		    "missing redefinition specifier in line %s", lines[i]);
      while (isspace ((int)*newredef))
	newredef++;

      charp = strarr_add (charp, &ncharp, newchar);
      redefp = strarr_add (redefp, &nredefp, newredef);
    }

  for (i = 0; i < nlines; i++)
    {
      if ((chindex = string_to_char (charp[i])) == -1)
	error_gram (builtin_get (idx_DEFINECHARTABLE),
		    "at %s: expected 'c' character specifier", charp[i]);

      if (*redefp[i] != '"' || !*(redefp[i] + 1))
	error_gram (builtin_get (idx_DEFINECHARTABLE),
		    "redefinition %s: must have form \"redef\"", redefp);
      strcpy (redefp[i], redefp[i] + 1);
      redeflen = strlen (redefp[i]) - 1;
      if (redefp[i][redeflen] != '"')
	error_gram (builtin_get (idx_DEFINECHARTABLE),
		    "redefinition %s: unterminated string", redefp[i]);
      redefp[i][redeflen] = '\0';

      (*dest)[chindex] = string_to_string (redefp[i]);
    }

  strarr_free (lines, nlines);
  strarr_free (charp, ncharp);
  strarr_free (redefp, nredefp);
}

void
gram_DEFINECHARTABLE ()
{
  char
   *tabname,			/* table name */
   *table;			/* table itself */
  int
    i;				/* index in chartab */
  CHARTAB
    * dest;			/* new table to define */
  static char
    buf[2];			/* conversion buf */


  tabname =			/* retrieve table name */
    gram_parlist (builtin_get (idx_DEFINECHARTABLE), 0);
  gram_onename (builtin_get (idx_DEFINECHARTABLE), tabname);

  message (3, "%s %s\n", builtin_get (idx_DEFINECHARTABLE), tabname);

  while (lextok == tok_space ||	/* skip spaces, newlines */
	 lextok == tok_newline
    )
    lexer ();

  table =			/* retrieve table itself */
    gram_parlist (builtin_get (idx_DEFINECHARTABLE), 0);
  if (!table || !*table)	/* may not be empty */
    error_gram (builtin_get (idx_DEFINECHARTABLE),
		"empty table definition for table %s", tabname);

  /* table already defined? */
  if (strtab_find (chartabname, nchartab, tabname) != -1)
    error_gram (builtin_get (idx_DEFINECHARTABLE),
		"table %s already defined", tabname);

  i = curchartab - chartab;	/* remember current */

  /* make new table */
  chartabname = strtab_add (chartabname, &nchartab, tabname);
  chartab = (CHARTAB *) xrealloc (chartab, nchartab * sizeof (CHARTAB));

  if (curchartab)		/* restore current */
    curchartab = chartab + i;

  dest = chartab + nchartab - 1;	/* destination of new */

  for (i = 0; i < 256; i++)	/* reset table to defaults */
    {
      buf[0] = (char) i;
      (*dest)[i] = xstrdup (buf);
    }

  parse_table (table, dest);

  free (tabname);
  free (table);
}
