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

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

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


/* 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 usint read_staves[STAVE_BITVEC_SIZE];   /* Map of staves actually read */
static int   read_prevstave;                   /* Previous stave number */
static int   read_count;

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 };

/* Default "macro" for the &* replication feature */

static macrostr replicate_macro = { 1, US"&1", { US"" } };




/*************************************************
*     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. Also deal with &*()
  repetitions, which uses very similar code. */

  else if (ch == '&')
    {
    if (*inptr == '&') *temp++ = *inptr++; else
      {
      macrostr *mm = NULL;
      BOOL had_semicolon = FALSE;
      usint count = 1;

      /* Set up for a macro call. Note that macro names may start with a digit
      and are case-sensitive, so we can't use next_word(). */

      if (isalnum(*inptr))
        {
        int i = 0;
        tree_node *s;
        uschar name[WORD_BUFFERSIZE];

        name[i++] = *inptr++;
        while (isalnum(*inptr))
          {
          if (i >= WORD_BUFFERSIZE - 1)
            error_moan(ERR136, "Macro name", WORD_BUFFERSIZE - 1);  /* Hard */
          name[i++] = *inptr++;
          }
        name[i] = 0;

        if (*inptr == ';')
          {
          inptr++;                /* Optional semicolon after name is */
          had_semicolon = TRUE;   /*   skipped, and no args allowed */
          }

        if ((s = Tree_Search(define_tree, name)) != NULL)
          {
          mm = (macrostr *)s->data;  /* Will be NULL for no replacement */
          if (mm == NULL) goto END_MACRO;
          }
        else
          {
          error_moan(ERR9, name);              /* Couldn't find name */
          goto END_MACRO;
          }
        }

      /* Set up for a replication call */

      else if (*inptr == '*')
        {
        int len;
        if (sscanf((char *)(++inptr), "%u(%n", &count, &len) <= 0)
          {
          error_moan(ERR10, "Unsigned number followed by \"(\"");
          goto END_MACRO;
          }
        inptr += len - 1;
        mm = &replicate_macro;
        }

      else
        {
        error_moan(ERR8);                      /* Bad uschar after '&' */
        goto END_MACRO;
        }

      /* Found a macro or &*<digits>. For a macro, mm points to its data, and
      count is 1. For a replication, mm points to a dummy with 1 empty default
      argument, count contains the replication count, and we know there is an
      argument. */

      /* Optimize the case when macro is defined with no arguments */

      if (mm->argcount == 0)
        {
        int k = Ustrlen(mm->text);
        if (outlen - 2 - (temp - outbuffer) < k + (inend - inptr))
          error_moan(ERR88);   /* hard error - buffer overflow */
        Ustrcpy(temp, mm->text);
        temp += k;
        }

      /* Otherwise we have to process char by char, and read arguments, if
      any. There need not be; they can all be defaulted. Arguments are read,
      serially, into argbuff, and then expanded for nested macros into what
      remains of argbuff. */

      else
        {
        int i;
        int argcount = mm->argcount;
        uschar *args[MAX_MACROARGS];
        uschar argbuff[4*READ_BUFFERSIZE];
        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 (!had_semicolon && *inptr == '(')
          {
          for (i = 0;; i++)
            {
            int bracount = 0;
            BOOL inquotes = FALSE;
            uschar *ss = ap;

            if (argcount >= MAX_MACROARGS)
              error_moan(ERR137, MAX_MACROARGS);  /* Hard */

            while (++inptr < inend &&
                  ((*inptr != ',' && *inptr != ')') ||
                    bracount > 0 || inquotes))
              {
              int cch = *inptr;
              if (cch == '&' && !isalnum(inptr[1]) && inptr[1] != '*')
                *ap++ = *(++inptr);
              else
                {
                if (cch == '\"') inquotes = !inquotes;
                if (!inquotes)
                  {
                  if (cch == '(') bracount++;
                    else if (cch == ')') bracount--;
                  }
                *ap++ = cch;
                }
              }

            if (inptr >= inend) error_moan(ERR99);

            if (i >= argcount)
              {
              args[i] = NULL;
              argcount++;
              }

            if (ap - ss > 0)
              {
              *ap++ = 0;
              args[i] = ss;
              }

            if (inptr >= inend || *inptr == ')')
              {
              inptr++;
              break;
              }
            }

          /* Only one argument is currently allowed for a replication. Any
          others are ignored. */

          if (mm == &replicate_macro && argcount > 1) error_moan(ERR134);
          }

        /* 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. For a replication we
        repeat many times. For a macro, count is always 1. */

        while (count-- > 0)
          {
          uschar *pp = mm->text;

          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 *ss = args[arg];
                if (ss != NULL)
                  {
                  if (READ_BUFFERSIZE - 2 - (temp - outbuffer) <
                    Ustrlen(ss)) error_moan(ERR88);  /* hard */
                  Ustrcpy(temp, ss);
                  temp += Ustrlen(ss);
                  }
                }
              }
            else
              {
              if (temp - outbuffer + 2 >= outlen)
                error_moan(ERR88);    /* hard error - buffer overflow */
              *temp++ = *pp++;
              }
            }
          }
        }

      /* Macro/replication processing is done */

      END_MACRO: continue;
      }
    }

  /* 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 logical line, joining together physical lines that end with &&&.
  At end of file, check for missing "fi"s and deal with included files. */

  for (;;)  /* Loop for included files */
    {
    if (input_file != NULL)
      {
      BOOL line_read = FALSE;
      uschar *tbuffer = this_buffer;
      int size = READ_BUFFERSIZE - 4;

      len = 0;
      for (;;)  /* Loop for concatenated lines */
        {
        if (Ufgets(tbuffer, size, input_file) != NULL)
          {
          int tlen = Ustrlen(tbuffer);
          line_read = TRUE;
          read_linenumber++;
          len += tlen;
          if (tlen < 4 || Ustrcmp(tbuffer + tlen - 4, "&&&\n") != 0)
            goto PROCESS_LINE;
          len -= 4;
          tlen -= 4;
          tbuffer += tlen;
          *tbuffer = 0;
          size -= tlen;
          }
        else  /* Reached EOF */
          {
          if (line_read) goto PROCESS_LINE; else break;
          }
        }

      /* No lines read */

      fclose(input_file);
      input_file = NULL;
      }

    /* Handle reaching the end of an input file */

    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(ERR18);

    /* 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. Nor will a final line that is just "&&&\n". */

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

  if (len >= READ_BUFFERSIZE - 5) error_moan(ERR81);  /* 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(ERR12);
    read_chptr = read_endptr;
    read_in_quotes = FALSE;
    }
  else read_chptr = (read_skipdepth > 0)? read_endptr : this_buffer;
  }
}





/*************************************************
*          Read and lowercase next word          *
*************************************************/

/*
Returns:    nothing
*/

void
next_word(void)
{
int i = 0;
sigch();
if (isalpha(read_ch))
  {
  do
    {
    if (i >= WORD_BUFFERSIZE - 1)
      error_moan(ERR136, "Word", WORD_BUFFERSIZE - 1);  /* Hard */
    read_word[i++] = tolower(read_ch);
    next_ch();
    }
  while (isalnum(read_ch) || read_ch == '_');
  }
read_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. They are stored in read_word.

Returns:     TRUE if OK, FALSE if starting quote missing
*/

BOOL
read_plainstring(void)
{
int i = 0;
sigch();
if (read_ch != '\"') return FALSE;
next_ch();
while (read_ch != '\"' && read_ch != '\n')
  {
  if (i >= WORD_BUFFERSIZE - 1)
    error_moan(ERR136, "String", WORD_BUFFERSIZE - 1);  /* Hard */
  read_word[i++] = read_ch;
  next_ch();
  }
read_word[i] = 0;
if (read_ch == '\"') next_ch();
  else error_moan(ERR16, "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(ERR10, 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 key signature (C major after an error)
*/

int
read_key(void)
{
int key = C_key;

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 = C_key; error_moan(ERR24); }
  }
else if (read_ch == 'n') { key = N_key; next_ch(); }

else if (read_ch == 'x')
  {
  int n;
  next_ch();
  if (!read_expect_integer(&n, FALSE, FALSE) || n == 0 || n > MAX_XKEYS)
    error_moan(ERR144, MAX_XKEYS);
  else
    key = X_key + n - 1;
  }

else error_moan(ERR10, "Key signature");
return key;
}



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

/* The global variables main_notenum and main_noteden contain the numerator and
denominator of any note scaling (doublenotes, halvenotes) that was set up in
the movement's header. This affects time signatures. The stave directives
[doublenotes] and [halvenotes] do not affect time signatures.

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(ERR102);
    }
  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)
{
BOOL 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(ERR10, "\"/\"");
  return 0;
  }

next_ch();
if (!read_expect_integer(&d, FALSE, FALSE)) return 0;
if (d < 1 || d > 64 || d != (d & (-d))) error_moan(ERR87); /* 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(ERR110, 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->number = main_lastmovement;
curmovt->barcount = 0;
curmovt->baroffset = 0;
curmovt->barnovector = NULL;           /* Got when first stave encountered */
curmovt->heading = curmovt->footing = NULL;
curmovt->key = C_key;                  /* 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);
}



/*************************************************
*         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 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();

  /* 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. */

for (;;)
  {
  sigch();
  if (read_ch == EOF) break;

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

  if (read_stavestart)
    {
    if (read_ch != '[') { error_moan(ERR29); break; }
    next_ch();
    next_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(read_word, "stave") == 0 || Ustrcmp(read_word, "staff") == 0)
      {
      int k;
      if (!read_expect_integer(&curstave, FALSE, FALSE))
        {
        error_moan(ERR30, "Stave number");  /* Failed */
        break;
        }

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

      if (curstave > MAX_STAVE)
        { error_moan(ERR22, MAX_STAVE+1); break; }
      if (stavetable[curstave] != NULL)
        { error_moan(ERR31, 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, (usint)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_notenum = main_notenum;
      stave_noteden = main_noteden;
      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);

      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_word, "name");
        read_stavedir = TRUE;
        }
      read_chptr--;
      read_ch = '[';
      }

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

    else if (Ustrcmp(read_word, "newmovement") == 0)
      {
      int opt = movt_default;
      int ph = 0;
      int lf = 0;
      movtstr *newcurmovt;

      if (main_lastmovement >= main_max_movements)
        { error_moan(ERR120, main_max_movements); break; }   /* disaster */

      sigch();
      while (read_ch != ']')
        {
        next_word();
        sigch();
        if (Ustrcmp(read_word, "thispage") == 0) opt = movt_thispage;
        else if (Ustrcmp(read_word, "thisline") == 0) opt = movt_thisline;
        else if (Ustrcmp(read_word, "newpage") == 0) opt = movt_newpage;
        else if (Ustrcmp(read_word, "nopageheading") == 0) ph = movt_nopageheading;
        else if (Ustrcmp(read_word, "uselastfooting") == 0) lf = movt_uselastfooting;
        else error_moan(ERR10,
          "\"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(ERR29); 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(ERR36, 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(ERR65, 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_word);

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(ERR104, 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_word = store_Xget(WORD_BUFFERSIZE);
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_stavedir = 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));

/* All custom keys default to no key signature. */

memset(main_xkeys, 0, sizeof(main_xkeys));

/* Initialize movement table for first movement */

main_lastmovement = 1;
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_tablen; i++)
  font_table[i] = font_search(US"Times-Roman");

if (font_table[font_rm] < 0 || font_table[font_mf] < 0)
  error_moan(ERR25);    /* 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);
beam_overbeam = FALSE;  /* For misc_nextnote() while reading */

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_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 */
