/* 
 * parseabc.c - code to parse an abc file. This file is used by the
 * following 3 programs :
 * abc2midi - program to convert abc files to MIDI files.
 * abc2abc  - program to manipulate abc files.
 * yaps     - program to convert abc to PostScript music files.
 * Copyright (C) 1999 James Allwright
 * e-mail: J.R.Allwright@westminster.ac.uk
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */


/* Macintosh port 30th July 1996 */
/* DropShell integration   27th Jan  1997 */
/* Wil Macaulay (wil@syndesis.com) */ 


#define TAB 9
#include "abc.h"
#include "parseabc.h"
#include <stdio.h>
#include <stdlib.h>

#define SIZE_ABBREVIATIONS ('Z' - 'H' + 1)

#ifdef _MSC_VER
#define ANSILIBS
#define casecmp stricmp
#else
#define casecmp strcasecmp
#endif
#define	stringcmp	strcmp

#ifdef __MWERKS__
#define __MACINTOSH__ 1
#endif /* __MWERKS__ */

#ifdef __MACINTOSH__
#define main macabc2midi_main
#define STRCHR
#endif /* __MACINTOSH__ */

/* define USE_INDEX if your C libraries have index() instead of strchr() */
#ifdef USE_INDEX
#define strchr index
#endif

#ifdef ANSILIBS
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#else
extern char* malloc();
extern char* strchr();
#endif

int lineno;
int parsing_started =0;
int parsing, slur;
int inhead, inbody;
int parserinchord;
int ingrace=0;
int chorddecorators[DECSIZE];
char decorations[] = ".MLRH~Tuv";
char *abbreviation[SIZE_ABBREVIATIONS];

int voicecodes = 0;
char voicecode[16][30]; /*for interpreting V: string */

int decorators_passback[DECSIZE];
/* this global array is linked as an external to store.c and 
 * yaps.tree.c and is used to pass back decorator information
 * from event_instruction to parsenote.
*/
 

int nokey=0;  /* K: none was encountered */
int chord_n,chord_m ; /* for event_chordoff */
int fileline_number = 1;
int intune = 1;

extern programname fileprogram;
int oldchordconvention = 0;

char *mode[10] = {"maj", "min", "m", 
                       "aeo", "loc", "ion", "dor", "phr", "lyd", "mix"};
int modeshift[10] = {0, -3, -3,
                         -3, -5, 0, -2, -4, 1, -1 };
int modeminor[10] = {0, 1, 1,
                          1, 0, 0, 0, 0, 0, 0};
int modekeyshift[10] = {0,5,5,5,6,0,1,2,3,4};

int* checkmalloc(bytes)
/* malloc with error checking */
int bytes;
{
  int *p;

  p = (int*) malloc(bytes);
  if (p == NULL) {
    printf("Out of memory error - malloc failed!\n");
    exit(0);
  };
  return (p);
}

char* addstring(s)
/* create space for string and store it in memory */
char* s;
{
  char* p;

  p = (char*) checkmalloc(strlen(s)+1);
  strcpy(p, s);
  return(p);
}

void initvstring(s)
struct vstring *s;
/* initialize vstring (variable length string data structure) */
{
  s->len = 0;
  s->limit = 40;
  s->st = (char*) checkmalloc(s->limit + 1);
  *(s->st) = '\0';
}

void extendvstring(s)
struct vstring *s;
/* doubles character space available in string */
{
  char* p;

  if (s->limit > 0) {
    s->limit = s->limit * 2;
    p = (char*) checkmalloc(s->limit + 1);
    strcpy(p, s->st);
    free(s->st);
    s->st = p;
  } else {
    initvstring(s);
  };
}

void addch(ch, s)
char ch;
struct vstring *s;
/* appends character to vstring structure */
{
  if (s->len >= s->limit) {
    extendvstring(s);
  };
  *(s->st+s->len) = ch;
  *(s->st+(s->len)+1) = '\0';
  s->len = (s->len) + 1;
}

void addtext(text, s)
char* text;
struct vstring *s;
/* appends a string to vstring data structure */
{
  int newlen;

  newlen = s->len + strlen(text);
  while (newlen >= s->limit) {
    extendvstring(s);
  };
  strcpy(s->st+s->len, text);
  s->len = newlen;
}

void clearvstring(s)
struct vstring *s;
/* set string to empty */
/* does not deallocate memory ! */
{
  *(s->st) = '\0';
  s->len = 0;
}

void freevstring(s)
struct vstring *s;
/* deallocates memory allocated for string */
{
  if (s->st != NULL) {
    free(s->st);
    s->st = NULL;
  };
  s->len = 0;
  s->limit = 0;
}

void parseron()
{
  parsing = 1;
  slur = 0;
  parsing_started = 1;
}

void parseroff()
{
  parsing = 0;
  slur = 0;
}

int getarg(option, argc, argv)
/* look for argument 'option' in command line */
char *option;
char *argv[];
int argc;
{
  int j, place;

  place = -1;
  for (j=0; j<argc; j++) {
    if (strcmp(option, argv[j]) == 0) {
      place = j + 1;
    };
  };
  return (place);
}

void skipspace(p)
char **p;
{
  /* skip space and tab */
  while(((int)**p == ' ') || ((int)**p == TAB)) *p = *p + 1;
}

void skiptospace(p)
char **p;
{
 while(((int)**p != ' ') && ((int)**p != TAB) && (int)**p != '\0') *p = *p +1;
}


int isnumberp(p)
char **p;
/* returns 1 if positive number, returns 0 if not a positive number */
/* ie -4 or 1A both return 0. This function is needed to get the    */
/* voice number.                                                    */
{
char **c;
c = p;
 while(((int)**c != ' ') && ((int)**c != TAB) && (int)**c != '\0') {
   if (((int)**c >= '0') && ((int)**c <= '9')) 
     *c = *c +1;
   else 
     return 0;
   }
return 1;
}



int readnumf(num)
char *num;
/* read integer from string without advancing character pointer */
{
  int t;
  char* p;

  p =num;
  if (!isdigit(*p)) {
    event_error("Missing Number");
  };
  t = 0;
  while (((int)*p >= '0') && ((int)*p <= '9')) {
    t = t * 10 + (int) *p - '0';
    p = p + 1;
  };
  return (t);
}

int readsnumf(s)
char* s;
/* reads signed integer from string without advancing character pointer */
{
  char* p;

  p = s;
  if (*p == '-') {
    p = p+1;
    skipspace(&p);
    return(-readnumf(p));
  } else {
    return(readnumf(p));
  }
}

int readnump(p)
char **p;
/* read integer from string and advance character pointer */
{
  int t;

  t = 0;
  while (((int)**p >= '0') && ((int)**p <= '9')) {
    t = t * 10 + (int) **p - '0';
    *p = *p + 1;
  };
  return (t);
}

int readsnump(p)
char** p;
/* reads signed integer from string and advance character pointer */
{
  if (**p == '-') {
    *p = *p+1;
    skipspace(p);
    return(-readnump(p));
  } else {
    return(readnump(p));
  }
}

void readsig(a, b, sig)
int *a, *b;
char **sig;
/* read time signature (meter) from M: field */
{
  int t;

  if ((**sig == 'C') || (**sig == 'c')) {
    *a = 4;
    *b = 4;
    return;
  };
  *a = readnump(sig);
  if ((int)**sig != '/') {
    event_error("Missing / ");
  } else {
    *sig = *sig + 1;
  };
  *b = readnump(sig);
  if ((*a == 0) || (*b == 0)) {
    event_error("Expecting fraction in form A/B");
  } else {
    t = *b;
    while (t > 1) {
      if (t%2 != 0) {
        event_error("divisor must be a power of 2");
        t = 1;
        *b = 0;
      } else {
        t = t/2;
      };
    };
  };
}

void readlen(a, b, p)
int *a, *b;
char **p;
/* read length part of a note and advance character pointer */
{
  int t;

  *a = readnump(p);
  if (*a == 0) {
    *a = 1;
  };
  *b = 1;
  if (**p == '/') {
    *p = *p + 1;
    *b = readnump(p);
    if (*b == 0) {
      *b = 2;
      while (**p == '/') {
        *b = *b * 2;
        *p = *p + 1;
      };
    };
  };
  t = *b;
  while (t > 1) {
    if (t%2 != 0) {
      event_warning("divisor not a power of 2");
      t = 1;
    } else {
      t = t/2;
    };
  };
}

void readlen_nocheck(a, b, p)
int *a, *b;
char **p;
/* read length part of a note and advance character pointer */
{
  int t;

  *a = readnump(p);
  if (*a == 0) {
    *a = 1;
  };
  *b = 1;
  if (**p == '/') {
    *p = *p + 1;
    *b = readnump(p);
    if (*b == 0) {
      *b = 2;
      while (**p == '/') {
        *b = *b * 2;
        *p = *p + 1;
      };
    };
  };
  t = *b;
  while (t > 1) {
    if (t%2 != 0) {
      /*event_warning("divisor not a power of 2"); */
      t = 1;
    } else {
      t = t/2;
    };
  };
}

int ismicrotone(p,dir)
char **p;
int dir;
{
int a, b;
readlen_nocheck(&a, &b, p);
if (b != 1) {
  event_microtone(dir,a,b);
  return 1;}
return 0;
}




int isclef(s, gotoctave, octave, strict)
char* s;
int *gotoctave, *octave;
int strict;
/* part of K: parsing - looks for a clef in K: field                 */
/* format is K:string where string is treble, bass, baritone, tenor, */
/* alto, mezzo, soprano or K:clef=arbitrary                          */
{
  int gotclef;

  s = s;
  gotclef = 0;
  if (strncmp(s, "bass", 4) == 0) {
    gotclef= 1;
    *gotoctave=1;
    *octave = -1;
  };
  if (strncmp(s, "treble", 6) == 0) {
    gotclef= 1;
  };
  if (strncmp(s, "treble+8", 8) == 0) {
    gotclef= 1;
    *gotoctave=1;
    *octave =1;
  };
  if (strncmp(s, "treble-8", 8) == 0) {
    gotclef= 1;
    *gotoctave=1;
    *octave = -1;
  };
  if (strncmp(s, "baritone", 8) == 0) {
    gotclef= 1;
  };
  if (strncmp(s, "tenor", 5) == 0) {
    gotclef= 1;
  };
  if (strncmp(s, "tenor-8", 7) == 0) {
    gotclef= 1;
    *gotoctave=1;
    *octave= -1;
  };
  if (strncmp(s, "alto", 4) == 0) {
    gotclef= 1;
  };
  if (strncmp(s, "mezzo", 5) == 0) {
    gotclef= 1;
  };
  if (strncmp(s, "soprano", 7) == 0) {
    gotclef= 1;
  };
/*
 * only clef=F or clef=f is allowed, or else
 * we get a conflict with the key signature
 * indication K:F
*/

  if (strncmp(s, "f",1) == 0 && strict==0) {
    gotclef = 1;
  }
  if (strncmp(s, "F",1) == 0 && strict==0) {
    gotclef = 1;
  }
  if (strncmp(s, "g",1) == 0 && strict==0) {
    gotclef = 1;
  }
  if (strncmp(s, "G",1) == 0 && strict==0) {
    gotclef = 1;
  }

  if (!strict && !gotclef) {
	  gotclef = 1;
	  event_warning("cannot recognize clef indication");
          }

  return(gotclef);
}

char* readword(word, s)
/* part of parsekey, extracts word from input line */
char word[];
char* s;
{
  char* p;
  int i;

  p = s;
  i = 0;
  while ((*p != '\0') && (*p != ' ') && ((i == 0) || (*p != '='))) {
    if (i < 29) {
      word[i] = *p;
      i = i + 1;
    };
    p = p + 1;
  };
  word[i] = '\0';
  return(p);
}

void lcase(s)
/* convert word to lower case */
char* s;
{
  char* p;

  p = s;
  while (*p != '\0') {
    if (isupper(*p)) {
      *p = *p + 'a' - 'A';
    };
    p = p + 1;
  };
}


void init_voicecode()
{
int i;
for (i=0;i<16;i++) voicecode[i][0] = 0;
voicecodes =0;
}

void print_voicecodes()
{
int i;
if (voicecodes == 0) return;
printf("voice mapping:\n");
for (i=0;i<voicecodes;i++)
  {
	if(i%4 == 3) printf("\n");
	printf("%s  %d   ",voicecode[i],i+1);
  }
  printf("\n");
}

int interpret_voicestring(char *s)
{
/* if V: is followed  by a string instead of a number
 * we check to see if we have encountered this string
 * before. We assign the number associated with this
 * string and add it to voicecode if it was not encountered
 * before. If more than 16 distinct strings were encountered
 * we report an error -1.
*/
int i;
char code[32];
char *c;
c =readword(code,s);
if (code[0] == '\0') return 0;
if (voicecodes == 0) {strcpy(voicecode[voicecodes],code);
	              voicecodes++;
		      return voicecodes;
                     }
for (i=0;i<voicecodes;i++)
  if(stringcmp(code,voicecode[i]) == 0) return (i+1);
if ((voicecodes +1) > 15) return -1; 
strcpy(voicecode[voicecodes],code);
voicecodes++;	
return voicecodes;
}
    
/* The following three functions parseclefs, parsetranspose,
 * parseoctave are used to parse the K: field which not
 * only specifies the key signature but also other descriptors
 * used for producing a midi file or postscript file.
 *
 * The char* word contains the particular token that
 * is being interpreted. If the token can be understood,
 * other parameters are extracted from char ** s and
 * s is advanced to point to the next token.
 */

int parseclef(s,word,gotclef,clefstr,gotoctave,octave)
char** s;
char* word;
int *gotclef;
char *clefstr;
int *gotoctave,*octave;
/* extracts string clef= something */
{
int successful;
skipspace(s);
*s = readword(word, *s);
successful = 0;
if (casecmp(word, "clef") == 0) {
     skipspace(s);
     if (**s != '=') {
        event_error("clef must be followed by '='");
      } else {
        *s = *s + 1;
        skipspace(s);
        *s = readword(clefstr, *s);
        if (isclef(clefstr,gotoctave,octave,0)) {
          *gotclef = 1;
        };
      };
      successful = 1;
    }
else if (isclef(word,gotoctave,octave,1)) {
      *gotclef = 1;
      strcpy(clefstr, word);
      successful = 1;
    };
return successful;
} 


int parsetranspose(s,word,gottranspose,transpose)
/* parses string transpose= number */
char** s;
char* word;
int *gottranspose;
int *transpose;
{
if  (casecmp(word, "transpose") != 0) return 0;
skipspace(s);
if (**s != '=') {
   event_error("transpose must be followed by '='");
   } else {
      *s = *s + 1;
      skipspace(s);
      *transpose = readsnump(s);
      *gottranspose = 1;
      };
  return  1;
};


int parseoctave(s,word,gotoctave,octave)
/* parses string octave= number */
char** s;
char* word;
int *gotoctave;
int *octave;
{
if  (casecmp(word, "octave") != 0) return 0;
skipspace(s);
if (**s != '=') {
   event_error("octave must be followed by '='");
   } else {
       *s = *s + 1;
       skipspace(s);
       *octave = readsnump(s);
       *gotoctave = 1;
       };
  return 1;
};


int parsename(s,word,gotname,namestring,maxsize)
/* parses string name= "string" in V: command 
   for compatability of abc2abc with abcm2ps
*/
char **s;
char * word;
int *gotname;
char namestring[];
int maxsize;
{
int i;
i = 0;
if  (casecmp(word, "name") != 0) return 0;
skipspace(s);
if (**s != '=') {
   event_error("name must be followed by '='");
   } else {
       *s = *s + 1;
       skipspace(s);
       if (**s == '"')   /* string enclosed in double quotes */
          {
          namestring[i] = (char) **s; 
          *s = *s + 1;
          i++;
          while (i < maxsize && **s != '"' && **s != '\0') 
           {namestring[i] = (char) **s;
            *s = *s +1;
            i++;
            }
           namestring[i] = (char) **s; /* copy double quotes */
           i++;
           namestring[i]= '\0'; 
          } else      /* string not enclosed in double quotes */
           {
           while (i < maxsize && **s != ' ' && **s != '\0')
            {
            namestring[i] = (char) **s;
            *s = *s +1;
            i++; 
            }
           namestring[i] = '\0'; 
           }
       *gotname = 1;
        }
  return 1;
};

int parsesname(s,word,gotname,namestring,maxsize)
/* parses string name= "string" in V: command 
   for compatability of abc2abc with abcm2ps
*/
char **s;
char * word;
int *gotname;
char namestring[];
int maxsize;
{
int i;
i = 0;
if  (casecmp(word, "sname") != 0) return 0;
skipspace(s);
if (**s != '=') {
   event_error("name must be followed by '='");
   } else {
       *s = *s + 1;
       skipspace(s);
       if (**s == '"')   /* string enclosed in double quotes */
          {
          namestring[i] = (char) **s; 
          *s = *s + 1;
          i++;
          while (i < maxsize && **s != '"' && **s != '\0') 
           {namestring[i] = (char) **s;
            *s = *s +1;
            i++;
            }
           namestring[i] = (char) **s; /* copy double quotes */
           i++;
           namestring[i]= '\0'; 
          } else      /* string not enclosed in double quotes */
           {
           while (i < maxsize && **s != ' ' && **s != '\0')
            {
            namestring[i] = (char) **s;
            *s = *s +1;
            i++; 
            }
           namestring[i] = '\0'; 
           }
       *gotname = 1;
        }
  return 1;
};

int parsemiddle(s, word, gotmiddle, middlestring, maxsize)
/* parse string middle=X in V: command
 for abcm2ps compatibility
*/
char **s;
char * word;
int *gotmiddle;
char middlestring[];
int maxsize;
{
int i;
i = 0;
if (casecmp(word, "middle") != 0) return 0;
skipspace(s);
if( **s != '=' ) {
   event_error("middle must be followed by '='");
   } else {
	*s = *s + 1;
	skipspace(s);
/* we really ought to check the we have a proper note name; for now, just copy non-space
characters */
       while (i < maxsize && **s != ' ' && **s != '\0') {
          middlestring[i] = (char) **s;
          *s = *s + 1;
          ++i;
       }
   middlestring[i] = '\0';
   *gotmiddle = 1;
   }
   return 1;
}



int parsekey(str)
/* parse contents of K: field */
/* this works by picking up a strings and trying to parse them */
/* returns 1 if valid key signature found, 0 otherwise */
char* str;
{
  char* s;
  char word[30];
  int parsed;
  int gotclef, gotkey, gotoctave, gottranspose;
  int foundmode;
  int transpose, octave;
  char clefstr[30];
  char modestr[30];
  char msg[80];
  char* moveon;
  int sf = -1, minor = -1;
  char modmap[7];
  int modmul[7];
  int i, j;
  int cgotoctave,coctave;
  char *key = "FCGDAEB";
  int modeindex;

  clefstr[0] = (char) 0;
  modestr[0] = (char) 0;
  s = str;
  transpose = 0;
  gottranspose = 0;
  octave = 0;
  gotkey = 0;
  gotoctave = 0;
  gotclef=0;
  cgotoctave=0;
  coctave=0;
  modeindex = 0;
  for (i=0; i<7; i++) {
    modmap[i] = ' ';
    modmul[i] = 1;
  };
  while (*s != '\0') {
    parsed = parseclef(&s,word,&gotclef,clefstr,&cgotoctave,&coctave);

    if (!parsed) parsed = parsetranspose(&s,word,&gottranspose,&transpose);

    if (!parsed) parsed = parseoctave(&s,word,&gotoctave,&octave);

    if ((parsed == 0) && (casecmp(word, "Hp") == 0)) {
      sf = 2;
      minor = 0;
      gotkey = 1;
      parsed = 1;
    };

    if ((parsed == 0) && (casecmp(word,"none") ==0)) {
       gotkey =1;
       parsed = 1;
       nokey = 1;
       minor =0;
       sf = 0;
       }

    if ((parsed == 0) && ((word[0] >= 'A') && (word[0] <= 'G'))) {
      gotkey = 1;
      parsed = 1;
      /* parse key itself */
      sf = strchr(key, word[0]) - key - 1;
      j = 1;
      /* deal with sharp/flat */
      if (word[1] == '#') {
        sf += 7;
        j = 2;
      } else {
        if (word[1] == 'b') {
          sf -= 7;
          j = 2;
        };
      }
      minor = 0;
      foundmode = 0;
      if ( (int) strlen(word) == j) {
        /* look at next word for mode */
        skipspace(&s);
        moveon = readword(modestr, s);
        lcase(modestr);
        for (i = 0; i<10; i++) {
          if (strncmp(modestr, mode[i], 3) == 0) {
            foundmode = 1;
            sf = sf + modeshift[i];
            minor = modeminor[i];
            modeindex = i;
          };
        };
        if (foundmode) {
          s = moveon;
        };
      } else {
        strcpy(modestr, &word[j]);
        lcase(modestr);
        for (i = 0; i<10; i++) {
          if (strncmp(modestr, mode[i], 3) == 0) {
            foundmode = 1;
            sf = sf + modeshift[i];
            minor = modeminor[i];
            modeindex = i;
          };
        };
        if (!foundmode) {
          sprintf(msg, "Unknown mode '%s'", &word[j]);
          event_error(msg);
          modeindex = 0;
        };
      };
    };
    if (gotkey) {
      if (sf > 7) {
        event_warning("Unusual key representation");
       sf = sf - 12;
      } ;
      if (sf < -7) {
        event_warning("Unusual key representation");
        sf = sf + 12;
      };
    };
    if ((word[0] == '^') || (word[0] == '_') || (word[0] == '=')) {
      if ((strlen(word) == 2) && (word[1] >= 'a') && (word[1] <= 'g')) {
        j = (int)word[1] - 'a';
        modmap[j] = word[0];
        modmul[j] = 1;
        parsed = 1;
      } else {
        if ((strlen(word) == 3) && (word[0] != '=') && (word[0] == word[1]) &&
            (word[2] >= 'a') && (word[2] <= 'g')) {
          j = (int)word[2] - 'a';
          modmap[j] = word[0];
          modmul[j] = 2;
          parsed = 1;
        };
      };
    };
    if ((parsed == 0) && (strlen(word) > 0)) {
      sprintf(msg, "Ignoring string '%s' in K: field", word);
      event_warning(msg);
    };
  };
  if (cgotoctave) {gotoctave=1; octave=coctave;}
  event_key(sf, str, modeindex, modmap, modmul, gotkey, gotclef, clefstr,
            octave, transpose, gotoctave, gottranspose);
  return(gotkey);
}


void parsevoice(s)
char *s;
{
int num;		/* voice number */
struct voice_params vparams;
char word[30];
int parsed;
int coctave, cgotoctave;

vparams.transpose = 0;
vparams.gottranspose = 0;
vparams.octave = 0;
vparams.gotoctave = 0;
vparams.gotclef = 0;
cgotoctave=0;
coctave=0;
vparams.gotname = 0;
vparams.gotsname = 0;
vparams.gotmiddle = 0;

skipspace(&s);
if (isnumberp(&s) == 1) {
  num = readnump(&s);
  } else {
  num = interpret_voicestring(s);
  if(num == 0) event_error("No voice number or string in V: field");
  if(num == -1) {event_error("More than 16 voices encountered in V: fields");
    num =0;}
   skiptospace(&s);
  };
skipspace(&s);
while (*s != '\0') {
  parsed = parseclef(&s,word, &vparams.gotclef, vparams.clefname, &cgotoctave,&coctave);
  if (!parsed) parsed = parsetranspose(&s, word, &vparams.gottranspose, &vparams.transpose);
  if (!parsed) parsed = parseoctave(&s, word, &vparams.gotoctave, &vparams.octave);
  if (!parsed) parsed = parsename(&s, word, &vparams.gotname, vparams.namestring, V_STRLEN);
  if (!parsed) parsed = parsesname(&s, word, &vparams.gotsname, vparams.snamestring, V_STRLEN);
  if (!parsed) parsed = parsemiddle(&s, word, &vparams.gotmiddle, vparams.middlestring, V_STRLEN);
  }
if (cgotoctave) {vparams.gotoctave=1; vparams.octave=coctave;}
event_voice(num, s, &vparams);

/*
if (gottranspose) printf("transpose = %d\n", vparams.transpose);
 if (gotoctave) printf("octave= %d\n", vparams.octave);
 if (gotclef) printf("clef= %s\n", vparams.clefstr);
if (gotname) printf("parsevoice: name= %s\n", vparams.namestring);
if(gotmiddle) printf("parsevoice: middle= %s\n", vparams.middlestring);
*/
}


void parsenote(s)
char **s;
/* parse abc note and advance character pointer */
{
  int decorators[DECSIZE];
  int i, t;
  int mult;
  char accidental, note;
  int octave, n, m;
  char msg[80];
  int microtone;

  mult = 1;
  microtone=0;
  accidental = ' ';
  note = ' ';
  for (i = 0; i<DECSIZE; i++) {
	  decorators[i] = decorators_passback[i];
	  decorators_passback[i] = 0;
          }
  while (strchr(decorations, **s) != NULL) {
    t = strchr(decorations, **s) - decorations;
    decorators[t] = 1;
    *s = *s + 1;
  };
  /*check for decorated chord */
  if (**s == '[') {
    if (fileprogram == YAPS) event_warning("decorations applied to chord");
    for (i = 0; i<DECSIZE; i++) chorddecorators[i] = decorators[i];
    event_chordon(chorddecorators);
    if (fileprogram == ABC2ABC)
      for (i=0; i<DECSIZE; i++) decorators[i] = 0;
    parserinchord = 1;
    *s = *s + 1;
    skipspace(s);
  };
  if (parserinchord) {
    /* inherit decorators */
    if (fileprogram != ABC2ABC)
       for (i = 0; i<DECSIZE; i++) {
          decorators[i] = decorators[i] | chorddecorators[i];
          };
  };
  /* read accidental */
  switch (**s) {
  case '_':
    accidental = **s;
    *s = *s + 1;
    if (**s == '_') {
      *s = *s + 1;
      mult = 2;
    };
    microtone =  ismicrotone(s,-1);
    if (microtone) {
       if(mult ==2) mult = 1;
       else accidental = ' '; 
       }
    break;
  case '^':
    accidental = **s;
    *s = *s + 1;
    if (**s == '^') {
      *s = *s + 1;
      mult = 2;
    };
    microtone =  ismicrotone(s,1);
    if (microtone) {
       if(mult ==2) mult = 1;
       else accidental = ' '; 
       }

    break;
  case '=':
    accidental = **s;
    *s = *s + 1;
   /* if ((**s == '^') || (**s == '_')) {
      accidental = **s;
    };*/
    if (**s == '^') {
      accidental = **s;
      *s = *s + 1;
      microtone = ismicrotone(s,1);
      if (microtone ==0) accidental = '^';
      } 
    else if (**s == '_') { 
      accidental = **s;
      *s = *s + 1;
      microtone = ismicrotone(s,-1);
      if (microtone ==0) accidental = '_';
      } 
    break;
  default:
    /* do nothing */
    break;
  };
  if ((**s >= 'a') && (**s <= 'g')) {
    note = **s;
    octave = 1;
    *s = *s + 1;
    while ((**s == '\'') || (**s == ',')) {
      if (**s == '\'') {
        octave = octave + 1;
        *s = *s + 1;
      };
      if (**s == ',') {
        sprintf(msg, "Bad pitch specifier , after note %c", note);
        event_error(msg);
        octave = octave - 1;
        *s = *s + 1;
      };
    };
  } else {
    octave = 0;
    if ((**s >= 'A') && (**s <= 'G')) {
      note = **s + 'a' - 'A';
      *s = *s + 1;
      while ((**s == '\'') || (**s == ',')) {
        if (**s == ',') {
          octave = octave - 1;
          *s = *s + 1;
        };
        if (**s == '\'') {
          sprintf(msg, "Bad pitch specifier ' after note %c", note + 'A' - 'a');
          event_error(msg);
          octave = octave + 1;
          *s = *s + 1;
        };
      };
    };
  };
  if (note == ' ') {
    event_error("Malformed note : expecting a-g or A-G");
  } else {
    readlen(&n, &m, s);
    event_note(decorators, accidental, mult, note, octave, n, m);
    if (microtone) event_normal_tone();
  };
}

char* getrep(p, out)
char* p;
char* out;
/* look for number or list following [ | or :| */
{
  char* q;
  int digits;
  int done;
  int count;

  q = p;
  count = 0;
  done = 0;
  digits = 0;
  while (!done) {
    if (isdigit(*q)) {
      out[count] = *q;
      count = count + 1;
      q = q + 1;
      digits = digits + 1;
    } else {
      if (((*q == '-')||(*q == ','))&&(digits > 0)&&(isdigit(*(q+1)))) {
        out[count] = *q;
        count = count + 1;
        q = q + 1;
        digits = 0;
      } else {
        done = 1;
      };
    };
  };
  out[count] = '\0';
  return(q);
}

int checkend(s)
char* s;
/* returns 1 if we are at the end of the line 0 otherwise */
/* used when we encounter '\' '*' or other special line end characters */
{
  char* p;
  int atend;

  p = s;
  skipspace(&p);
  if (*p == '\0') {
    atend = 1;
  } else {
    atend = 0;
  };
  return(atend);
}

void readstr(out, in, limit)
char out[];
char **in;
int limit;
/* copy across alphanumeric string */
{
  int i;

  i = 0;
  while ((isalpha(**in)) && (i < limit-1)) {
    out[i] = **in;
    i = i + 1;
    *in = *in + 1;
  };
  out[i] = '\0';
}

void parse_precomment(s)
char* s;
/* handles a comment field */
{
  char package[40];
  char *p;

  if (*s == '%') {
    p = s+1;
    readstr(package, &p, 40);
    event_specific(package, p);
  } else {
    event_comment(s);
  };
}

void parse_tempo(place)
char* place;
/* parse tempo descriptor i.e. Q: field */
{
  char* p;
  int a, b;
  int n;
  int relative;
  char *pre_string;
  char *post_string;

  relative = 0;
  p = place;
  pre_string = NULL;
  if (*p == '"') {
    p = p + 1;
    pre_string = p;
    while ((*p != '"') && (*p != '\0')) {
      p = p + 1;
    };
    if (*p == '\0') {
      event_error("Missing closing double quote");
    } else {
      *p = '\0';
      p = p + 1;
      place = p;
    };
  };
  while ((*p != '\0') && (*p != '=')) p = p + 1;
  if (*p == '=') {
    p = place;
    skipspace(&p);
    if (((*p >= 'A') && (*p <= 'G')) || ((*p >= 'a') && (*p <= 'g'))) {
      relative = 1;
      p = p + 1;
    };
    readlen(&a, &b, &p);
    skipspace(&p);
    if (*p != '=') {
      event_error("Expecting = in tempo");
    };
    p = p + 1;
  } else {
    a = 1;
    b = 4;
    p = place;
  };
  skipspace(&p);
  n = readnump(&p);
  post_string = NULL;
  if (*p == '"') {
    p = p + 1;
    post_string = p;
    while ((*p != '"') && (*p != '\0')) {
      p = p + 1;
    };
    if (*p == '\0') {
      event_error("Missing closing double quote");
    } else {
      *p = '\0';
      p = p + 1;
    };
  };
  event_tempo(n, a, b, relative, pre_string, post_string);
}

void
preparse_words(s)
char *s;
/* takes a line of lyrics (w: field) and strips off */
/* any continuation character */
{
  int continuation;
  int l;

  /* printf("Parsing %s\n", s); */
  /* strip off any trailing spaces */
  l = strlen(s) - 1;
  while ((l>= 0) && (*(s+l) == ' ')) {
    *(s+l) = '\0';
    l = l - 1;
  };
  if (*(s+l) != '\\') {
    continuation = 0;
  } else {
    continuation = 1;
    /* remove continuation character */
    *(s+l) = '\0';
    l = l - 1;
    while ((l>= 0) && (*(s+l) == ' ')) {
      *(s+l) = '\0';
      l = l - 1;
    };
  };
  event_words(s, continuation);
}

void init_abbreviations()
/* initialize mapping of H-Z to strings */
{
  int i;

  for (i = 0; i< 'Z' - 'H'; i++) {
     abbreviation[i] = NULL;
  };
}

void record_abbreviation(char symbol, char *string)
/* update record of abbreviations when a U: field is encountered */
{
  int index;

  if ((symbol <'H') || (symbol > 'Z')) {
    return;
  };
  index = symbol - 'H';
  if (abbreviation[index] != NULL) {
    free(abbreviation[index]);
  };
  abbreviation[index] = addstring(string);
}

char *lookup_abbreviation(char symbol)
/* return string which s abbreviates */
{
  if ((symbol < 'H') || (symbol > 'Z')) {
    return(NULL);
  } else {
    return(abbreviation[symbol - 'H']);
  };
}

void free_abbreviations()
/* free up any space taken by abbreviations */
{
  int i;

  for (i=0; i<SIZE_ABBREVIATIONS; i++) {
    if (abbreviation[i] != NULL) {
      free(abbreviation[i]);
    };
  };
}

void parsefield(key, field)
char key;
char* field;
/* top-level routine handling all lines containing a field */
{
  char* comment;
  char* place;
  char* xplace;
  int iscomment;
  int foundkey;

  if (key == 'X')
    {
      int x;
    
      xplace =field;
      skipspace(&xplace);
      x = readnumf(xplace);
      if (inhead) {
        event_error("second X: field in header");
      };
      event_refno(x);
      inhead = 1;
      inbody = 0;
      parserinchord = 0;
      return;
    };

  if (parsing == 0) return;

  if ((inbody) && (strchr("EIKLMPQTVdswW", key) == NULL)) {
    event_error("Field not allowed in tune body");
  };
  comment = field;
  iscomment = 0;
  while ((*comment != '\0') && (*comment != '%')) {
    comment = comment + 1;
  };
  if (*comment == '%') {
    iscomment = 1;
    *comment = '\0';
    comment = comment + 1;
  };
  place =field;
  skipspace(&place);
  switch (key) {
  case 'K':
    foundkey = parsekey(place);
    if (inhead || inbody) {
      if (foundkey) {
        inbody = 1;
        inhead = 0;
      } else {
        if (inhead) {
          event_error("First K: field must specify key signature");
        };
      };
    } else {
      event_error("No X: field preceding K:");
    };
    break;
  case 'M':
    {
      int num, denom;

      if (strncmp(place, "none", 4) == 0) {
        event_timesig(4, 4, 0);
      } else {
        readsig(&num, &denom, &place);
        if ((*place == 's') || (*place == 'l')) {
          event_error("s and l in M: field not supported");
        };
        if ((num != 0) && (denom != 0)) {
          event_timesig(num, denom, 1);
        };
      };
      break;
    };
  case 'L':
    {
      int num, denom;

      readsig(&num, &denom, &place);
      if (num != 1) {
        event_error("Default length must be 1/X");
      } else {
        if (denom > 0) {
          event_length(denom);
        } else {
          event_error("invalid denominator");
        };
      };
      break;
    };
  case 'P':
    event_part(place);
    break;
  case 'I':
    event_info(place);
    break;
  case 'V':
    parsevoice(place);
    break;
  case 'Q':
    parse_tempo(place);
    break;
  case 'U':
    {
      char symbol;
      char container;
      char *expansion;

      skipspace(&place);
      if ((*place >= 'H') && (*place <= 'Z')) {
        symbol = *place;
        place = place + 1;
        skipspace(&place);
        if (*place == '=') {
          place = place + 1;
          skipspace(&place);
          if (*place == '!') {
            place = place + 1;
            container = '!';
            expansion = place;
            while ((!iscntrl(*place)) && (*place != '!')) {
              place = place +1;
            };
            if (*place != '!') {
              event_error("No closing ! in U: field");
            };
            *place = '\0';
          } else {
            container = ' ';
            expansion = place;
            while (isalnum(*place)) {
              place = place + 1;
            };
            *place = '\0';
          };
          if (strlen(expansion) > 0) {
            record_abbreviation(symbol, expansion);
            event_abbreviation(symbol, expansion, container);
          } else {
            event_error("Missing term in U: field");
          };
        } else {
          event_error("Missing '=' U: field ignored");
        };
      } else {
        event_warning("only 'H' - 'Z' supported in U: field");
      };
    };
    break;
  case 'w':
    preparse_words(place);
    break;
  case 'd':
    /* decoration line in abcm2ps */
    break;
  case 's':
    break;
  default:
    event_field(key, place);
  };
  if (iscomment) {
    parse_precomment(comment);  
  };
}

char* parseinlinefield(p)
char* p;
/* parse field within abc line e.g. [K:G] */
{
  char* q;

  event_startinline();
  q = p;
  while ((*q != ']') && (*q != '\0')) {
    q = q + 1;
  };
  if (*q == ']') {
    *q = '\0';
    parsefield(*p, p+2);
    q = q + 1;
  } else {
    event_error("missing closing ]");
    parsefield(*p, p+2);
  };
  event_closeinline();
  return(q);
}

void parsemusic(field)
char* field;
/* parse a line of abc notes */
{
  char* p;
  char* comment;
  char endchar;
  int iscomment;
  int starcount;
  int i;
  char playonrep_list[80];
  int decorators[DECSIZE];

  event_startmusicline();
  endchar = ' ';
  comment = field;
  iscomment = 0;
  while ((*comment != '\0') && (*comment != '%')) {
    comment = comment + 1;
  };
  if (*comment == '%') {
    iscomment = 1;
    *comment = '\0';
    comment = comment + 1;
  };

  p = field;
  skipspace(&p);
  while(*p != '\0') {
    if (((*p >= 'a') && (*p <= 'g')) || ((*p >= 'A') && (*p <= 'G')) ||
        (strchr("_^=", *p) != NULL) || (strchr(decorations, *p) != NULL)) {
      parsenote(&p);
    } else {
      switch(*p) {
      case '"':
        {
          struct vstring gchord;
   
          p = p + 1;
          initvstring(&gchord);
          while ((*p != '"') && (*p != '\0')) {
            addch(*p, &gchord);
            p = p + 1;
          };
          if (*p == '\0') {
            event_error("Guitar chord name not properly closed");
          } else {
            p = p + 1;
          };
          event_gchord(gchord.st);
          freevstring(&gchord);
          break;
        };
      case '|':
        p = p + 1;
        switch(*p) {
          case ':':
            event_bar(BAR_REP, "");
            p = p + 1;
            break;
          case '|' :
            event_bar(DOUBLE_BAR, "");
            p = p + 1;
            break;
          case ']' :
            event_bar(THIN_THICK, "");
            p = p + 1;
            break;
          default :
            p = getrep(p, playonrep_list);
            event_bar(SINGLE_BAR, playonrep_list);
        };
        break;
      case ':':
        p = p + 1;
        switch(*p) {
          case ':':
            event_bar(DOUBLE_REP, "");
            p = p + 1;
            break;
          case '|':
            p = p + 1;
            p = getrep(p, playonrep_list);
            event_bar(REP_BAR, playonrep_list);
            break;
          default:
            event_error("Single colon in bar");
        };
        break;
      case ' ':
        event_space();
        skipspace(&p);
        break;
      case TAB:
        event_space();
        skipspace(&p);
        break;
      case '(':
        p = p + 1;
        {
          int t, q, r;

          t = 0;
          q = 0;
          r = 0;
          t = readnump(&p);
          if ((t != 0) && (*p == ':')) {
            p = p + 1;
            q = readnump(&p);
            if (*p == ':') {
              p = p + 1;
              r = readnump(&p);
            };
          };
          if (t == 0) {
            event_sluron(1);
          } else {
            event_tuple(t, q, r);
          };
        };
        break;
      case ')':
        p = p + 1;
        event_sluroff(0);
        break;
      case '{':
        p = p + 1;
        event_graceon();
        ingrace=1;
        break;
      case '}':
        p = p + 1;
        event_graceoff();
        ingrace=0;
        break;
      case '[':
        p = p + 1;
        switch(*p) {
/* following lines are now redundant */
/*
        case '1':
          p = p + 1;
          event_rep1();
          break;
        case '2':
          p = p + 1;
          event_rep2();
          break;
*/
        case '|':
          p = p + 1;
          event_bar(THICK_THIN, "");
          break;
        default:
          if (isdigit(*p)) {
            p = getrep(p, playonrep_list);
            event_playonrep(playonrep_list);
          } else {
            if (isalpha(*p) && (*(p+1) == ':')) {
              p = parseinlinefield(p);
            } else {
              event_chordon(chorddecorators);
              parserinchord = 1;
            };
          };
          break;
        };
        break;
      case ']':
        p = p + 1;
        readlen(&chord_n, &chord_m, &p);
        event_chordoff(chord_n,chord_m);
        parserinchord = 0;
        for (i = 0; i<DECSIZE; i++) chorddecorators[i] = 0;
        break;
/*  hidden rest  */
      case 'x':
        {
          int n, m;

          p = p + 1;
          readlen(&n, &m, &p);
/* in order to handle a fermata applied to a rest we must
 * pass decorators to event_rest.
 */
          for (i = 0; i<DECSIZE; i++) {
  	    decorators[i] = decorators_passback[i];
	    decorators_passback[i] = 0;
            }
          event_rest(decorators,n, m, 1);
          break;
        };
/*  regular rest */
      case 'z':
        {
          int n, m;

          p = p + 1;
          readlen(&n, &m, &p);
/* in order to handle a fermata applied to a rest we must
 * pass decorators to event_rest.
 */
          for (i = 0; i<DECSIZE; i++) {
  	    decorators[i] = decorators_passback[i];
	    decorators_passback[i] = 0;
            }
          event_rest(decorators,n, m, 0);
          break;
        };
      case 'y': /* used by Barfly and abcm2ps to put space */
/* I'm sure I've seen somewhere that /something/ allows a length
 * specifier with y to enlarge the space length. Allow it anyway; it's
 * harmless.
 */
	{
	   int n, m;

           p = p + 1;
	   readlen(&n, &m, &p);
	   event_spacing(n, m);
           break;
	};
/* full bar rest */
      case 'Z':
        {
          int n, m;

          p = p + 1;
          readlen(&n, &m, &p);
          if (m != 1) {
            event_error("Z must be followed by a whole integer");
          };
          event_mrest(n, m);
          break;
        };
      case '>':
        {
          int n;

          n = 0;
          while (*p == '>') {
            n = n + 1;
            p = p + 1;
          };
          if (n>3) {
            event_error("Too many >'s");
          } else {
            event_broken(GT, n);
          };
          break;
        };
      case '<':
        {
          int n;

          n = 0;
          while (*p == '<') {
            n = n + 1;
            p = p + 1;
          };
          if (n>3) {
            event_error("Too many <'s");
          } else {
            event_broken(LT, n);
          };
          break;
        };
      case 's':
        if (slur == 0) {
          slur = 1;
        } else {
          slur = slur - 1;
        };
        event_slur(slur);
        p = p + 1;
        break;
      case '-':
        event_tie();
        p = p + 1;
        break;
      case '\\':
        p = p + 1;
        if (checkend(p)) {
          event_lineend('\\', 1);
          endchar = '\\';
        } else {
          event_error("'\\' in middle of line ignored");
        };
        break;
      case '+':
        if (oldchordconvention) {
          event_chord();
          parserinchord = 1 - parserinchord;
          if (parserinchord == 0) {
          for (i = 0; i<DECSIZE; i++) chorddecorators[i] = 0;
          };
        p = p + 1;
        break;
        }
      /* otherwise we fall through into the next case statement */
      case '!':
          {
          struct vstring instruction;
          char *s;
          char endcode;
   
          endcode = *p;
          p = p + 1;
          s = p;
          initvstring(&instruction);
          while ((*p != endcode) && (*p != '\0')) {
            addch(*p, &instruction);
            p = p + 1;
          };
          if (*p != endcode) {
            p = s;
            if (checkend(s)) {
              event_lineend('!', 1);
              endchar = '!';
            } else {
              event_error("'!' or '+' in middle of line ignored");
            };
          } else {
            event_instruction(instruction.st);
            p = p + 1;
          };
          freevstring(&instruction);
        }
        break;
      case '*':
        p = p + 1;
        starcount = 1;
        while (*p == '*') {
          p = p + 1;
          starcount = starcount + 1;
        };
        if (checkend(p)) {
          event_lineend('*', starcount);
          endchar = '*';
        } else {
          event_error("*'s in middle of line ignored");
        };
        break;
      case '/':
        p = p + 1;
        if (ingrace) event_acciaccatura();
        else event_error("stray / not in grace sequence");
        break;
      case '&':
        p = p + 1;
        event_split_voice();
        break;
      default:
        {
          char msg[40];

          if ((*p >= 'H') && (*p <= 'Z')) {
            event_reserved(*p);
          } else {
            sprintf(msg, "Unrecognized character: %c", *p);
            event_error(msg);
          };
        };
        p = p + 1;
      };
    };
  };
  event_endmusicline(endchar);
  if (iscomment) {
    parse_precomment(comment);
  };
}

void parseline(line)
char* line;
/* top-level routine for handling a line in abc file */
{
  char *p, *q;

/*  printf("%d parsing : %s\n", lineno, line);  */
  p = line;
  ingrace=0;
  skipspace(&p);
  if (strlen(p) == 0) {
    event_blankline();
    inhead = 0;
    inbody = 0;
    return;
  };
  if ((int)*p == '\\') {
    if (parsing) {
      event_tex(p);
    };
    return;
  };
  if ((int)*p == '%') {
    parse_precomment(p+1);
    if (!parsing) event_linebreak();
    return;
  };
  if (strchr("ABCDEFGHIKLMNOPQRSTUVdwsWXZ", *p) != NULL) {
    q = p + 1;
    skipspace(&q);
    if ((int)*q == ':') {
      if (*(line+1) != ':') {
        event_warning("whitespace in field declaration");
      };
      if ((*(q+1) == ':') || (*(q+1) == '|')) {
        event_warning("potentially ambiguous line");
      };
      parsefield(*p, q+1);
    } else {
      if (inbody) {
        if (parsing) parsemusic(p);
      } else {
        if (parsing) event_text(p);
      };
    };
  } else {
    if (inbody) {
      if (parsing) parsemusic(p);
    } else {
      if (parsing) event_text(p);
    };
  };
}

void parsefile(name)
char* name;
/* top-level routine for parsing file */
{
  FILE *fp;
  int reading;
  int fileline;
  struct vstring line;
  /* char line[MAXLINE]; */
  int t;
  int lastch, done_eol;

  /* printf("parsefile called %s\n", name); */
  /* The following code permits abc2midi to read abc from stdin */
  if ((strcmp(name, "stdin") == 0) || (strcmp(name, "-") == 0)) {
    fp = stdin;
  } else {
    fp = fopen(name, "r");
  };
  if (fp == NULL) {
    printf("Failed to open file %s\n", name);
    exit(1);
  };
  inhead = 0;
  inbody = 0;
  parseroff();
  reading = 1;
  line.limit = 4;
  initvstring(&line);
  fileline = 1;
  done_eol = 0;
  lastch = '\0';
  while (reading) {
    t = getc(fp);
    if (t == EOF) {
      reading = 0;
      if (line.len>0) {
        parseline(line.st);
        fileline = fileline + 1;
        lineno = fileline;
        if (parsing) event_linebreak();
      };
    } else {
      /* recognize  \n  or  \r  or  \r\n  or  \n\r  as end of line */
      /* should work for DOS, unix and Mac files */
      if ((t != '\n') && (t != '\r')) {
        addch((char) t, &line);
        done_eol = 0;
      } else {
        if ((done_eol) && (((t == '\n') && (lastch == '\r')) || 
                           ((t == '\r') && (lastch == '\n')))) {
          done_eol = 0;
          /* skip this character */
        } else {
          /* reached end of line */
          parseline(line.st);
          clearvstring(&line);
          fileline = fileline + 1;
          lineno = fileline;
          if (parsing) event_linebreak();
          done_eol = 1;
        };
      };
      lastch = t;
    };
  };
  fclose(fp);
  event_eof();
  freevstring(&line);
  if (parsing_started == 0) event_error("No tune processed. Possible missing X: field");
}    


int parsetune(FILE *fp)
/* top-level routine for parsing file */
{
  struct vstring line;
  /* char line[MAXLINE]; */
  int t;
  int lastch, done_eol;

  inhead = 0;
  inbody = 0;
  parseroff();
  intune  = 1;
  line.limit = 4;
  initvstring(&line);
  done_eol = 0;
  lastch = '\0';
  do {
    t = getc(fp);
    if (t == EOF) {
      if (line.len>0) {
        printf("%s\n",line.st);
        parseline(line.st);
        fileline_number = fileline_number + 1;
        lineno = fileline_number;
        event_linebreak();
      };
      break;
    } else {
      /* recognize  \n  or  \r  or  \r\n  or  \n\r  as end of line */
      /* should work for DOS, unix and Mac files */
      if ((t != '\n') && (t != '\r')) {
        addch((char) t, &line);
        done_eol = 0;
      } else {
        if ((done_eol) && (((t == '\n') && (lastch == '\r')) || 
                           ((t == '\r') && (lastch == '\n')))) {
          done_eol = 0;
          /* skip this character */
        } else {
          /* reached end of line */
          parseline(line.st);
          clearvstring(&line);
          fileline_number = fileline_number + 1;
          lineno = fileline_number;
          event_linebreak();
          done_eol = 1;
        };
      };
      lastch = t;
    };
  } while (intune);
  freevstring(&line);
return t;  
}

/*
int getline ()
{
  return (lineno);
}
*/
