-
Notifications
You must be signed in to change notification settings - Fork 1
/
nlParseUtils.c
executable file
·374 lines (307 loc) · 11 KB
/
nlParseUtils.c
1
/* nlParseUtils.c - utility routines for parsing Nightingale's notelist files.NB: Tab stops in this file should be set to 3 spaces. */#include "nlParse.h"extern char gInBuf[MAX_LINELENGTH];extern short gLineCount;/* ------------------------------------------------------------------- ExtractLong -- *//* Extracts a signed long value from a string having the format "label=x".The string is provided in <str>; the value is passed back to the callerby reference as a long in <val>. (Both vars allocated by caller.)Returns TRUE if okay, FALSE if error. */Boolean ExtractLong( char *str, /* source string */ long *val /* pass back extracted value */ ){ char *p; short count; p = strchr(str, '='); /* p will point to '=' */ if (p) { p++; /* advance pointer to character following '=' */ count = sscanf(p, "%ld", val); /* read numerical value in string as a long */ if (count > 0) return TRUE; } /* no '=' found in str or sscanf error */ *val = 0L; return FALSE;}/* Function for decoding flag field of a note. Can also be used for grace notes, whichhave only one flag (for mCode), or rests, which have all flags though some are neverused by Nightingale. Returns TRUE if OK, FALSE if error.This fills in the flag bitfields of <note>, which is allocated by the caller. Ithas a complete check for illegal characters in flagStr.Here's how the flags work for notes: flag numbers: "......" 0||||| 1|||| 2||| 3|| 4| 5 flagNum flagName values meaning 0 mCode '.' is not in chord '-' is in chord and is not main note '+' is in chord and is main note 1 tiedL '.' is not tiedL ')' is tiedL 2 tiedR '.' is not tiedR '(' is tiedR 3 slurredL '.' is not slurredL '>' is slurredL 4 slurredR '.' is not slurredR '<' is slurredR 5 inTuplet '.' is not in tuplet 'T' is in tuplet*/Boolean ExtractNoteFlags( char *flagStr, ANOTE *note /* isGrace field must be initialized! */ ){ char *p; p = flagStr; switch (*p) { case '.': /* not in chord; not main note */ note->inChord = FALSE; note->mainNote = FALSE; break; case '-': /* in chord; not main note */ note->inChord = TRUE; note->mainNote = FALSE; break; case '+': /* in chord; main note */ note->inChord = TRUE; note->mainNote = TRUE; break; default: /* illegal char */ goto illegal; } if (note->isGrace) return TRUE; /* no more flags for grace notes */ switch (*++p) { case '.': /* not tiedL */ note->tiedL = FALSE; break; case ')': /* tiedL */ note->tiedL = TRUE; break; default: goto illegal; } switch (*++p) { case '.': note->tiedR = FALSE; break; case '(': note->tiedR = TRUE; break; default: goto illegal; } switch (*++p) { case '.': note->slurredL = FALSE; break; case '>': note->slurredL = TRUE; break; default: goto illegal; } switch (*++p) { case '.': note->slurredR = FALSE; break; case '<': note->slurredR = TRUE; break; default: goto illegal; } switch (*++p) { case '.': note->inTuplet = FALSE; break; case 'T': note->inTuplet = TRUE; break; default: goto illegal; } return TRUE;illegal: STATUS_PRINTF("ERROR: Illegal character %c in note flag string.\n", *p); return FALSE;}/* Parses modifier strings like these: mods=2 [one modifier] mods=3,10,12 [three modifiers] mods=2:127,12 [two modifiers, one with non-zero data field]Passes back an array of modifiers and the number of modifiers in that array.Returns TRUE if all is OK, FALSE on error.NB: an earlier version of this function used strtok to do the parsing, buthad mysterious problems. This version is due to Doug McKenna (thanks, Doug)and does the parsing itself. It seems very solid and it checks its input morecarefully, but may fail with non-ASCII chars. */#define MODS_PREFIX "mods"Boolean ExtractNoteMods( char *modStr, /* Input: modifier string */ short *numMods, /* Output: number of modifiers */ AMOD tmpMods[] /* Output: array of modifiers */ ){ char *p; Boolean nextIntegerIsAValue; short modIndex, ashort; /* Ensure all our error returns deliver a non-garbage count. */ *numMods = 0; /* If this isn't the start of a mods declaration, skip it. Note that sizeof() includes a C string's final null byte. */ if (strncmp(modStr,MODS_PREFIX,sizeof(MODS_PREFIX)-1) != 0) return FALSE; p = modStr+sizeof(MODS_PREFIX)-1; /* The syntax following the prefix consists of an '=' followed by a series of items, each of which consists of a ':' or a ',' followed by an integer code or value. Parse each separate modifier spec into the mod array, til end of string. Note that *numMods is always delivered 0 if there's any error at all, other than overflowing the tmpMods array, which currently isn't an error. */ for (modIndex=0; *p!='\0' && modIndex<MAX_MODNRS; modIndex++) { if (*p!=',' && *p!=':' && *p!='=') return FALSE; /* Bad syntax: unexpected character */ if (*p=='=' && modIndex>0) return FALSE; /* Bad syntax: more than one '=' */ if (*p==':' && modIndex==0) return FALSE; /* Bad syntax: expecting a mod code first */ nextIntegerIsAValue = (*p++ == ':'); /* Parse the next integer, with optional white space in front of it. */ while (isspace(*p)) p++; if (isdigit(*p) || *p=='-') { Boolean isNegative = (*p=='-'); if (isNegative) p++; ashort = 0; while (isdigit(*p)) ashort = 10* ashort + (*p++ - '0'); if (isNegative) ashort = -ashort; } else return FALSE; /* Bad syntax: expecting an integer */ if (nextIntegerIsAValue) { /* Then we're really talking about last mod code's value, so back up the loop by one and overwrite last value (0) Note that our check at the loop top precludes modIndex==0. */ tmpMods[--modIndex].data = ashort; } else { /* Store the mod code with default data value 0, and move on. */ tmpMods[modIndex].modCode = ashort; tmpMods[modIndex].data = 0; } /* p points to first character after integer, skip any white space now. */ while (isspace(*p)) p++; /* And on to the next integer in mods list, prefixed by either ':' or ','. */ } *numMods = modIndex; /* Since loop never breaks, index will be incremented when loop falls out, and thus is always a count */ return TRUE; /* Ignores more than MAX_MODNRS mods */}/* ----------------------------------------------------------- ExtractDelimString -- *//* Given a C string, extract a substring enclosed in the given delimiter: thesubstring can contain any characters other than the delimiter, including whitespace.It must not be longer than <maxLen> chars (excluding terminating null). Return-1 if we can't even find a delimiter to start with, the position of the startingdelimiter otherwise. Cf. Nightingale's ExtractString(), plus code in itsParseStructComment(). */long ExtractDelimString(char string[], char delimL, char delimR, char subStr[], long subStrMaxLen){ char *p, *q; long startPos; p = strchr(string, delimL); /* Point to starting delimiter */ startPos = p-string; if (!p) return -1; p++; q = subStr; while (*p!=delimR && q-subStr<subStrMaxLen) /* Look for ending delimiter */ *q++ = *p++; *q = '\0'; /* Wipe out ending delimiter */ return startPos;}/* ------------------------------------------------------------- NotelistVersion -- *//* If the input file is a version of Notelist that we support, return a nonnegativeinteger version number, else return -1.If you change any of the COMMENT_NLHEADER's or add a new one, cf. ParseStructComment(). */#define COMMENT_NLHEADER0 "%%Score file=" /* start of structured comment: before Ngale 3.1 */#define COMMENT_NLHEADER1 "%%Score-V1 file=" /* start of structured comment: Ngale 3.1 thru early 99 */#define COMMENT_NLHEADER2 "%%Notelist-V2 file=" /* start of structured comment: Ngale 99 */short NotelistVersion(){ short index = 1; /* index of error string in NOTELIST_STRS 'STR#' */ /* See if file starts with a Notelist structured comment header (e.g., "%%Notelist-V2 file='Mozart clart quintet' partstaves=1 1 1 1 1 0") */ if (strncmp(gInBuf, COMMENT_NLHEADER0, strlen(COMMENT_NLHEADER0))==0) return 0; if (strncmp(gInBuf, COMMENT_NLHEADER1, strlen(COMMENT_NLHEADER1))==0) return 1; if (strncmp(gInBuf, COMMENT_NLHEADER2, strlen(COMMENT_NLHEADER2))==0) return 2; Err: /* Notelist file version isn't supported: say so. */ ReportParseFailure("NotelistVersion"); return -1;}/* -------------------------------------------------------------------------------- */void ReportParseFailure(char *functionName){ char tempBuf[MAX_LINELENGTH]; long len; strcpy(tempBuf, gInBuf); tempBuf[MAX_LINELENGTH-1] = '\0'; /* Just in case */ len = strlen(tempBuf); tempBuf[len-1] = '\''; /* Replace the end-of-line (or end of the end-of-line sequence) */ STATUS_PRINTF("ERROR (%s): problem while parsing line no. %d in notelist:\n '%s\n", functionName, gLineCount, tempBuf);}/* This function is equivalent to ISO fgets() except it attempts to recognize any ofCR (Mac), LF (Unix), or CR followed by LF (Windows) as terminating a line, so as tohandles files from all three OS's properly regardless of the platform it's runningon. It relies on getc(), but unfortunately, the Symantec C library version of getc()on Mac converts CRs to LFs, so, with Symantec, there's no way we can distinguish asingle Windows newline from two consecutive Mac or Unix newlines. The only problemthis causes is that, with Symantec C and a Windows file, we'll find a (spurious)empty line after each genuine line.This is based on the not too readable version of fgets() in the Symantec library. */char *fgetsAllPlatform(char *s, int n, FILE *fp){ char *t = s; int c, cNext, status; if (n < 1) return(NULL); while (--n) { c = getc(fp); if (c < 0) { if (c == EOF && t != s) break; return(NULL); } *t++ = c; /* Check for either CR or LF; if so, the line is ended. Also, if it's CR, * check if the next char is LF and, if so, discard it (the Windows case). */ if (c == '\n') break; if (c == '\r') { cNext = getc(fp); if (cNext != '\n') { status = ungetc(cNext, fp); if (status==EOF) STATUS_PRINTF("ERROR (fgetsAllPlatform): at line no. %d, ungetc failed.\n", gLineCount); } break; } } *t = '\0'; return(s);}