/*************************************************
*    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 code for handling errors. There are some spares in the
list of messages. They were Acorn-specific ones that are no longer in use.
These numbers can be re-used for new errors. */

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




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

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

static error_struct error_data[] = {

/* 0-4 */
{ rc_disaster, "Failed to decode command line: \"%s\" %s\n" },
{ rc_disaster, "Ran out of memory: increase free memory (e.g. by removing tasks)\n" },
{ rc_disaster, "Internal failure - store overlap upwards (%x %d %x)\n" },
{ rc_disaster, "Internal failure - store overlap downwards (%x %d %x)\n" },
{ rc_serious,  "Unable to open file \"%s\": %s\n" },
/* 5-9 */
{ rc_serious,  "%s\n" },
{ rc_serious,  "Wrong data type on stack for \"%s\" while drawing \"%s\" %s\n" },
{ rc_disaster, "Unable to open font file \"%s\": %s (check PMW installation)\n" },
{ rc_serious,  "Defined name expected after \"&\"\n" },
{ rc_serious,  "The name \"%s\" has not been defined\n" },
/* 10-14 */
{ rc_serious,  "%s expected" },
{ rc_disaster, "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\nheading" },
{ 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_disaster, "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 (%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" },
{ 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_disaster, "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 data\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_disaster, "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,   "Metrics file for font \"%s\" not found in \"%s\"\n" },
/* 60-64 */
{ rc_disaster, "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_disaster, "-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 and ornaments 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_disaster, "Page range out of order on command line\n" },
{ rc_disaster, "Malformed -dsb option data\n" },
{ rc_warning,  "Character value %d is greater than 255 for a non-standardly encoded\n" 
               "   font - changed to %d\n" },
{ rc_warning,  "Unsupported Unicode code point U+%04X changed to U+%04X\n" },
/* 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_disaster, "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_disaster, "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_disaster, "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,   "Metrics 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" },
{ rc_disaster, "PostScript font \"%s\" not found in \"%s\" or \"%s\"\n" }
/* 130-134 */
};

#define error_maxerror 130   /* Tests are in misctests/Errors */

/* 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;
int oldchptr;
uschar buff[256];
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_disaster;
  }
else
  {
  Ustrcpy(buff, "** ");
  format_vsprintf(buff + 3, error_data[n].text, ap);
  rc = error_data[n].rc;
  }

/* Save character position for marking input line reflection */

oldchptr = read_chptr - this_buffer - 1;

/* We skip to the end of the input line or ']' or '\' for certain errors.
However, we don't skip to EOL if we are in fact at the start of a line, since
that case must really be a mistake in the previous line. */

if (n == 10 || n == 19 || n == 20 || n == 32) switch(error_skip)
  {
  case skip_EOL:
  if (read_chptr != this_buffer + 1)
    {
    Ustrcat(buff, " - skipping to end of line\n");
    read_chptr = read_endptr;
    read_ch = ' ';
    }
  else Ustrcat(buff, "\n");
  break;

  case skip_KET:
  Ustrcat(buff, " - skipping to next \"]\" or end of line\n");
  while (read_ch != ']' && read_ch != '\n' && read_ch != EOF) next_ch();
  sigch();
  break;

  default:
  Ustrcat(buff, " - skipping to next \"\\\" or end of line\n");
  while (read_ch != '\\' && read_ch != '\n' && read_ch != EOF) next_ch();
  sigch();
  break;
  }

/* Display the message */

eprintf(buff);

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

if (!main_initialized) rc = rc_disaster;

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

/* Disaster if too many errors */

if (rc > rc_warning)
  {
  error_count++;
  if (error_count > 40)
    {
    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 */
