/* libexo.c - EXO library main line routines */

/* SimpleScalar(TM) Tool Suite
 * Copyright (C) 1994-2003 by Todd M. Austin, Ph.D. and SimpleScalar, LLC.
 * All Rights Reserved. 
 * 
 * THIS IS A LEGAL DOCUMENT, BY USING SIMPLESCALAR,
 * YOU ARE AGREEING TO THESE TERMS AND CONDITIONS.
 * 
 * No portion of this work may be used by any commercial entity, or for any
 * commercial purpose, without the prior, written permission of SimpleScalar,
 * LLC (info@simplescalar.com). Nonprofit and noncommercial use is permitted
 * as described below.
 * 
 * 1. SimpleScalar is provided AS IS, with no warranty of any kind, express
 * or implied. The user of the program accepts full responsibility for the
 * application of the program and the use of any results.
 * 
 * 2. Nonprofit and noncommercial use is encouraged. SimpleScalar may be
 * downloaded, compiled, executed, copied, and modified solely for nonprofit,
 * educational, noncommercial research, and noncommercial scholarship
 * purposes provided that this notice in its entirety accompanies all copies.
 * Copies of the modified software can be delivered to persons who use it
 * solely for nonprofit, educational, noncommercial research, and
 * noncommercial scholarship purposes provided that this notice in its
 * entirety accompanies all copies.
 * 
 * 3. ALL COMMERCIAL USE, AND ALL USE BY FOR PROFIT ENTITIES, IS EXPRESSLY
 * PROHIBITED WITHOUT A LICENSE FROM SIMPLESCALAR, LLC (info@simplescalar.com).
 * 
 * 4. No nonprofit user may place any restrictions on the use of this software,
 * including as modified by the user, by any other authorized user.
 * 
 * 5. Noncommercial and nonprofit users may distribute copies of SimpleScalar
 * in compiled or executable form as set forth in Section 2, provided that
 * either: (A) it is accompanied by the corresponding machine-readable source
 * code, or (B) it is accompanied by a written offer, with no time limit, to
 * give anyone a machine-readable copy of the corresponding source code in
 * return for reimbursement of the cost of distribution. This written offer
 * must permit verbatim duplication by anyone, or (C) it is distributed by
 * someone who received only the executable form, and is accompanied by a
 * copy of the written offer of source code.
 * 
 * 6. SimpleScalar was developed by Todd M. Austin, Ph.D. The tool suite is
 * currently maintained by SimpleScalar LLC (info@simplescalar.com). US Mail:
 * 2395 Timbercrest Court, Ann Arbor, MI 48105.
 * 
 * Copyright (C) 1994-2003 by Todd M. Austin, Ph.D. and SimpleScalar, LLC.
 */


#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <limits.h>
#include <errno.h>
#include <assert.h>

#include "../host.h"
#include "../misc.h"
#include "../machine.h"
#include "libexo.h"

/* EXO term classes print strings */
char *exo_class_str[ec_NUM] = {
  "integer",
  "address",
  "float",
  "char",
  "string"
  "list",
  "array",
  "token",
  "blob"
};

/* return the value of an escape sequence, ESCAPE is a pointer to the first
   character following '\', sets NEXT to first character after escape */
/* A2.5.2 */
static int
intern_escape(char *esc, char **next)
{
  int c, value, empty, count;

  switch (c = *esc++) {
  case 'x':
    /* \xhh hex value */
    value = 0;
    empty = TRUE;
    while (1)
      {
        c = *esc++;
        if (!(c >= 'a' && c <= 'f')
            && !(c >= 'A' && c <= 'F')
            && !(c >= '0' && c <= '9'))
          {
            esc--;
            break;
          }
        value *=16;
        if (c >= 'a' && c <= 'f')
          value += c - 'a' + 10;
        if (c >= 'A' && c <= 'F')
          value += c - 'A' + 10;
        if (c >= '0' && c <= '9')
          value += c - '0';
        empty = FALSE;
      }
    if (empty)
      fatal("\\x used with no trailing hex digits");
    break;

  case '0': case '1': case '2': case '3':
  case '4': case '5': case '6': case '7':
    /* \ooo octal value */
    value = 0;
    count = 0;
    while ((c <= '7') && (c >= '0') && (count++ < 3))
      {
        value = (value * 8) + (c - '0');
        c = *esc++;
      }
    esc--;
    break;

  case '\\':
  case '\'':
  case '"':
    value = c;
    break;

  case 'n':
    value = '\n';
    break;

  case 't':
    value = '\t';
    break;

  case 'r':
    value = '\r';
    break;

  case 'f':
    value = '\f';
    break;

  case 'b':
    value = '\b';
    break;

  case 'a':
    value = '\a';
    break;

  case 'v':
    value = '\v';
    break;

  case '?':
    value = c;
    break;

  case '(':
  case '{':
  case '[':
  case '%':
    value = c;
    warn("non-ANSI escape sequence `\\%c'", c);
    break;

  default:
    fatal("unknown escape, '\\' followed by char %x (`%c')", (int)c, c);
  }

  if (*next)
    *next = esc;

  return value;
}

/* return the value of an character literal sequence */
/* A2.5.2 */
static int
intern_char(char *s, char **next)
{
  unsigned char value;

  if (s[0] != '\'' || s[strlen(s)-1] != '\'')
    panic("mal-formed string constant");

  if (s[1] != '\\')
    {
      value = (unsigned)s[1];
      if (s[2] != '\'')
	panic("mal-formed string constant");
      if (next)
	*next = s + 2;
    }
  else
    {
      /* escaped char constant */
      value = intern_escape(s+2, next);
    }

  /* map to a signed char value */
  value = (signed int)((unsigned char)((unsigned char)((unsigned int)value)));

  if (UCHAR_MAX < value)
    fatal("character constant out of range");

  return value;
}

static void
print_char(unsigned char c, FILE *stream)
{
  switch (c)
    {
    case '\n':
      fprintf(stream, "\\n");
      break;

    case '\\':
      fprintf(stream, "\\\\");
      break;

    case '\'':
      fprintf(stream, "\\'");
      break;

    case '\t':
      fprintf(stream, "\\t");
      break;

    case '\r':
      fprintf(stream, "\\r");
      break;

    case '\f':
      fprintf(stream, "\\f");
      break;

    case '\b':
      fprintf(stream, "\\b");
      break;

    case '\a':
      fprintf(stream, "\\a");
      break;

    case '\v':
      fprintf(stream, "\\v");
      break;

    default:
      if (isprint(c))
	fprintf(stream, "%c", c);
      else
	fprintf(stream, "\\x%02x", c);
    }
}

/* expand all escapes in string STR, return pointer to allocation w/ result */
static char *
intern_string(char *str)
{
  char *s, *istr;

  /* resulting string cannot be longer than STR */
  s = istr = malloc(strlen(str)+1);

  if (!str || !*str || *str != '\"') /* " */
    panic("mal-formed string constant");

  /* skip `"' */ /* " */
  str++;

  while (*str)
    {
      if (*str == '\\')
        *s++ = intern_escape(str+1, &str);
      else
        {
          /* A2.6 */
          if (*str == '\n')
            warn("ANSI C forbids newline in character constant");
          /* A2.6 */
          if (*str == '"' && str[1] != '\0')
            panic("encountered `\"' embedded in string constant");

          if (*str != '\"') /* " */
            *s++ = *str;
          str++;
        }
    }
  *s = '\0';
  return istr;
}

static void
print_string(unsigned char *s, FILE *stream)
{
  while (*s)
    {
      print_char(*s, stream);
      s++;
    }
}

/* bogus token value */
#define TOKEN_BOGON		0

static int token_id = TOKEN_BOGON + 1;

#define TOKEN_HASH_SIZE		1024
struct exo_token_t *token_hash[TOKEN_HASH_SIZE];

/* hash a string */
static unsigned long
hash_str(char *s)
{
  unsigned h = 0;
  while (*s)
    h = (h << 1) + *s++;
  return (h % TOKEN_HASH_SIZE);
}

/* intern token TOKEN_STR */
struct exo_token_t *
exo_intern(char *token_str)		/* string to intern */
{
  int index;
  struct exo_token_t *ent;

  index = hash_str(token_str);

  for (ent=token_hash[index]; ent != NULL; ent=ent->next)
    {
      if (!strcmp(token_str, ent->str))
	{
	  /* got a match, return token entry */
	  return ent;
	}
    }

  /* not found, create a new entry */
  ent = (struct exo_token_t *)calloc(1, sizeof(struct exo_token_t));
  if (!ent)
    fatal("out of virtual memory");

  ent->str = mystrdup(token_str);
  ent->token = token_id++;
  ent->next = token_hash[index];
  token_hash[index] = ent;

  return ent;
}

/* intern token TOKEN_STR as value TOKEN */
struct exo_token_t *
exo_intern_as(char *token_str,		/* string to intern */
	      int token)		/* internment value */
{
  struct exo_token_t *ent;

#if 0
  if (token_id > token)
    fatal("token value is already in use");
#endif

  ent = exo_intern(token_str);
  /* overide the default value */
  ent->token = token;

#if 0
  if (ent->token != token)
    fatal("symbol `%s' was previously interned", token_str);
#endif

  return ent;
}

/* allocate an EXO node, fill in its type */
static struct exo_term_t *
exo_alloc(enum exo_class_t ec)
{
  struct exo_term_t *exo;

  exo = (struct exo_term_t *)calloc(1, sizeof(struct exo_term_t));
  if (!exo)
    fatal("out of virtual memory");
  exo->next = NULL;
  exo->ec = ec;

  return exo;
}


/*
 * create a new EXO term, usage:
 *
 *	exo_new(ec_integer, (exo_integer_t)<int>);
 *	exo_new(ec_address, (exo_integer_t)<int>);
 *	exo_new(ec_float, (exo_float_t)<float>);
 *	exo_new(ec_char, (int)<char>);
 *      exo_new(ec_string, "<string>");
 *      exo_new(ec_list, <list_ent>..., NULL);
 *      exo_new(ec_array, <size>, <array_ent>..., NULL);
 *	exo_new(ec_token, "<token>"); 
*/
struct exo_term_t *
exo_new(enum exo_class_t ec, ...)
{
  struct exo_term_t *exo;
  va_list v;
  va_start(v, ec);

  exo = exo_alloc(ec);
  switch (ec)
    {
    case ec_integer:
      exo->as_integer.val = va_arg(v, exo_integer_t);
      break;

    case ec_address:
      exo->as_address.val = va_arg(v, exo_address_t);
      break;

    case ec_float:
      exo->as_float.val = va_arg(v, exo_float_t);
      break;

    case ec_char:
      exo->as_char.val = va_arg(v, int);
      break;

    case ec_string:
      {
	char *str;

	str = va_arg(v, char *);
	exo->as_string.str = (unsigned char *)mystrdup(str);
      }
      break;

    case ec_list:
      {
	struct exo_term_t *ent;

	exo->as_list.head = NULL;
	do {
	  ent = va_arg(v, struct exo_term_t *);
	  exo->as_list.head = exo_chain(exo->as_list.head, ent);
	} while (ent != NULL);
      }
      break;

    case ec_array:
      {
	int i;
	struct exo_term_t *ent;

	exo->as_array.size = va_arg(v, int);
	exo->as_array.array = (struct exo_term_t **)
	  calloc(exo->as_array.size, sizeof(struct exo_term_t *));
	if (!exo->as_array.array)
	  fatal("out of virtual memory");
	i = 0;
	do {
	  ent = va_arg(v, struct exo_term_t *);
	  if (ent != NULL)
	    {
	      if (i == exo->as_array.size)
		fatal("array constructor overflow");
	      SET_EXO_ARR(exo, i, ent);
	    }
	  i++;
	} while (ent != NULL);
      }
      break;

    case ec_token:
      {
	char *str;

	str = va_arg(v, char *);
	exo->as_token.ent = exo_intern(str);
      }
      break;

    case ec_blob:
      {
	unsigned size;
	unsigned char *data;

	size = va_arg(v, unsigned);
	data = va_arg(v, unsigned char *);

	exo->as_blob.size = size;
	exo->as_blob.data = malloc(size);
	if (data != NULL)
	  memcpy(exo->as_blob.data, data, size);
	else
	  memset(exo->as_blob.data, 0, size);
      }
      break;

    case ec_null:
      break;

    default:
      panic("bogus EXO class");
    }

  va_end(v);
  return exo;
}

/* release an EXO term */
void
exo_delete(struct exo_term_t *exo)
{
  exo->next = NULL;

  switch (exo->ec)
    {
    case ec_integer:
      /* no extra storage */
      exo->as_integer.val = 0;
      break;

    case ec_address:
      /* no extra storage */
      exo->as_address.val = 0;
      break;

    case ec_float:
      /* no extra storage */
      exo->as_float.val = 0.0;
      break;

    case ec_char:
      /* no extra storage */
      exo->as_char.val = '\0';
      break;

    case ec_string:
      free(exo->as_string.str);
      exo->as_string.str = NULL;
      break;

    case ec_list:
      {
	struct exo_term_t *ent, *next_ent;

	for (ent=exo->as_list.head; ent != NULL; ent = next_ent)
	  {
	    next_ent = ent->next;
	    exo_delete(ent);
	  }
	exo->as_list.head = NULL;
      }
      break;

    case ec_array:
      {
	int i;

	for (i=0; i < exo->as_array.size; i++)
	  {
	    if (exo->as_array.array[i] != NULL)
	      exo_delete(exo->as_array.array[i]);
	  }
	free(exo->as_array.array);
	exo->as_array.array = NULL;
	exo->as_array.size = 0;
      }
      break;

    case ec_token:
      /* no extra storage */
      exo->as_token.ent = NULL;
      break;

    case ec_blob:
      /* free the blob data */
      free(exo->as_blob.data);
      exo->as_blob.data = NULL;
      break;

    case ec_null:
      /* no extra storage */
      break;

    default:
      panic("bogus EXO class");
    }
  exo->ec = (enum exo_class_t)0;

  /* release the node */
  free(exo);
}

/* chain two EXO lists together, FORE is attached on the end of AFT */
struct exo_term_t *
exo_chain(struct exo_term_t *fore, struct exo_term_t *aft)
{
  struct exo_term_t *exo, *prev;

  if (!fore && !aft)
    return NULL;

  if (!fore)
    return aft;

  /* find the tail of FORE */
  for (prev=NULL,exo=fore; exo != NULL; prev=exo,exo=exo->next)
    /* nada */;
  assert(prev);

  /* link onto the tail of FORE */
  prev->next = aft;

  return fore;
}

/* copy an EXO node */
struct exo_term_t *
exo_copy(struct exo_term_t *exo)
{
  struct exo_term_t *new_exo;

  /* NULL copy */
  if (!exo)
    return NULL;

  new_exo = exo_alloc(exo->ec);
  *new_exo = *exo;

  /* the next link is always blown away on a copy */
  new_exo->next = NULL;

  switch (new_exo->ec)
    {
    case ec_integer:
    case ec_address:
    case ec_float:
    case ec_char:
    case ec_string:
    case ec_list:
    case ec_token:
      /* no internal parts to copy */
      break;

    case ec_array:
      {
	int i;

	/* copy the array */
	new_exo->as_array.array = (struct exo_term_t **)
	  calloc(new_exo->as_array.size, sizeof(struct exo_term_t *));

	for (i=0; i<new_exo->as_array.size; i++)
	  {
	    SET_EXO_ARR(new_exo, i, EXO_ARR(exo, i));
	  }
      }
      break;

    case ec_blob:
      new_exo->as_blob.data = malloc(new_exo->as_array.size);
      memcpy(new_exo->as_blob.data, exo->as_blob.data, new_exo->as_array.size);
      break;

    default:
      panic("bogus EXO class");
    }

  return new_exo;
}

/* deep copy an EXO structure */
struct exo_term_t *
exo_deepcopy(struct exo_term_t *exo)
{
  struct exo_term_t *new_exo;

  /* NULL copy */
  if (!exo)
    return NULL;

  new_exo = exo_copy(exo);
  switch (new_exo->ec)
    {
    case ec_integer:
    case ec_address:
    case ec_float:
    case ec_char:
    case ec_token:
      /* exo_copy() == exo_deepcopy() for these node classes */
      break;

    case ec_string:
      /* copy the referenced string */
      new_exo->as_string.str =
	(unsigned char *)mystrdup((char *)exo->as_string.str);
      break;

    case ec_list:
      /* copy all list elements */
      {
	struct exo_term_t *elt, *new_elt, *new_list;

	new_list = NULL;
	for (elt=new_exo->as_list.head; elt != NULL; elt=elt->next)
	  {
	    new_elt = exo_deepcopy(elt);
	    new_list = exo_chain(new_list, new_elt);
	  }
	new_exo->as_list.head = new_list;
      }
      break;

    case ec_array:
      /* copy all array elements */
      {
	int i;

	for (i=0; i<new_exo->as_array.size; i++)
	  {
	    SET_EXO_ARR(new_exo, i, exo_deepcopy(EXO_ARR(exo, i)));
	  }
      }
      break;

    case ec_blob:
      new_exo->as_blob.data = malloc(new_exo->as_array.size);
      memcpy(new_exo->as_blob.data, exo->as_blob.data, new_exo->as_array.size);
      break;

    default:
      panic("bogus EXO class");
    }

  return new_exo;
}

/* print an EXO term */
void
exo_print(struct exo_term_t *exo, FILE *stream)
{
  if (!stream)
    stream = stderr;

  switch (exo->ec)
    {
    case ec_integer:
      if (sizeof(exo_integer_t) == 4)
	myfprintf(stream, "%u", exo->as_integer.val);
      else
	myfprintf(stream, "%lu", exo->as_integer.val);
      break;

    case ec_address:
      if (sizeof(exo_address_t) == 4)
	myfprintf(stream, "0x%x", exo->as_integer.val);
      else
	myfprintf(stream, "0x%lx", exo->as_integer.val);
      break;

    case ec_float:
      fprintf(stream, "%f", exo->as_float.val);
      break;

    case ec_char:
      fprintf(stream, "'");
      print_char(exo->as_char.val, stream);
      fprintf(stream, "'");
      break;

    case ec_string:
      fprintf(stream, "\"");
      print_string(exo->as_string.str, stream);
      fprintf(stream, "\"");
      break;

    case ec_list:
      {
	struct exo_term_t *ent;

	fprintf(stream, "(");
	for (ent=exo->as_list.head; ent != NULL; ent=ent->next)
	  {
	    exo_print(ent, stream);
	    if (ent->next)
	      fprintf(stream, ", ");
	  }
	fprintf(stream, ")");
      }
      break;

    case ec_array:
      {
	int i, last;

	/* search for last first non-NULL entry */
	for (last=exo->as_array.size-1; last >= 0; last--)
	  {
	    if (EXO_ARR(exo, last) != NULL)
	      break;
	  }
	/* LAST == index of last non-NULL array entry */

	fprintf(stream, "{%d}[", exo->as_array.size);
	for (i=0; i<exo->as_array.size && i <= last; i++)
	  {
	    if (exo->as_array.array[i] != NULL)
	      exo_print(exo->as_array.array[i], stream);
	    else
	      fprintf(stream, " ");
	    if (i != exo->as_array.size-1 && i != last)
	      fprintf(stream, ", ");
	  }
	fprintf(stream, "]");
      }
      break;

    case ec_token:
      fprintf(stream, "%s", exo->as_token.ent->str);
      break;

    case ec_blob:
      {
	int i, cr;

	fprintf(stream, "{%d}<\n", exo->as_blob.size);
	for (i=0; i < exo->as_blob.size; i++)
	  {
	    cr = FALSE;
	    if (i != 0 && (i % 38) == 0)
	      {
		fprintf(stream, "\n");
		cr = TRUE;
	      }
	    fprintf(stream, "%02x", exo->as_blob.data[i]);
	  }
	if (!cr)
	  fprintf(stream, "\n");
	fprintf(stream, ">");
      }
      break;

    default:
      panic("bogus EXO class");
    }
}

/* (f)lex external defs */
extern int yylex(void);
extern int yy_nextchar(void);
extern char *yytext;
extern FILE *yyin;

static void
exo_err(char *err)
{
  extern int line;

  fprintf(stderr, "EXO parse error: line %d: %s\n", line, err);
  exit(1);
}

/* read one EXO term from STREAM */
struct exo_term_t *
exo_read(FILE *stream)
{
  int tok;
  char tok_buf[1024], *endp;
  struct exo_term_t *ent = NULL;
  extern int errno;
  extern void yy_setstream(FILE *);

  /* make sure we have a valid stream */
  if (!stream)
    stream = stdin;
  yy_setstream(stream);

  /* make local copies of everything, allows arbitrary recursion */
  tok = yylex();
  strcpy(tok_buf, yytext);

  switch (tok)
    {
    case lex_integer:
      {
	exo_integer_t int_val;

	/* attempt integer conversion */
	errno = 0;
#ifdef HOST_HAS_QWORD
	int_val = myatoq(tok_buf, &endp, /* parse base */10);
#else /* !HOST_HAS_QWORD */
	int_val = strtoul(tok_buf, &endp, /* parse base */10);
#endif /* HOST_HAS_QWORD */
	if (!errno && !*endp)
	  {
	    /* good conversion */
	    ent = exo_new(ec_integer, int_val);
	  }
	else
	  exo_err("cannot parse integer literal");
      }
      break;

    case lex_address:
      {
	exo_address_t addr_val;

	/* attempt address conversion */
	errno = 0;
#ifdef HOST_HAS_QWORD
	addr_val = myatoq(tok_buf, &endp, /* parse base */16);
#else /* !HOST_HAS_QWORD */
	addr_val = strtoul(tok_buf, &endp, /* parse base */16);
#endif /* HOST_HAS_QWORD */
	if (!errno && !*endp)
	  {
	    /* good conversion */
	    ent = exo_new(ec_address, addr_val);
	  }
	else
	  exo_err("cannot parse address literal");
      }
      break;

    case lex_float:
      {
	exo_float_t float_val;

	/* attempt double conversion */
	errno = 0;
	float_val = strtod(tok_buf, &endp);
	if (!errno && !*endp)
	  {
	    /* good conversion */
	    ent = exo_new(ec_float, float_val);
	  }
	else
	  exo_err("cannot parse floating point literal");
      }
      break;

    case lex_char:
      {
	int c;

	c = intern_char(tok_buf, &endp);
	if (!endp)
	  exo_err("cannot convert character literal");
	ent = exo_new(ec_char, c);
      }
      break;

    case lex_string:
      {
	char *s;

	s = intern_string(tok_buf);
	ent = exo_new(ec_string, s);
	free(s);
      }
      break;

    case lex_token:
      ent = exo_new(ec_token, tok_buf);
      break;

    case lex_byte:
      exo_err("unexpected blob byte encountered");
      break;

    case '(':
      {
	struct exo_term_t *elt;

	ent = exo_new(ec_list, NULL);

	if (yy_nextchar() != ')')
	  {
	    /* not an empty list */
	    do {
	      elt = exo_read(stream);
	      if (!elt)
		exo_err("unexpected end-of-file");
	      ent->as_list.head =
		exo_chain(ent->as_list.head, elt);

	      /* consume optional commas */
	      if (yy_nextchar() == ',')
		yylex();
	    } while (yy_nextchar() != ')');
	  }

	/* read tail delimiter */
	tok = yylex();
	if (tok != ')')
	  exo_err("expected ')'");
      }
      break;

    case ')':
      exo_err("unexpected ')' encountered");
      break;

    case '<':
      exo_err("unexpected '<' encountered");
      break;

    case '>':
      exo_err("unexpected '>' encountered");
      break;

    case '{':
      {
	int cnt, size;
	struct exo_term_t *elt;

	/* get the size */
	elt = exo_read(stream);
	if (!elt || elt->ec != ec_integer)
	  exo_err("badly formed array size");

	/* record the size of the array/blob */
	size = (int)elt->as_integer.val;

	/* done with the EXO integer */
	exo_delete(elt);

	/* read the array delimiters */
	tok = yylex();
	if (tok != '}')
	  exo_err("expected '}'");

	tok = yylex();
	switch (tok)
	  {
	  case '[': /* array definition */
	    /* allocate an array definition */
	    ent = exo_new(ec_array, size, NULL);

	    /* read until array is full or tail delimiter encountered */
	    if (yy_nextchar() != ']')
	      {
		/* not an empty array */
		cnt = 0;
		do {
		  if (cnt == ent->as_array.size)
		    exo_err("too many initializers for array");

		  /* NULL element? */
		  if (yy_nextchar() == ',')
		    {
		      elt = NULL;
		    }
		  else
		    {
		      elt = exo_read(stream);
		      if (!elt)
			exo_err("unexpected end-of-file");
		    }
		  SET_EXO_ARR(ent, cnt, elt);
		  cnt++;

		  /* consume optional commas */
		  if (yy_nextchar() == ',')
		    yylex();
		} while (yy_nextchar() != ']');
	      }

	    /* read tail delimiter */
	    tok = yylex();
	    if (tok != ']')
	      exo_err("expected ']'");
	    break;

	  case '<': /* blob definition */
	    /* allocate an array definition */
	    ent = exo_new(ec_blob, size, /* zero contents */NULL);

	    /* read until blob is full */
	    if (yy_nextchar() != '>')
	      {
		unsigned int byte_val;

		/* not an empty array */
		cnt = 0;
		for (;;) {
		  /* read next blob byte */
		  tok = yylex();

		  if (tok == lex_byte)
		    {
		      if (cnt == ent->as_blob.size)
			exo_err("too many initializers for blob");

		      /* attempt hex conversion */
		      errno = 0;
		      byte_val = strtoul(yytext, &endp, /* parse base */16);
		      if (errno != 0 || *endp != '\0')
			exo_err("cannot parse blob byte literal");
		      if (byte_val > 255)
			panic("bogus byte value");
		      ent->as_blob.data[cnt] = byte_val;
		      cnt++;
		    }
		  else if (tok == '>')
		    break;
		  else
		    exo_err("unexpected character in blob");
		}
	      }

#if 0 /* zero tail is OK... */
	    if (cnt != ent->as_blob.size)
	      exo_err("not enough initializers for blob");
#endif
	    break;

	  default:
	    exo_err("expected '[' or '<'");
	  }
      }
      break;

    case '}':
      exo_err("unexpected '}' encountered");
      break;

    case ',':
      exo_err("unexpected ',' encountered");
      break;

    case '[':
      {
	int i, cnt;
	struct exo_term_t *list, *elt, *next_elt;

	/* compute the array size */
	list = NULL;
	if (yy_nextchar() == ']')
	  exo_err("unsized array has no initializers");

	cnt = 0;
	do {
	  /* NULL element? */
	  if (yy_nextchar() == ',')
	    {
	      elt = exo_new(ec_null);
	    }
	  else
	    {
	      elt = exo_read(stream);
	      if (!elt)
		exo_err("unexpected end-of-file");
	    }
	  cnt++;
	  list = exo_chain(list, elt);

	  /* consume optional commas */
	  if (yy_nextchar() == ',')
	    yylex();
	} while (yy_nextchar() != ']');

	/* read tail delimiter */
	tok = yylex();
	if (tok != ']')
	  exo_err("expected ']'");

	/* create the array */
	assert(cnt > 0);
	ent = exo_new(ec_array, cnt, NULL);

	/* fill up the array */
	for (i=0,elt=list; i<cnt; i++,elt=next_elt)
	  {
	    assert(elt != NULL);
	    next_elt = elt->next;
	    if (elt->ec == ec_null)
	      {
		SET_EXO_ARR(ent, cnt, NULL);
		exo_delete(ent);
	      }
	    else
	      {
		SET_EXO_ARR(ent, cnt, elt);
		elt->next = NULL;
	      }
	  }
      }
      break;

    case ']':
      exo_err("unexpected ']' encountered");
      break;

    case lex_eof:
      /* nothing to read */
      ent = NULL;
      break;

    default:
      panic("bogus token");
    }

  return ent;
}
