Hints on writing a reader

This is what I do... All functions return an error status as a pointer to a string. NULL means no error, otherwise it is a tag, which can be looked up. Working from the bottom up:
const char *readmstoken(char *name)
{
const char *err = 0;

if (1 != fscanf(mscript_iofp, " %32s", name))
  {
  if (ferror(mscript_iofp))
  { err = "Xmsread"; }
  else
  { err = "Xmstoken"; }
  }
return err;
}
readmstoken() reads the next token knowing it is not a string in double quotes. This will never be more than 31 chars.
const char *readmsstring(char *value)
{
const char *err = 0;
int c;
BOOL done;

insist(value);

while ((c = fgetc(mscript_iofp)) == ' ') { }
if (c != '\"')
  { err = "Xmsnodq"; }
done = FALSE;
while (!err && !done)
  {
  while ((c = fgetc(mscript_iofp)) != '\"')
  { *value++ = c; }
  c = fgetc(mscript_iofp);
  if (c != '\"')
  { ungetc(c, mscript_iofp); done = TRUE; }
  else
  { *value++ = c; }
  }
if (ferror(mscript_iofp))
  { err = "Xmsread"; }
return err;
}
readmsstring() reads a string in double quotes, knowing that is what to expect.
const char *mscriptskipstring(void)
{
....
}
Same as readmsstring(), except ignores what it reads.
const char *mscriptskipvalue(const char *keyword)
{
const char *err = 0;
char name[max_token_size];

if (keyword[strlen(keyword) - 1] == '$')
  {
  if (!err) { err = mscriptskipstring(); }
  }
else
  {
  if (!err) { err = readmstoken(name); }
  if (0 != strcmp(name, "{"))
    {
    return err;   /* simple value, done */
    }
  else   /* complex value, recurse */
    {
    if (!err) { err = readmstoken(name); }
    while (!err  &&  0 != strcmp(name, "}"))
      {
      if (name[strlen(name) - 1] == '$')
        {
        if (!err) { err = mscriptskipstring(); }
        }
      else
        {
        if (!err) { err = mscriptskipvalue(name); }
        }
	  if (!err) { err = readmstoken(name); }
      }
    }
  }
return err;
}
mscriptskipvalue() is called when an unknown name token is found.
const char *readmsstructstart(void)
{
const char *err = 0;
char token[max_token_size];

if (!err) { err = readmstoken(token); }
if (!err  &&  0 != strcmp(token, "{"))
  { err = "Xmssyntax"; }
return err;
}
When a known name is encountered, which is follwed by a complex value, readmsstructstart() checks the '{' and reads past it.
const char *readmsstartarray(int *n)
{
const char *err = 0;
char token[max_token_size];
int x = 0;

if (!err) { err = readmstoken(token); }
if (!err  &&  0 != strcmp(token, "{"))
  { err = "Xmssyntax"; }
if (!err) { err = readmstoken(token); }
if (!err  &&  0 != strcmp(token, "nof"))
  { err = "Xmssyntax"; }
if (!err) { err = readmstoken(token); }
x = atoi(token);
if (!err  &&  x <= 0)
  { err = "Xmssyntax"; }
*n = x;
return err;
}       
When a known name is encountered, which is followed by an array (list), readmsstartarray() checks the start, returns the size in *n
const char *readmsstruct(initialisestructFunc initf, 
             readtokeninstructFunc f, void *p)
{
const char *err = 0;
char token[max_token_size];

initf(p);
err = readmsstructstart();
if (!err) { err = readmstoken(token); }
while (!err  &&  strcmp(token, "}") != 0)
  {    
  if (!err) { err = (*f)(p, token); }  
  if (!err) { err = readmstoken(token); }
  }
return err;
}
This reads the inside of an arbitary structure. It calls initf() to intialise the structure, and f() to read each value, passing the name token to f().
const char *readmsbeam(void *p, const char *token)
{
const char *err = 0;
MScriptBeamNode *mbm = (MScriptBeamNode *)p;

if (0 == strcmp("id", token))
  {
  if (!err) { err = readmsint(&mbm->ID); }
  }
else if (0 == strcmp("nofnodes", token))
  {
  if (!err) { err = readmsint(&mbm->nofnodes); }
  }
else if (0 == strcmp("nofleft", token))
  {
  if (!err) { err = readmsint(&mbm->nofleft); }
  }
else if (0 == strcmp("nofright", token))
  {
  if (!err) { err = readmsint(&mbm->nofright); }
  }
else
  {
  if (!err) { err = mscriptskipvalue(token); }  /* skip unknown fields. */
  }
return err;
}
readmsbeam() is a typical function that is called by readmsstruct(). readmsint() reads an integer value.
Reading an array is very similar to reading a structure, but a bit more complex, and you have to allocate memory.