#/*************************************************
*    The PMW Music Typesetter - 2nd incarnation  *
*************************************************/

/* Copyright (c) Philip Hazel, 1991 - 2010 */

/* Written by Philip Hazel, starting November 1991 */
/* This file last modified: December 2010 */


/* This file contains part I of the code for reading in a PMW score file. */


#include "pmwhdr.h"
#include "readhdr.h"
#include "pagehdr.h"
#include "outhdr.h"




/*************************************************
*         Local static variables                 *
*************************************************/

static int  read_count;
static int  read_staves[stave_bitvec_size];   /* Map of staves actually read */
static int  read_prevstave;                   /* Previous stave number */

static BOOL read_in_quotes;
static BOOL read_stavestart;   /* True if expecting staff start */

/* A dummy page structure specially for the string escape mechanism which is
called to check strings and may need to handle a page number. */

static pagestr dummy_page = { NULL, NULL, NULL, 1, 0, 0, 0 };




/*************************************************
*     Expand string with macros substitutions    *
*************************************************/

/* This is called for input lines, and also for the arguments of nested macro 
calls. It copies the input string, expanding any macros encountered. If a 
comment character is encountered, the rest of the line is copied, for error 
messages, but the returned end value does not include the comment.

Arguments:
  inptr         point to the start of the string
  inend         point one past the end of the string
  outbuffer     where to put the expanded line
  outlen        length of the output buffer
  addNL         if TRUE, add a newline character on the end
  
Returns:        pointer one past the end of the active part of the
                  expanded string in outbuffer
*/     

static uschar *
expand_string(uschar *inptr, uschar *inend, uschar *outbuffer, int outlen,
  BOOL addNL)
{
uschar *temp = outbuffer;
uschar *comment = NULL;

while (inptr < inend)
  {
  int ch = *inptr++;

  /* Keep track of quotes; can't have comments in quotes */

  if (ch == '\"')
    {
    read_in_quotes = !read_in_quotes;
    *temp++ = ch;
    }

  /* After a comment character, just copy over the rest of the input (for error
  messages) noting where the comment started. */

  else if (!read_in_quotes && ch == '@')
    {
    comment = temp;
    *temp++ = ch;
    while (inptr < inend) *temp++ = *inptr++;
    break;
    }

  /* Deal with defined insertions, possibly with arguments */

  else if (ch == '&')
    {
    if (*inptr == '&') *temp++ = *inptr++; else
      {
      int i = 0;
      uschar name[20];
      if (isalnum(*inptr))
        {
        tree_node *s;
        name[i++] = *inptr++;
        while (isalnum(*inptr)) name[i++] = *inptr++;
        name[i] = 0;
        if ((s = Tree_Search(define_tree, name)) != NULL)
          {
          if (s->data != NULL)
            {
            macrostr *mm = (macrostr *)s->data; 
            int k = Ustrlen(mm->text);
            
            /* Optimize the case with no arguments */

            if (mm->argcount == 0)
              { 
              if (outlen - 2 - (temp - next_buffer) < k + (inend - inptr))
                error_moan(88);   /* hard error - buffer overflow */
              Ustrcpy(temp, mm->text);
              temp += k;
              }
            
            /* Otherwise have to process char by char, and read arguments, if
            any. */
               
            else
              {
              int i; 
              int argcount = mm->argcount;
              uschar *pp = mm->text;
              uschar *args[20]; 
              uschar argbuff[2048];
              uschar *ap = argbuff;  
              
              /* Set up the default arguments */
               
              for (i = 0; i < argcount; i++) args[i] = mm->args[i]; 
              
              /* Read given arguments, if any, increasing the count if more
              than the default number. */
              
              if (*inptr == '(')
                {
                for (i = 0;; i++)
                  {
                  int bracount = 0; 
                  BOOL inquotes = FALSE; 
                  uschar *s = ap; 
                  while (++inptr < inend && 
                        ((*inptr != ',' && *inptr != ')') ||
                          bracount > 0 || inquotes))  
                    {
                    int ch = *inptr; 
                    if (ch == '&' && !isalnum(inptr[1])) 
                      *ap++ = *(++inptr); 
                    else
                      { 
                      if (ch == '\"') inquotes = !inquotes; 
                      if (!inquotes)
                        { 
                        if (ch == '(') bracount++;
                          else if (ch == ')') bracount--; 
                        }   
                      *ap++ = ch;
                      } 
                    }
                    
                  if (inptr >= inend) error_moan(99);
                  if (i >= argcount) { args[i] = NULL; argcount++; }
                  if (ap - s > 0) { *ap++ = 0; args[i] = s; }  
                    
                  if (inptr >= inend || *inptr == ')')
                    { inptr++; break; } 
                  } 
                }
                
              /* Process the arguments for nested macro calls */
              
              for (i = 0; i < argcount; i++)
                {
                uschar *new_ap; 
                if (args[i] == NULL || Ustrchr(args[i], '&') == NULL) continue;
                new_ap = expand_string(args[i], args[i] + Ustrlen(args[i]),
                  ap, argbuff + sizeof(argbuff) - ap, FALSE); 
                args[i] = ap;
                ap = new_ap + 1;  /* final zero must remain */
                }  
                
              /* Now copy the replacement, inserting the args */    
                
              while (*pp != 0)
                {
                if (*pp == '&' && isdigit(pp[1]))
                  {
                  int arg = 0;
                  while (isdigit(*(++pp))) arg = arg*10 + *pp - '0';
                  if (*pp == ';') pp++;
                  if (--arg < argcount)
                    {
                    uschar *s = args[arg];
                    if (s != NULL)
                      {
                      if (read_buffersize - 2 - (temp - next_buffer) <
                        Ustrlen(s)) error_moan(88);  /* hard */  
                      Ustrcpy(temp, s);
                      temp += Ustrlen(s);    
                      }   
                    }     
                  }
                else
                  {
                  if (temp - next_buffer + 2 >= outlen) 
                    error_moan(88);    /* hard error - buffer overflow */
                  *temp++ = *pp++;    
                  }       
                }   
              }  
            }
          }
        else error_moan(9, name);              /* Couldn't find name */
        if (*inptr == ';') inptr++;
        }
      else error_moan(8);                      /* Bad uschar after '&' */
      }
    }

  /* Otherwise it is a normal character */

  else *temp++ = ch;
  }

/* For whole lines, keep buffer as a NL-terminated string for debugging */

if (addNL)
  {
  temp[0] = '\n';
  temp[1] = 0;
  }
else *temp = 0;   
  
/* Return the end of the active data */  

return comment? comment : temp;
}   


/*************************************************
*                Read next character             *
*************************************************/

/* This function updates the global variable read_ch with the next character, 
including a newline at the end of each line. It deals with macro expansions and 
preprocessing directives, and it skips comments.

Arguments: none
Returns:   nothing
*/

void 
next_ch(void)
{
for (;;)   /* Loop until a character is obtained or very end is reached */
  {
  int len;

  /* Test for more chars in the current line. If not, return '\n' at end of
  line (it's not in the data because that may actually end with an '@' for a
  comment). */

  if (read_chptr < read_endptr) { read_ch = *read_chptr++; return; }
  if (read_chptr++ == read_endptr) { read_ch = '\n'; return; }

  /* Copy the line just finished into the previous buffer, for use by the error
  printing routine, unless this line was empty. */

  if (this_buffer[0] != 0 && this_buffer[0] != '\n')
    {
    uschar *temp = prev_buffer;
    prev_buffer = this_buffer;
    this_buffer = temp;
    }

  /* Get next line; at end of file, check for missing "fi"s and deal with
  included files. */

  for (;;)
    {
    if (input_file != NULL)
      {  
      if (Ufgets(this_buffer, read_buffersize - 4, input_file) != NULL) break;
      fclose(input_file);
      input_file = NULL;
      }
         
    Ustrcpy(this_buffer, "--- End of file ---");  /* for reflection */
    read_chptr = this_buffer + 19;                /* just in case */
    read_endptr = read_chptr + 1;                 /* nothing left */

    if (read_skipdepth > 0 || read_okdepth > 0) error_moan(18);

    /* Real end */

    if (read_filestackptr <= 0)
      {
      read_EOF = TRUE;
      read_ch = EOF;
      return;
      }

    /* Pop stack at end of included file */
    
    DEBUG(("end of %s: popping include stack\n", main_filename));
    store_free(main_filename);
    main_filename = read_filestack[--read_filestackptr].filename;
    input_file = read_filestack[read_filestackptr].file;
    read_linenumber = read_filestack[read_filestackptr].linenumber;
    read_okdepth = read_filestack[read_filestackptr].okdepth;
    read_skipdepth = 0;
    }

  /* Another line has been read; take care with the final one which may not
  have a newline on the end. */
  
  read_linenumber++;
  len = Ustrlen(this_buffer);

  read_count += len;
  read_chptr  = this_buffer;
  read_endptr = this_buffer + len;

  if (len >= read_buffersize - 5) error_moan(81);  /* give-up error */

  if (len > 0 && read_endptr[-1] == '\n') read_endptr--;

  /* Scan the line for comment and defined names, copying into the next buffer.
  The working buffer is always this_buffer, so that error messages can reflect
  it. However, if skipping lines, skip this processing too. */

  if (read_skipdepth <= 0)
    {
    uschar *temp;
    read_endptr = expand_string(read_chptr, read_endptr, next_buffer, 
      read_buffersize, TRUE);
      
    /* Swap this buffer and next buffer, initialize pointer. */

    temp = this_buffer;
    this_buffer = next_buffer;
    next_buffer = temp;
    read_chptr = this_buffer;
    }

  /* If this buffer begins with '*', it is a pre-processing directive. We
  process it, and treat as a null line. Set up read_ch before preprocessing,
  so that code can call normal item reading routines. Clear the in-quotes flag,
  because a *define can legitimately have unmatched quotes, and no preprocessor
  directive can in any case have a quoted string that runs over onto the next
  line. */

  while (*read_chptr == ' ' || *read_chptr == '\t') read_chptr++;
  if (*read_chptr++ == '*')
    {
    if (isalpha(read_ch = *read_chptr++)) pre_process(); else error_moan(12);
    read_chptr = read_endptr;
    read_in_quotes = FALSE;
    }
  else read_chptr = (read_skipdepth > 0)? read_endptr : this_buffer;
  }
}





/*************************************************
*                Read next word                  *
*************************************************/

/*
Argument:   where to put the word
Returns:    nothing
*/

void 
read_word(uschar *word)
{
int i = 0;
sigch();
if (isalpha(read_ch))
  {
  do
    {
    word[i++] = tolower(read_ch);
    next_ch();
    }
  while (isalnum(read_ch));
  }
word[i] = 0;
}




/*************************************************
*             Read plain string                  *
*************************************************/

/* This procedure is used for reading file names and the like
in heading directives. These are in quotes, but are not to be
interpreted as PMW strings. 

Argument:    where to put the string
Returns:     TRUE if OK, FALSE if quote missing
*/

BOOL 
read_plainstring(uschar *s)
{
sigch();
if (read_ch != '\"') return FALSE;
next_ch();
while (read_ch != '\"' && read_ch != '\n')
  {
  *s++ = read_ch;
  next_ch();
  }
*s = 0;
if (read_ch == '\"') next_ch();
  else error_moan(16, "Terminating quote missing");
return TRUE;
}




/*************************************************
*      Read integer or fixed point number        *
*************************************************/

/* Called when the first character is known to be a digit or a dot.

Argument:    TRUE to read a fixed point number; FALSE for an integer
Returns:     the value 
*/

int 
read_integer(BOOL fixed)
{
int yield = 0;

while (isdigit(read_ch))
  {
  yield = yield*10 + read_ch - '0';
  next_ch();
  }

if (fixed)
  {
  yield *= 1000;
  if (read_ch == '.')
    {
    int d = 100;
    while (next_ch(), isdigit(read_ch))
      {
      yield += (read_ch - '0')*d;
      d /= 10;
      }
    }
  }

return yield;
}




/*************************************************
*         Read an expected int or fixed          *
*************************************************/

/* This is called when the first character hasn't been
checked, and an error indication is required if the value is missing.

Arguments:
  yield       where to put the value
  fixed       TRUE for a fixed point value, FALSE for an integer
  allowsign   TRUE if a sign is permitted
  
Returns:      TRUE if OK, FALSE on error
*/

BOOL 
read_expect_integer(int *yield, int fixed, int allowsign)
{
int sign = 1;
sigch();

if (allowsign)
  {
  if (read_ch == '+') next_ch();
    else if (read_ch == '-') { sign = -1; next_ch(); }
  }

if (!isdigit(read_ch))
  {
  error_moan(10, allowsign? "Number" : "Unsigned number");
  return FALSE;
  }

*yield = sign * read_integer(fixed);
return TRUE;
}



/*************************************************
*       Read an expected movement dimension      *
*************************************************/

/* This function is called after /u, /d, /l, or /r has been read.

Arguments:   none
Returns:     the value, or zero after an error
*/

int 
read_movevalue(void)
{
int x;
next_ch();
return (read_expect_integer(&x, TRUE, FALSE))? x : 0;
}





/*************************************************
*             Read key signature                 *
*************************************************/

/*
Arguments:  none
Returns:    the keysignature or zero on error
*/

int 
read_key(void)
{
int key = 0;
sigch();
read_ch = tolower(read_ch);
if ('a' <= read_ch && read_ch <= 'g')
  {
  key = read_ch - 'a';
  next_ch();
  if (read_ch == '#')      { key += 7; next_ch(); }
    else if (read_ch == '$') { key += 14; next_ch(); }
  if (tolower(read_ch) == 'm') { key += 21; next_ch(); }
  if (main_keysigtable[key] == -100) { key = 2; error_moan(24); }
  }
else error_moan(10, "Key signature");
return key;
}



/*************************************************
*            Double/halve time signature         *
*************************************************/

/* The global variables main_notenum and main_noteden contain the numerator and 
denominator of any time scaling that has been set up.

Argument:   the time signature
Returns:    the scaled time signature
*/

int 
read_scaletime(int ts)
{
int m, n, d;
if (main_notenum == 1 && main_noteden == 1) return ts;

m = (ts & 0xffff0000) >> 16;      /* multiplier */
n = (ts & 0x0000ff00) >> 8;       /* numerator */
d = (ts & 0x000000ff);            /* denominator */

if (d == time_common || d == time_cut)
  {
  m *= main_notenum;
  if (main_noteden > 1)
    {
    if (m%main_noteden == 0) m /= main_noteden;
      else error_moan(102); 
    }    
  return (m << 16) | d;  
  }

d *= main_noteden;
if (d % main_notenum == 0) d /= main_notenum;
  else n *= main_notenum;
  
return (m << 16) + (n << 8) + d; 
}



/*************************************************
*              Read time signature               *
*************************************************/

/* Return zero on any error. A time signature can be of the form m*n/d.

Arguments:  none
Returns:    the packed up time signature
*/

int 
read_time(void)
{
int gotnum = FALSE;
int m, n, d;

sigch();
if (isdigit(read_ch))
  {
  (void)read_expect_integer(&m, FALSE, FALSE);
  sigch();
  if (read_ch == '*') next_ch(); else
    {
    n = m;
    m = 1;
    gotnum = TRUE;
    }
  }
else m = 1;

if (!gotnum)
  {
  read_ch = tolower(read_ch); 
  if (read_ch == 'a' || read_ch == 'c')
    {
    int type = (read_ch == 'c')? time_common : time_cut;
    next_ch();
    return read_scaletime((m << 16) | type);
    }
  if (!read_expect_integer(&n, FALSE, FALSE)) return 0;
  }
   
sigch();
if (read_ch != '/')
  {
  error_moan(10, "\"/\"");
  return 0;
  }

next_ch();
if (!read_expect_integer(&d, FALSE, FALSE)) return 0;

if (d < 1 || d > 64 || d != (d & (-d))) error_moan(87); /* hard error */

return read_scaletime((m << 16) | (n << 8) | d);
}



/*************************************************
*      Compute barlength from time signature     *
*************************************************/

/* 
Argument:  the time signature
Returns:   the bar length
*/

int 
read_compute_barlength(int ts)
{
int m = (ts >> 16) & 255;
int n = (ts >> 8) & 255;
int d = ts & 255;

if (d == time_common) n = d = 4;
  else if (d == time_cut) n = d = 2;

return n * m * (len_semibreve/d);
}



/*************************************************
*       Get MIDI number from string              *
*************************************************/

/* This function scans a list of MIDI voice or percussion names to find the 
number for a given name. If we are not generating MIDI (midi_filename is NULL), 
there is no search, but we do not give an error. Just return zero.

Arguments:
  list        the list of MIDI names
  string      the string to search for
  text        text for use in the error message
  
Returns:      the MIDI number
*/   

int 
read_getmidinumber(uschar *list, uschar *string, uschar *text)
{
int yield = -1;

if (list != NULL) while (*list)
  {
  int len = Ustrlen(list); 
  if (Ustrcmp(list, string) == 0)
    {
    yield = list[len+1];
    break;  
    }
  list += len + 2;      
  } 
  
if (yield < 0)
  {
  if (midi_filename != NULL) error_moan(110, text, string); 
  yield = 0;
  }   

return yield;
}




/*************************************************
*      Start of movement initialization          *
*************************************************/

/* This function resets values that must be reset at the start of every 
movement. Other per-movement data is initialized to default values (the 
init_movtstr variable) for the first movement, then copied to subsequent 
movements.

Argument:   option for type of movement (same page, new page, etc)
Returns:    nothing
*/

static 
void init_movt(int opt)
{
int i;
curmovt->stavetable = store_Xget((max_stave+1) * sizeof(stavestr *));

stavetable = curmovt->stavetable;
for (i = 0; i <= max_stave; i++) stavetable[i] = NULL;

curmovt->barcount = 0;
curmovt->baroffset = 0;
curmovt->barnovector = NULL;           /* Got when first stave encountered */
curmovt->heading = curmovt->footing = NULL;
curmovt->key = 2;                      /* C major */
curmovt->laststave = -1;
curmovt->movt_opt = opt;
curmovt->notespacing = store_Xget(8*sizeof(int));
memcpy(curmovt->notespacing, main_notespacing, 8*sizeof(int));

curmovt->layout = NULL;
curmovt->play_changes = NULL;
curmovt->play_tempo_changes = NULL;
curmovt->posvector = NULL;
curmovt->showtime = TRUE;
curmovt->startbracketbar = 0;
curmovt->startnotime = FALSE;
memcpy(curmovt->staves, main_staves, stave_bitvec_size*sizeof(int));
curmovt->time = 0x00010404;            /* 1*4/4 */
curmovt->transpose = main_transpose;
curmovt->unfinished = FALSE;

read_copied_fontsizestr = FALSE;
read_headcount = 1;        /* Very first size for first movement only */
read_headmap = 0;
read_lastplaychange = &(curmovt->play_changes);
main_notenum = main_noteden = 1;
read_prevstave = 0;        /* previous stave (fudged when stave 0 read) */

mac_initstave(curmovt->suspend, 0);
mac_initstave(read_staves, 0);

error_skip = skip_EOL;     /* Where to skip for some errors */

/* In case there are transposed text strings in the headers, ensure the
appropriate stave data is fudged. */

stave_key = curmovt->key;
stave_transpose = curmovt->transpose;
stave_key_tp = transpose_key(stave_key, stave_transpose, TRUE);
}



/*************************************************
*         Handle end of movement                 *
*************************************************/

/* Called to tidy up at the end of each movement.

Arguments:   none
Returns:     nothing
*/

static void 
read_endmovement(void)
{
int i;
for (i = 0; i < stave_bitvec_size; i++)
  curmovt->staves[i] &= read_staves[i];      /* Disable those not read */

/* If there was a playtempo directive, adjust the bar numbers so that they are
internal rather than external values */

if (curmovt->play_tempo_changes != NULL)
  {
  int *p = curmovt->play_tempo_changes;

  while (*p < BIGNUMBER)
    {
    uschar *bv = curmovt->barnovector;
    int i;
    int n = (*p)/1000 - curmovt->baroffset;
    int f = (*p)%1000;

    if ((f%10) == 0)
      {
      f = ((f%100) == 0)? (f/100) : (f/10);
      }

    if (n <= 0) n = 1; else
      {
      for (i = 1; i <= n; i++) if (bv[i] < bv[i+1]) n++;
      }
    *p = n + f;
    p += 2;
    }
  }
}




/*************************************************
*         Top-level reading control              *
*************************************************/

/* Called from the main control function. This function reads the input stream, 
alternating between heading directives and music staves for the different 
movements that are present.

Arguments:  none
Returns:    nothing
*/

void 
read_go(void)
{
DEBUG(("read_go() start\n"));

/* Deal with reading the heading part of a movement. We jump back here when a
new movement is encountered. */

READ_HEADING:
for (;;)
  {
  next_heading();
  if (main_rc >= rc_failed) break;

  /* The appearance of '[' indicates the start of the stave data. Set up things
  that can't be done till now. */

  if (read_ch == '[')
    {
    int j;
    curmovt->barnovector = store_Xget(curmovt->maxbarcount+1);
    for (j = 0; j <= curmovt->maxbarcount; j++) curmovt->barnovector[j] = 0;
    read_stavestart = TRUE;
    error_skip = skip_KET;  /* Where to skip for some errors */
    break;
    }

  /* End of file before any staff data has been read. */

  else if (read_ch == EOF) break;
  }

/* Loop to deal with reading the stave part of a movement. */

if (main_rc < rc_failed) for (;;)
  {
  sigch();
  if (read_ch == EOF || main_rc >= rc_failed) break;

  /* Deal with starting a new stave or movement. */

  if (read_stavestart)
    {
    uschar word[100];
    if (read_ch != '[') { error_moan(29); break; }
    next_ch();
    read_word(word);


    /* It's another stave. Check that a valid number is given. If so, set up
    the index for its bars and get ready to read the rest of the data. */

    if (Ustrcmp(word, "stave") == 0 || Ustrcmp(word, "staff") == 0)
      {
      int k;
      if (!read_expect_integer(&curstave, FALSE, FALSE))
        {
        error_moan(30, "Stave number");  /* Failed */
        break;
        }

      /* Error if number too big or stave supplied twice */

      if (curstave > max_stave) 
        { error_moan(22, max_stave+1); break; }  
      if (stavetable[curstave] != NULL)
        { error_moan(31, curstave); break; }

      /* Staves must be in order; missing staves are inserted as empty, except
      for stave 0, which is just omitted. This is historical really. The rest
      of PMW assumes contiguous staves, and to allow some to be missing, it is
      less boat-rocking to do it this way. */

      if (curstave == 0) read_prevstave = -1;

      for (k = read_prevstave+1; k <= curstave; k++)
        {
        int j;
        mac_setstave(read_staves, k);
        if (k > curmovt->laststave) curmovt->laststave = k;

        /* Set up the data structure for the stave */

        stavehead = stavetable[k] = store_Xget(sizeof(stavestr));
        stavehead->stave_name = NULL;
        stavehead->lastbar = 0;
        stavehead->toppitch = -1;
        stavehead->botpitch = 9999;
        stavehead->totalpitch = 0;
        stavehead->notecount = 0;
        stavehead->omitempty = FALSE;
        stavehead->stavelines = 5;
        stavehead->barlinestyle = 255;   /* "unset" */

        barvector = stavehead->barindex =
          store_Xget((curmovt->maxbarcount+1) * sizeof(bstr *));
        for (j = 0; j <= curmovt->maxbarcount; j++) barvector[j] = NULL;
        }

      read_prevstave = curstave;

      /* Initialize miscellaneous variables */

      stave_accritvalue = 3;
      stave_couplestate = stave_octave = 0;
      stave_notes = TRUE;
      stave_noteflags = (curstave == 0)? nf_hidden : 0;
      stave_matchnum = 0; /* indicates "unset" */
      stave_totalnocount = stave_slurcount = 
        stave_hairpinbegun = stave_hairpiny = 0;
      stave_stemforce = stave_ties = stave_textflags = 0;
      stave_textabsolute = 0;
      stave_accentflags = stave_restlevel = stave_stemlength = 0;
      stave_pletflags = stave_plety = 0;
      stave_ornament = -1;
      stave_printpitch = 0; 
      stave_stemflag = nf_stem;
      stave_pendulay = NULL;
      stave_hairpinflags = hp_below;
      stave_hairpinwidth = curmovt->hairpinwidth; 

      stave_clef = clef_treble;
      stave_clef_octave = 0;
      stave_barlinestyle = curmovt->barlinestyle; 

      stave_transposedaccforce = main_transposedaccforce;
      stave_transpose = curmovt->transpose;
      stave_key = curmovt->key;       /* Original key */

      /* We have to call transpose_key in order to get the value for the letter
      transformation set, even if there is a forced key signature */

      stave_key_tp = transpose_key(stave_key, stave_transpose, TRUE);

      stave_requiredbarlength = read_compute_barlength(curmovt->time);

      stave_beaming = stave_lastwastied = stave_lastwasdouble = FALSE;
      stave_laststemup = TRUE;
      stave_smove = stave_octave = 0;

      stave_minpitch = 999;
      stave_maxpitch = stave_pitchtotal = stave_pitchcount = 0;
      stave_lastbasenoteptr = NULL;

      stave_fbfont = stave_ulfont = stave_olfont = font_rm;
      stave_textfont = font_it;

      stave_textsize = 0;
      stave_olsize = ff_offset_olay;
      stave_ulsize = ff_offset_ulay;
      stave_fbsize = ff_offset_fbass;

      stave_suspended = mac_teststave(curmovt->suspend, curstave);
      stave_stemswaplevel = curmovt->stemswaplevel;
      
      read_prev_had_dbar = read_prev_had_ibar = FALSE;
      read_prev_barlinestyle = stave_barlinestyle;  


      /* Set the bar number, and enter the normal reading state, with a
      pre-fixed "name" directive if necessary. */

      stave_barnumber = 1;
      read_stavestart = read_endstave = FALSE;
      sigch();
      if (read_ch == '\"' || (read_ch == 'd' && Ustrncmp(read_chptr, "raw ", 4) == 0)) 
        Ustrcpy(read_stavedir, "name");
          else read_stavedir[0] = 0;
      read_chptr--;
      read_ch = '[';
      }


    /* It's another movement. Check for options, set up a new movement control
    block, and change state. */

    else if (Ustrcmp(word, "newmovement") == 0)
      {
      int opt = movt_default;
      int ph = 0;
      int lf = 0; 
      movtstr *newcurmovt;
      
      if (main_lastmovement >= main_max_movements)
        { error_moan(120, main_max_movements); break; }   /* disaster */

      sigch();
      while (read_ch != ']')
        {
        read_word(word);
        sigch();
        if (Ustrcmp(word, "thispage") == 0) opt = movt_thispage;
        else if (Ustrcmp(word, "thisline") == 0) opt = movt_thisline;
        else if (Ustrcmp(word, "newpage") == 0) opt = movt_newpage;
        else if (Ustrcmp(word, "nopageheading") == 0) ph = movt_nopageheading;
        else if (Ustrcmp(word, "uselastfooting") == 0) lf = movt_uselastfooting; 
        else error_moan(10,
          "\"thispage\", \"thisline\", \"newpage\", \"nopageheading\", or \"uselastfooting\"");
        }
      next_ch();
      read_endmovement();                  /* Tidy up */
      newcurmovt = movement[++main_lastmovement] = store_Xget(sizeof(movtstr));
      *newcurmovt = *curmovt;
      format_movt = curmovt = newcurmovt;
      init_movt(opt | ph | lf);
      goto READ_HEADING;         /* Jump back to start of function */
      }

    /* It's a disastrous error */

    else { error_moan(29); break; }
    }

  /* Otherwise we are in the middle of reading a stave's data. Read another
  bar. FALSE is yielded at the end of the staff. */

  else
    {
    int oldnocount = stave_totalnocount;
    BOOL more = read_bar();
    int nocount = (stave_totalnocount > oldnocount)? 1:0;

    /* If there was a repeat count in the bar, set up additional pointers to
    the repeated part of the bar and handle nocounts. */

    while (stave_barrepeatcount--)
      {
      if (++stave_barnumber > curmovt->maxbarcount)
        { error_moan(36, curmovt->maxbarcount); more = FALSE; break; }

      if (stave_totalnocount > (curmovt->barnovector)[stave_barnumber])
        (curmovt->barnovector)[stave_barnumber] = stave_totalnocount;

      stave_totalnocount += nocount;
      (stavehead->barindex)[stave_barnumber] = stave_barrepeatptr;
      }

    /* Increase the bar number and set the high water mark if there is more or
    if this is a non-empty end-of-stave bar. */

    if (more || (stavehead->barindex)[stave_barnumber] != NULL)
      stavehead->lastbar = stave_barnumber++;

    /* Deal with reaching the end of the stave */

    if (!more)
      {
      if (stavehead->lastbar > curmovt->barcount)
        curmovt->barcount = stavehead->lastbar;

      if (stave_pitchcount)
        {
        stavehead->toppitch = stave_maxpitch;
        stavehead->botpitch = stave_minpitch;
        stavehead->totalpitch = stave_pitchtotal;
        stavehead->notecount = stave_pitchcount;
        }
      else stavehead->toppitch = stavehead->botpitch = 0;

      if (stave_totalnocount > curmovt->totalnocount)
        curmovt->totalnocount = stave_totalnocount;

      if (stave_pendulay != NULL)
        {
        error_moan(65, curstave);   /* Give warning */
        while (stave_pendulay != NULL)
          {
          ulaypend *next = stave_pendulay->next;
          store_free(stave_pendulay);
          stave_pendulay = next;
          }
        }

      read_stavestart = TRUE;    /* Flag to expect a new stave or movement */
      }
    }
  }

read_endmovement();            /* Terminate the final movement */
store_free(next_buffer);
store_free(this_buffer);
store_free(prev_buffer);
store_free(read_stavedir);

if (input_file != NULL) fclose(input_file);
while (read_filestackptr > 0) fclose(read_filestack[--read_filestackptr].file);
  
/* Warn if the format word was never tested. By giving the error after
resetting reading_input, it won't try to reflect the input line. */

reading_input = FALSE;
if (main_format[0] != 0 && !main_format_tested) error_moan(104, main_format);
  
DEBUG(("read_go() end\n"));
}



/*************************************************
*          Set up for reading routines           *
*************************************************/

/* This function is called at the start of reading. 

Arguments:  none
Returns:    nothing
*/

void 
read_start(void)
{
int i;

/* Stack for included files */

read_filestack = store_Xget(max_include*sizeof(filestr));

/* Set up buffers for line reading */

read_stavedir = store_Xget(64);
next_buffer = store_Xget(read_buffersize);
this_buffer = store_Xget(read_buffersize);
prev_buffer = store_Xget(read_buffersize);
this_buffer[0] = prev_buffer[0] = 0;

/* Variables for controlling reading of the file */

read_filestackptr = 0;
read_linenumber = read_count = error_count = main_rc = 0;
read_skipdepth = read_okdepth = 0;
read_EOF = FALSE;
read_in_quotes = FALSE;
read_chptr = this_buffer + 1;
read_endptr = this_buffer;
read_ch = ' ';

/* Default start of movement note spacing */

memcpy(main_notespacing, init_notespacing, 8*sizeof(int));

/* Initialize movement table for first movement */

for (i = 0; i <= main_max_movements; i++) movement[i] = NULL;
curmovt = movement[1] = store_Xget(sizeof(movtstr));
*curmovt = init_movtstr;       /* Default settings */
init_movt(movt_default);       /* Reset things that need resetting */
read_headcount = 0;            /* First size for movement 1 only */

/* Set available fonts to those read from the font list file, and set the
default font allocations. */

font_count = font_basecount;

font_table[font_rm] = font_search(US"Times-Roman");
font_table[font_it] = font_search(US"Times-Italic");
font_table[font_bf] = font_search(US"Times-Bold");
font_table[font_bi] = font_search(US"Times-BoldItalic");
font_table[font_sy] = font_search(US"Symbol");
font_table[font_mf] = font_search(main_musicchoice);
font_table[font_mu] = font_search(main_musicchoice);

for (i = font_xx; i < font_xx + MaxExtraFont; i++)
  font_table[i] = font_search(US"Times-Roman");

if (font_table[font_rm] < 0 || font_table[font_mf] < 0)
  error_moan(25);    /* Hard */
  
/* Point current page at a dummy (relevant for string escape checking) */

curpage = &dummy_page; 

/* Miscellaneous start-of-file initialization */

baraccs = store_Xget(baraccs_len);
baraccs_tp = store_Xget(baraccs_len);
stave_tiedata = store_Xget(max_chordsize * sizeof(tiedata));

stave_beamstack = store_Xget(beamstacksize);
stave_stemstack = store_Xget(stemstacksize);

page_postable = store_Xget(max_postablesize * sizeof(workposstr));

define_tree = draw_tree = draw_variable_tree = NULL;
draw_thickness = 500;
draw_nextvariable = 0;

opt_landscape = opt_oldbeambreak = opt_oldrestlevel =
  opt_oldstemlength = FALSE;

main_firstpage = main_pageinc = 1;
main_format_tested = FALSE;
main_htypes = NULL;
main_kerning = TRUE;
main_lastmovement = 1;
main_magnification = 1000;
main_maxvertjustify = 60000;
main_pageanchor = NULL;
main_printkey = NULL;
main_printtime = NULL;
main_pssetup = NULL;
main_sheetheight = 842000;
main_sheetwidth = 595000; 
opt_sheetsize = sheet_A4;
opt_stretchrule = 2;               /* The latest rule */
main_transposedaccforce = TRUE;
main_transposedkeys = NULL;
main_truepagelength = 720000;

draw_lgx = draw_lgy = 0;

out_depthvector = store_Xget((max_stave+1) * sizeof(int *));

font_reset();        /* To set no transformation */
font_xstretch = 0;   /* No justification */

/* Finally, enter the reading state. This affects the form of error messages. */

reading_input = TRUE;
}

/* End of read1.c */
