/*************************************************
*    The PMW Music Typesetter - 3rd 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 "preprocessing" functions. */


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




/*************************************************
*        Deal with pre-processing directive      *
*************************************************/

/* We enter with read_ptr pointing to the first letter of the directive's name,
so we are guaranteed to read a word. We can use normal item reading routines,
but must take care not to do so at the end of a line.

Arguments: none
Returns:   nothing
*/

void
pre_process(void)
{
next_word();
sigchNL();

DEBUG(("pre_process(%s) entered\n", read_word));

/* Deal with "if" */

if (Ustrcmp(read_word, "if") == 0)
  {
  int OK;
  if (read_skipdepth > 0)
    {
    read_skipdepth++;
    read_chptr = read_endptr;
    read_ch = '\n';
    DEBUG(("pre_process(if) ending\n"));
    return;
    }

  for (;;)
    {
    OK = TRUE;
    read_word[0] = 0;
    if (read_ch != '\n') next_word();

    if (Ustrcmp(read_word, "not") == 0)
      {
      OK = !OK;
      sigchNL();
      read_word[0] = 0;
      if (read_ch != '\n') next_word();
      }

    if (read_word[0] == 0)
      { error_moan(ERR10, "Word"); break; }
    else
      {
      int i;

      if (Ustrcmp(read_word, "score") == 0)
        {
        for (i = 0; i < STAVE_BITVEC_SIZE; i++)
          if (curmovt->staves[i] != ~0u) { OK = !OK; break; }
        }
      else if (Ustrcmp(read_word, "part") == 0)
        {
        for (i = 0; i < STAVE_BITVEC_SIZE; i++)
          if (curmovt->staves[i] != ~0u) break;
        if (i >= STAVE_BITVEC_SIZE) OK = !OK;
        }

      /* For a stave list, we can't use a common routine with other directives
      because we mustn't stray over the end of the line! */

      else if (Ustrcmp(read_word, "staff") == 0 || Ustrcmp(read_word, "stave") == 0 ||
              Ustrcmp(read_word, "staves") == 0)
        {
        usint map[STAVE_BITVEC_SIZE];
        mac_initstave(map, 0);
        mac_setstave(map, 0);       /* Staff zero is always selected */
        sigchNL();

        while (isdigit(read_ch))
          {
          int s = read_integer(FALSE);
          int t = s;
          sigchNL();
          if (read_ch == '-')
            {
            next_ch();
            sigchNL();
            if (!isdigit(read_ch))
              {
              error_moan(ERR10, "Number");
              t = s;
              }
            else t = read_integer(FALSE);
            }

          if (t < s) error_moan(ERR21);
            else if (t > MAX_STAVE) error_moan(ERR22, MAX_STAVE+1);
          else
            {
            for (i = s; i <= t; i++) mac_setstave(map, i);
            }

          sigchNL();
          if (read_ch == ',')
            {
            next_ch();
            sigchNL();
            }
          }

        for (i = 0; i < STAVE_BITVEC_SIZE; i++)
          if (map[i] != curmovt->staves[i]) { OK = !OK; break; }
        }

      /* Deal with definition test */

      else if (Ustrcmp(read_word, "undef") == 0)
        {
        sigchNL();
        read_word[0] = 0;
        if (read_ch != '\n') next_word();
        if (read_word[0] == 0) error_moan(ERR10, "Macro name"); else
          {
          if (Tree_Search(define_tree, read_word) != NULL) OK = !OK;
          }
        }

      /* Test if any format is set */

      else if (Ustrcmp(read_word, "format") == 0)
        {
        if (main_format[0] == 0) OK = !OK;
        }

      /* Not recognized; take as format word */

      else
        {
        if (Ustrcmp(read_word, main_format) != 0) OK = !OK;
          else main_format_tested = TRUE;
        }
      }

    /* See if the next thing is "or"; if not and if not newline, error.
    Otherwise, if it's "or" and OK == FALSE, let the loop continue. */

    sigchNL();
    if (read_ch == '\n') break;
    read_word[0] = 0;
    next_word();
    if (Ustrcmp(read_word, "or") != 0) { error_moan(ERR10, "\"or\""); break; }
    if (OK) break;
    sigchNL();
    }

  /* Decision taken; act appropriately */

  if (OK) read_okdepth++; else read_skipdepth++;
  DEBUG(("pre_process(if) ending\n"));
  return;
  }


/* Deal with "else" */

if (Ustrcmp(read_word, "else") == 0)
  {
  if (read_skipdepth <= 1)
    {
    if (read_skipdepth == 1)
      {
      read_skipdepth--;
      read_okdepth++;
      }
    else if (read_okdepth > 0)
      {
      read_skipdepth++;
      read_okdepth--;
      }
    else error_moan(ERR17, "\"*else\"");
    }
  DEBUG(("pre_process(else) ending\n"));
  return;
  }


/* Deal with "fi" */

if (Ustrcmp(read_word, "fi") == 0)
  {
  if (read_skipdepth > 0) read_skipdepth--; else
    if (read_okdepth > 0) read_okdepth--;
      else error_moan(ERR17, "*fi");
  DEBUG(("pre_process(fi) ending\n"));
  return;
  }


/* Others are only looked at when not skipping */

if (read_skipdepth >  0)
  {
  DEBUG(("pre_process() ending -- skipping\n"));
  return;
  }

/* Deal with defining names */

if (Ustrcmp(read_word, "define") == 0)
  {
  if (read_ch != '\n')     /* Don't use next_word(), because it */
    {                      /*   converts to lower case */
    int i = 0;
    sigch();
    if (isalnum(read_ch))
      {
      do
        {
        if (i >= WORD_BUFFERSIZE - 1)
          error_moan(ERR136, "Macro name", WORD_BUFFERSIZE - 1);  /* Hard */
        read_word[i++] = read_ch;
        next_ch();
        }
      while (isalnum(read_ch));
      }
    read_word[i] = 0;
    }

  if (read_word[0] == 0) error_moan(ERR10, "Macro name"); else
    {
    int len;
    int argcount = 0;
    uschar *args[MAX_MACROARGS];
    uschar arg[MAX_MACRODEFAULT + 1];
    tree_node *p = store_Xget(sizeof(tree_node));

    p->name = store_Xget(Ustrlen(read_word) + 1);
    Ustrcpy(p->name, read_word);

    if (*(--read_chptr) == '(')
      {
      while (read_chptr < read_endptr && *read_chptr != ')')
        {
        int bracount = 0;
        BOOL inquotes = FALSE;
        uschar *s = arg;

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

        while (++read_chptr < read_endptr &&
              ((*read_chptr != ',' && *read_chptr != ')') ||
                bracount > 0 || inquotes))
          {
          int ch = *read_chptr;
          if (s - arg >= MAX_MACRODEFAULT)
            error_moan(ERR136, "Macro default argument", MAX_MACRODEFAULT);  /* Hard */

          if (ch == '&') *s++ = *(++read_chptr); else
            {
            if (ch == '\"') inquotes = !inquotes;
            if (!inquotes)
              {
              if (ch == '(') bracount++;
                else if (ch == ')') bracount--;
              }
            *s++ = ch;
            }
          }

        if (read_chptr >= read_endptr) error_moan(ERR99);
        if (s - arg > 0)
          {
          uschar *ss = store_Xget(s - arg + 1);
          *s = 0;
          Ustrcpy(ss, arg);
          args[argcount++] = ss;
          }
        else args[argcount++] = NULL;
        }

      if (*read_chptr == ')') read_chptr++;
      }

    while (*read_chptr == ' ' || *read_chptr == '\t') read_chptr++;
    while (read_endptr[-1] == ' ' || read_endptr[-1] == '\t') read_endptr--;

    len = read_endptr - read_chptr;
    if (len <= 0 && argcount == 0) p->data = NULL; else
      {
      int i;
      uschar *pp = store_Xget(len + 1);

      macrostr *mm =
        store_Xget((int)sizeof(macrostr) + (argcount-1)*((int)sizeof(uschar *)));

      mm->argcount = argcount;
      p->data = (uschar *)mm;
      mm->text = pp;
      while (len-- > 0) *pp++ = *read_chptr++;
      *pp = 0;
      for (i = 0; i < argcount; i++) mm->args[i] = args[i];
      }

    if (!Tree_InsertNode(&define_tree, p)) error_moan(ERR14, read_word);
    read_chptr = read_endptr;
    read_ch = '\n';
    }
  }


/* Deal with included files */

else if (Ustrcmp(read_word, "include") == 0)
  {
  FILE *f;

  if (read_filestackptr >= MAX_INCLUDE)
    { error_moan(ERR15, MAX_INCLUDE); return; }

  if (read_ch == '\n' || !read_plainstring())
    { error_moan(ERR10, "File name in quotes"); return; }

  sys_relativize(read_word, WORD_BUFFERSIZE);

  f = Ufopen(read_word, "r");
  if (f == NULL) error_moan(ERR4, read_word, strerror(errno));   /* Hard error */

  /* Stack the current variables and replace with new ones. The
  size is added to the total size expected. */

  read_filestack[read_filestackptr].file = input_file;
  read_filestack[read_filestackptr].filename = main_filename;
  read_filestack[read_filestackptr].linenumber = read_linenumber;
  read_filestack[read_filestackptr++].okdepth = read_okdepth;

  input_file = f;
  read_okdepth = 0;
  read_linenumber = 0;
  main_filename = store_copystring(read_word);

  DEBUG(("including file %s\n", main_filename));
  }


/* Deal with comment */

else if (Ustrcmp(read_word, "comment") == 0)
  {
  uschar *s = read_chptr - 1;
  fprintf(stderr, "%s\n", s);
  read_chptr = read_endptr;
  read_ch = '\n';
  }


/* Else unknown preprocessing directive */

else
  {
  error_moan(ERR13, read_word);
  read_chptr = read_endptr;
  read_ch = '\n';
  }

/* Test for extraneous characters */

sigchNL();
if (read_ch != '\n') error_moan(ERR16, "Extraneous characters ignored");

DEBUG(("pre_process() ending\n"));
}


/* End of preprocess.c */
