/*************************************************
*    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 code for handling errors. */

#include "pmwhdr.h"
#include "readhdr.h"




/*************************************************
*            Texts and return codes              *
*************************************************/

typedef struct {
  char rc;
  const char *text;
} error_struct;

static error_struct error_data[] = {

/* 0-4 */
{ rc_failed,   "Failed to decode command line: \"%s\" %s\n" },
{ rc_failed,   "Ran out of memory: increase free memory (e.g. by removing tasks)\n" },
{ rc_failed,   "Internal failure - store overlap upwards (%x %d %x)\n" },
{ rc_failed,   "Internal failure - store overlap downwards (%x %d %x)\n" },
{ rc_failed,   "Unable to open file \"%s\": %s\n" },
/* 5-9 */
{ rc_serious,  "Mis-placed %s (does not immediately follow a note)\n" },
{ rc_serious,  "Wrong data type on stack for \"%s\" while drawing \"%s\" %s\n" },
{ rc_serious,  "Duplicate Unicode code point U+%04x in %s\n   Output will be unpredictable\n" },
{ rc_serious,  "Macro name or string repetition expected after \"&\"\n" },
{ rc_serious,  "The name \"%s\" has not been defined\n" },
/* 10-14 */
{ rc_serious,  "%s expected" },  /* Note: no newline (see #37) */
{ rc_failed,   "Bad -printscale value on command line\n" },
{ rc_serious,  "Pre-processing directive expected\n" },
{ rc_serious,  "Unknown pre-processing directive \"%s\"\n" },
{ rc_serious,  "The name \"%s\" is already defined\n" },
/* 15-19 */
{ rc_serious,  "Included files too deeply nested (max depth is %d)\n" },
{ rc_warning,  "Warning: %s\n" },
{ rc_serious,  "Unexpected %s\n" },
{ rc_serious,  "Missing \"*fi\" at end of file\n" },
{ rc_serious,  "Unknown heading directive \"%s\"" },
/* 20-24 */
{ rc_serious,  "This directive is permitted only in the first movement's heading\nsection" },
{ rc_serious,  "Numbers out of order\n" },
{ rc_failed,   "Stave number too large - must be less than %d\n" },
{ rc_failed,   "Unexpected end of file while reading string\n" },
{ rc_serious,  "Unsupported key signature\n" },
/* 25-29 */
{ rc_failed,   "Unable to find basic font definitions - check font list file\n" },
{ rc_failed,   "Too many different fonts declared (max is %d)\n" },
{ rc_serious,  "Malformed hexadecimal number after \"\\x\": unexpected '%c'\n" },
{ rc_failed,   "Incorrect PMW version (%c%f expected, %f used)\n" },
{ rc_failed,   "Start of stave or new movement expected\n" },
/* 30-34 */
{ rc_failed,   "%s expected\n" },
{ rc_failed,   "Stave %d%M is supplied twice or is out of order\n" },
{ rc_serious,  "Unknown stave directive \"%s\"" },
{ rc_serious,  "Wiggly %s are not supported\n" },
{ rc_serious,  "[reset] may not follow an item which relates to the following note\n" },
/* 35-39 */
{ rc_serious,  "[reset] may not appear inside an irregular note group\n" },
{ rc_failed,   "More than %d bars read%M - use BARCOUNT to increase the limit\n" },
{ rc_serious,  "%s expected\n" },  /* Note: with newline (see #10) */
{ rc_serious,  "Mis-matched curly brackets\n" },
{ rc_serious,  "Invalid text size (a number in the range 1 to %d was expected)\n" },
/* 40-44 */
{ rc_serious,  "Unknown escape character \"%c\" follows \"\\\" - skipping rest of string\n" },
{ rc_failed,   "Unable to access %s: %s\n" },
{ rc_serious,  "Stave number too large - maximum is %d\n" },
{ rc_serious,  "A chord may not contain a rest\n" },
{ rc_serious,  "The notes of a chord must all be the same length\n" },
/* 45-49 */
{ rc_serious,  "Error in note expression or ornament\n" },
{ rc_serious,  "Conflicting stem direction requests %s\n" },
{ rc_serious,  "The \\sw\\ option is only available for beamed notes when the\n"
               "   stem direction of the first note is forced\n" },
{ rc_serious,  "%s must be on the first note of a chord\n" },
{ rc_serious,  "Incorrect length for bar %b, stave %d%M - too %s by %l\n" },
/* 50-54 */
{ rc_failed,   "Internal failure - transposition (%d, %d, %d, %d)\n" },
{ rc_warning,  "Warning: tie after rest ignored\n" },
{ rc_serious,  "Mis-placed curly bracket\n" },
{ rc_serious,  "Character number not in range 32-255\n" },
{ rc_serious,  "%s expected - skipping to end of string\n" },
/* 55-59 */
{ rc_warning,  "Warning: Bar %b%M is too wide for the page at %f points.\n"
               "   Clefs, keys, etc. at the start occupy %f points. The bar will\n"
               "   be compressed to fit within the line width of %f points.\n" },
{ rc_warning,  "Warning: There is insufficient space to print a cautionary key or\n"
               "   time signature at the end of the line, following bar %b%M\n" },
{ rc_failed,   "Internal error - unknown item %d in bar data\n" },
{ rc_warning,  "Warning: Bar %b of stave %d%M has a different length to "
               "bar %b\n   on an earlier stave\n"
               "The expected length of bar %b is %l\n"
               "The actual length of bar %b on stave %d is %l\n" },
{ rc_failed,   "%s file for font \"%s\" not found in \"%s\"\n" },
/* 60-64 */
{ rc_failed,   "Internal failure - position data missing in bar %b, stave %d%M.\n"
               "The musical offset is %d, which is %l.\n" },
{ rc_serious,  "Insufficient space to print notes on opposite sides of beam\n"
               "   in bar %b of stave %d%M\n" },
{ rc_serious,  "Slur or line \"%c\" not found for [endslur], [endline], or [linegap]\n"
               "Detected in bar %b of stave %d%M\n" },
{ rc_serious,  "Attempt to draw slur or line of zero or negative length\n"
               "Detected in bar %b of stave %d%M\n" },
{ rc_serious,  "The musical system starting at bar %b of movement %d is deeper\n"
               "than the page length (by %f point%s) and cannot be handled\n" },
/* 65-69 */
{ rc_warning,  "Warning: There is underlay or overlay text left over at the end of\n"
               "   stave %d%M\n" },
{ rc_failed,   "-printside must specify 1 or 2 on command line\n" },
{ rc_serious,  "[reset] may not occur before the first note of a bar\n" },
{ rc_serious,  "Accidentals, dynamics, ornaments, and irrelevant options may not be specified for rests\n" },
{ rc_serious,  "\"%s\" is not a known variable or operator name in a draw item\n" },
/* 70-74 */
{ rc_serious,  "The drawing function \"%s\" has not been defined\n" },
{ rc_serious,  "Stack underflow for \"%s\" while drawing \"%s\" %s\n" },
{ rc_serious,  "No current point for \"%s\" command while drawing \"%s\" %s\n" },
{ rc_failed,   "Unexpected [newmovement] - has an [endstave] been omitted?\n" },
{ rc_serious,  "Malformed stave selection on command line\n" },
/* 75-79 */
{ rc_serious,  "Malformed page list on command line\n" },
{ rc_failed,   "Page range out of order on command line\n" },
{ rc_failed,   "Malformed -dsb option data\n" },
{ rc_warning,  "For a non-standardly encoded font, the following invalid Unicode code\n"
               "   point%s been changed to U+%04X:" },
{ rc_warning,  "The following unsupported Unicode code point%s been changed to U+%04X:" },
/* 80-84 */
{ rc_serious,  "Too many noteheads at the same or adjacent levels in a chord\n" },
{ rc_failed,   "An input line is too long - please split and reprocess\n" },
{ rc_serious,  "Corrupted text item for \"%s\" command while drawing \"%s\" %s\n" },
{ rc_serious,  "Incomplete irregular note group at end of bar\n" },
{ rc_serious,  "Draw subroutines too deeply nested%swhile drawing \"%s\" %s\n" },
/* 85-89 */
{ rc_serious,  "Too many tempo changes (maximum number is %d)\n" },
{ rc_serious,  "Tempo changes must be in ascending order of bar numbers\n" },
{ rc_failed,   "Invalid time signature\n" },
{ rc_failed,   "An input line is too long when macros are expanded -\n"
               "   please split the line and reprocess\n" },
{ rc_warning,  "Warning: Note spacing changed for breves only - is this really what\n"
               "   was intended? (Perhaps \"*\" has been omitted?)\n" },
/* 90-94 */
{ rc_warning,  "Warning: Hyphen string setting on non-underlay/overlay string ignored\n" },
{ rc_failed,   "Internal failure - hyphen type not found in bar %b of stave %d %M\n" },
{ rc_serious,  "Only two subsidiary strings are allowed\n" },
{ rc_serious,  "[Linegap] cannot be applied to a slur\n"
               "Detected in bar %b of stave %d%M\n" },
{ rc_serious,  "Editorial marks on intermittent slurs or lines are not supported\n" },
/* 95-99 */
{ rc_serious,  "Division by zero while drawing %s\"%s\" %s\n" },
{ rc_serious,  "Accidentals for printing above or below must be on the "
               "first note of a chord\n" },
{ rc_serious,  "Font stretching or shearing is not %s %s\n" },
{ rc_serious,  "Font rotation is not %s %s\n" },
{ rc_serious,  "Closing bracket missing in macro argument list\n" },
/* 100-104 */
{ rc_serious,  "Too many draw variables defined (limit is %d)\n" },
{ rc_serious,  "%sMisused \"def\" operator while drawing \"%s\" %s\n" },
{ rc_failed,   "Cannot halve C or A time signature\n" },
{ rc_failed,   "Cannot handle notes longer than a breve or shorter than a hemidemisemiquaver\n" },
{ rc_warning,  "Warning: The format word \"%s\" is not tested anywhere in the input file\n" },
/* 105-109 */
{ rc_serious,  "Clef name expected\n" },
{ rc_warning,  "Warning: Stave %d specified (or defaulted) more than once in \"%s\" directive\n" },
{ rc_serious,  "Setting stave spacing for stave 0 is not allowed\n" },
{ rc_serious,  "A small note head may not be specified for a grace note\n" },
{ rc_serious,  "Incorrect MIDI %s number (must be between 1 and %d inclusive)\n" },
/* 110-114 */
{ rc_serious,  "Unrecognized MIDI %s name: \"%s\"\n" },
{ rc_warning,  "Warning: One or more coupled notes were encountered where the stave spacing\n"
               "was not a multiple of 4 points (scaled to the size of the staves)\n" },
{ rc_warning,  "Warning: Cannot decrease page number: attempt to set page %d on page %d\n"
               "Detected in bar %b of stave %d%M\n" },
{ rc_serious,  "/%c may not appear in data for a split section of a slur\n" },
{ rc_warning,  "Warning: Accidental ignored before \"p\"\n" },
/* 115-119 */
{ rc_serious,  "No previous note to copy, or previous note cannot be copied\n" },
{ rc_serious,  "Closing curly bracket missing%swhile drawing \"%s\" %s\n" },
{ rc_serious,  "Invalid argument for conditional or looping command%swhile drawing \"%s\" %s\n" },
{ rc_serious,  "Unmatched closing curly bracket in drawing function \"%s\"\n" },
{ rc_serious,  "[Slurgap] cannot be applied to a line\n"
               "Detected in bar %b of stave %d%M\n" },
/* 120-124 */
{ rc_failed,   "Too many movements (maximum %d)\n" },
{ rc_serious,  "Stack overflow for \"%s\" while drawing \"%s\" %s\n" },
{ rc_failed,   "Error while reading font metrics for \"%s\": %s%s\n" },
{ rc_serious,  "Malformed MIDI bar selection on command line\n" },
{ rc_failed,   "Malformed options in .pmwrc file: \"%s\" %s\n" },
/* 125-129 */
{ rc_serious,  "Unrecognized escape sequence \"%s\"\n" },
{ rc_serious,  "Unrecognized clef name \"%s\"\n" },
{ rc_failed,   "%s file for font \"%s\" not found in \"%s\" or \"%s\"\n" },
{ rc_serious,  "Unknown accent number %d\n" },
{ rc_serious,  "Can't have both staccato and staccatissimo\n" },
/* 130-134 */
{ rc_failed,   "PostScript font \"%s\" not found in \"%s\" or \"%s\"\n" },
{ rc_failed,   "Too many text strings before a note (%d maximum)\n" },
{ rc_failed,   "Too many notes in a chord (%d maximum)\n" },
{ rc_serious,  "Missing backslash after note options" },
{ rc_serious,  "A &* replication must have only one argument - others ignored\n" },
/* 135-139 */
{ rc_serious,  "No options are allowed after \"x\" note repetition" },
{ rc_failed,   "%s is too long (max %d)\n" },
{ rc_failed,   "Too many macro arguments (max %d)\n" },
{ rc_warning,  "Warning: %s repeat at %s of bar - misplaced bar line?\n" },
{ rc_failed,   "%sransposition value (%d) is too large (max %d)\n" },
/* 140 - 144 */
{ rc_warning,  "/h and /r or /l with 'c' specified - the latter ignored\n" },
{ rc_serious,  "Follow-on string not permitted here\n" },
{ rc_warning,  "Follow-on ignored for %s\n" },
{ rc_serious,  "Too many text sizes (maximum %d)\n" },
{ rc_serious,  "Custom key name X1 or X2 or ... X%d expected" },
/* 145 - 149 */
{ rc_failed,   "Cannot transpose custom key X%d by %d without KeyTranspose instruction\n" },
{ rc_failed,   "Letter change value %d is too large for transpose value %d\n" },
{ rc_serious,  "Too many Unicode translations in %s (max %d)\n" },
{ rc_serious,  "Missing %s code value in line %d of %s\n%s" },
{ rc_serious,  "Invalid font code value (> 255) in line %d of %s\n%s" },
/* 150 - 154 */
{ rc_serious,  "! may only follow an upper case letter\n" },
{ rc_failed,   "This version of PMW was compiled without B2PF support\n" },
{ rc_serious,  "Unknown B2PF option \"%s\"\n" },
{ rc_failed,   "-norc must be given as the first option\n" },
{ rc_failed,   "Font is already configured for B2PF\n" },
/* 155 - 159 */
{ rc_failed,   "B2PF context creation failed: %s\n" },
{ rc_failed,   "B2PF processing failed for \"%s\": %s\n" }
};

#define error_maxerror (int)(sizeof(error_data)/sizeof(error_struct))

/* Save some typing */

#define eprintf(s) fprintf(stderr, "%s", CS s)


/*************************************************
*            Generate error message              *
*************************************************/

/* Arguments:

  n           error number
  ...         arguments for error text

Returns:      nothing
*/

void
error_moan(int n, ...)
{
int rc;
BOOL do_skip = FALSE;
uschar buff[2*WORD_BUFFERSIZE];   /* Allow for inclusion of very long word */
va_list ap;
va_start(ap, n);

if (!main_shownlogo)
  {
  sprintf(CS buff, "PMW version %s\n", version_string);
  eprintf(buff);
  main_shownlogo = TRUE;
  }

/* Now set up the text in the buffer */

if (n > error_maxerror)
  {
  sprintf(CS buff, "** Unknown error number %d\n", n);
  rc = rc_failed;
  }
else
  {
  Ustrcpy(buff, "** ");
  format_vsprintf(buff + 3, error_data[n].text, ap);
  rc = error_data[n].rc;
  }

/* We skip to the end of the input line or ']' or '\' for certain errors. Add
info to the error message here; the actual skip happens later after the message
is output (so that the right position in the input is shown). */

if (n == 10 || n == 19 || n == 20 || n == 32 || n == 133 || n == 135 ||
    n == 144)
  {
  do_skip = TRUE;
  switch(error_skip)
    {
    case skip_EOL:
    Ustrcat(buff, " - skipping to end of line");
    break;

    case skip_KET:
    Ustrcat(buff, " - skipping to next \"]\" or end of line");
    break;

    case skip_BACKSLASH:
    Ustrcat(buff, " - skipping to next \"\\\" or end of line");
    break;

    case skip_BAR:
    Ustrcat(buff, " - skipping to next \"|\" or end of line");
    stave_checklength = FALSE;   /* Don't check bar length */
    break;

    default:
    break;
    }

  Ustrcat(buff, "\n");
  }

/* Display the message */

eprintf(buff);

/* Any error before initialization is complete is a disaster */

if (!main_initialized) rc = rc_failed;

/* If the line number is non-zero, show it, but only in the reading state. We
used to add the file name only for included files, but now it is always
included for the benefit of people who use the "compilation mode" of Emacs (or
similar feature in other environments). This makes use of the error messages
and is able to open the file at the place where the error was found during the
compilation, but of course it needs the file name. */

if (reading_input)
  {
  if (read_linenumber > 0)
    {
    uschar *name = (main_filename == NULL)? US"<stdin>" : main_filename;
    if (read_filestackptr > 0)
      sprintf(CS buff, "** File \"%s\" (included), near line %d", name,
        read_linenumber);
    else
      sprintf(CS buff, "** File \"%s\", near line %d", name,
        read_linenumber);
    eprintf(buff);
    }

  /* Show the input line unless it is a null string. The error_ptr_correction
  value is used to point the the correct place in strings which have already
  been fully read. However, if the string spreads over several lines, we can't
  do this. If the pointer is at the start of the line, show the previous line
  as well, if it is significant. */

  if (this_buffer[0] != 0 || prev_buffer[0] != 0)
    {
    int oldchptr = read_chptr - this_buffer - 1;
    if (error_ptr_correction <= oldchptr) oldchptr -= error_ptr_correction;
    error_ptr_correction = 0;
    eprintf(US":\n");
    if (prev_buffer[0] != 0 && oldchptr <= 0) eprintf(prev_buffer);
    eprintf(this_buffer);
    if (oldchptr >= 0  && !read_EOF)
      {
      int i;
      /* Some people use unbeliveably long input lines */
      while (oldchptr > 250)
        {
        for (i = 0; i < 250; i++) buff[i] = ' ';
        buff[i] = 0;
        eprintf(buff);
        oldchptr -= 250;
        }
      for (i = 0; i < oldchptr; i++) buff[i] = ' ';
      sprintf(CS buff+i, "<");
      eprintf(buff);
      }
    }
  }

eprintf(US"\n");

/* Skip to the end of the input line or ']' or '\' for certain errors (detected
above, when the message was modified). */

if (do_skip) switch(error_skip)
  {
  case skip_EOL:
  read_chptr = read_endptr;
  read_ch = ' ';
  break;

  case skip_KET:
  while (read_ch != ']' && read_ch != '\n' && read_ch != EOF) next_ch();
  sigch();
  break;

  case skip_BACKSLASH:
  while (read_ch != '\\' && read_ch != '\n' && read_ch != EOF) next_ch();
  sigch();
  break;

  case skip_BAR:
  while (read_ch != '|' && read_ch != '\n' && read_ch != EOF) next_ch();
  sigch();
  break;

  default:
  break;
  }

/* Disaster if too many errors */

if (rc > rc_warning)
  {
  error_count++;
  if (error_count > error_maximum)
    {
    eprintf(US"** Too many errors - rest of file not scanned\n");
    if (rc < rc_failed) rc = rc_failed;
    }
  }

/* Save the highest return code and exit if too serious */

if (rc > main_rc) main_rc = rc;
if (rc >= rc_failed)
  {
  eprintf(US"** Hard error: PMW abandoned\n");
  exit(rc);
  }
}

/* End of error.c */
