/* ************************************************************************** * * --- File: abcpp.c * * --- Purpose: simple preprocessor for abc music files. * * --- Copyright (C) Guido Gonzato, guido dot gonzato at poste dot it * Modifications by John Fattaruso, johnf@ti.com, * Ewan A. Macpherson emacpher@umich.edu, and * D. Glenn Arthur Jr. dglenn@radix.net * * --- Last updated: 22 November 2005 (unofficial hack, branching from the * source code dated 14 April 2005 -- search for instances * of the line "#ifndef OMIT_DGA_MODS" to find my changes) * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. * * ************************************************************************ */ /* this is a no-brainer program. No efficient memory allocation, no lists, * no trees and some such. Only fixed-length arrays for now! */ #include #include #include #include #define PROGNAME "abcpp" #ifndef OMIT_DGA_MODS #define DATE "23 November 2005" #define VERSION "1.3.2dga2" #else #define DATE "14 April 2005" #define VERSION "1.3.2" #endif #define MAX_TOKENS 20 /* # of tokens following #ifdef etc. */ #define MAX_MACROS 100 /* # of #defined macros */ #define TOKEN_LENGHT 1024 /* max. token length */ #define LINE_LENGTH 1024 /* max. length of input line */ #define TRUE 1 #define FALSE 0 #ifdef WIN32 #define LIB_DIR "C:\\ABCPP\\" #else #define LIB_DIR "/usr/share/abcpp/" #endif /* function prototypes */ void error (int, char *); void handle_directive (char *, FILE *); void include_file (char *, FILE *); void output_line (FILE *, char *); void preprocess_line (FILE *, FILE *, char *); void remove_bang (char [], short int); void remove_deco (char [], char); void replace (char [], char *, char *); void replace_plus (char []); void strdel (char [], int, int); void strins (char [], char *, int); void define_macro (char [], char []); void undefine_macro (char []); void usage (void); void warning (int, char *); /* global variables */ int ndefines = 0; /* # of command line defines */ int condition = TRUE; /* condition after #ifdef */ int cond_else = FALSE; /* condition for #else */ int ifdef = FALSE; /* #ifdef was read */ int nmacros = 0; /* # of defined macros */ int line_num = 0; /* # of line being processed */ /* program options */ short int strip = FALSE; /* strip option */ short int strip_chords = FALSE; /* strip chords option */ short int strip_plus = FALSE; /* change +abc+ to [abc] */ short int strip_bang = FALSE; /* remove single '!' */ short int change_bang = FALSE; /* change single '!' to !break! */ short int undefine = FALSE; /* #undefine was read */ short int doremi = FALSE; /* #define 'do', 're', 'mi'... */ #ifndef OMIT_DGA_MODS short int DGAoverride_defines = FALSE; /* cmd line defs override #define */ #endif /* this array contains command-line simple defines (existence but no value) */ char defines[MAX_TOKENS][TOKEN_LENGHT]; struct macro { char macroname [TOKEN_LENGHT]; char replacement [TOKEN_LENGHT]; #ifndef OMIT_DGA_MODS int cmdline; /* Indicates whether macro was in file or on command line. */ #endif } macros [MAX_MACROS]; #define NUM_NOTES 14 /* this one will contain Latin notes */ char notes[NUM_NOTES][2][4] = { {"DO", "C"}, {"RE", "D"}, {"MI", "E"}, {"FA", "F"}, {"SOL", "G"}, {"LA", "A"}, {"SI", "B"}, {"do", "c"}, {"re", "d"}, {"mi", "e"}, {"fa", "f"}, {"sol", "g"}, {"la", "a"}, {"si", "b"} }; /* ----- */ int main (int argc, char *argv[]) { FILE *in, *out; char line[LINE_LENGTH]; char tmp[256], tmp1[256], tmp2[256]; int i, nfilespec = 0; /* parse command line */ /* stdin is default unless in-file specified on command line */ in = stdin; /* stdout is default unless in-file and out-file specified * on command line */ out = stdout; for (i = 1; i < argc; ++i) { if ('-' == argv[i][0]) { /* it's a command-line define */ /* check if it's a built-in define */ if (!strcmp ("-s", argv [i])) /* strip */ strip = TRUE; if (!strcmp ("-c", argv [i])) /* strip_chords */ strip_chords = TRUE; if (!strcmp ("-p", argv [i])) /* strip_plus */ strip_plus = TRUE; if (!strcmp ("-b", argv [i])) /* strip_bang */ strip_bang = TRUE; if (!strcmp ("-k", argv [i])) /* change_bang */ change_bang = TRUE; #ifndef OMIT_DGA_MODS if (!strcmp ("-o", argv [i])) /* override defines */ DGAoverride_defines = TRUE; #endif if (!strcmp ("-h", argv [i])) { usage (); exit (0); } /* in any case, copy it to defines */ if (MAX_TOKENS == ndefines) { sprintf (tmp, "Too many command line defines (max. %d).", MAX_TOKENS); error (1, tmp); exit (1); } else { #ifndef OMIT_DGA_MODS /* Look for an '=' within the token. If present, copy the entire * token to a buffer where we can munge it into a fake "#define" * input line, and feed that to handle_directive() -- both because * I'm lazy and because I want to minimize the number of footprints * I leave on the code without knowing whether this mod will be * rolled into the official code or not. And also because this way * if the logic in handle_directives() changes later, the change * does not need to be propogated to here by hand. Trading a bit * of efficiency for maintainability. If the original author wants * to redo this, my feelings will not be hurt. (At the very least, * my variables should be renamed.) -- D. Glenn Arthur Jr. */ if ( NULL != strchr(argv[i], '=') ) { char DGAtmpbuff[LINE_LENGTH]; /* Declared here to keep my mods in */ int DGAtmpidx; /* one place. Should really be at */ /* top of function or re-use some */ /* other convenient buffer. */ /* Build a string that looks just like a #define in an input * file: append the command-line token to the string "#define ", * then change the '=' to a space. If the macro or the definition * need to contain spaces and quotes, figuring out how to get them * in here is the user's problem... */ strcpy ( DGAtmpbuff, "#define " ); strcat ( DGAtmpbuff, &argv[i][1] ); DGAtmpidx = strcspn(DGAtmpbuff,"="); DGAtmpbuff[DGAtmpidx] = ' '; /* Make the call. It'll be as though command-line macro * definitions had been prepended to the input file; if * they're redefined within the input file, those definitions * will override command-line definitions, unless the '-o' * option was used. */ handle_directive ( DGAtmpbuff , out ); } /* No '=' in the arg, so just add the token to the defines[] list * instead of the macros[] list. */ else { strcpy ((char *) defines[ndefines], &argv[i][1]); ndefines++; } #else strcpy ((char *) defines[ndefines], &argv[i][1]); ndefines++; #endif } } else { /* it's a filespec */ switch (nfilespec) { /* no files specified yet, so this is the infile spec */ case 0: strcpy (tmp1, argv[i]); if (NULL == (in = fopen (tmp1, "r"))) { sprintf (tmp, "%s: can't open %s\n", PROGNAME, tmp1); error (1, tmp); exit (1); } nfilespec++; break; /* 1 file specified already, so this is the outfile spec */ case 1: strcpy (tmp2, argv[i]); /* check if input and output are the same file */ if (!strcmp (tmp1, tmp2)) { sprintf (tmp, "%s and %s cannot be the same file.", tmp1, tmp2); error (1, tmp); fclose (in); exit (1); } if (NULL == (out = fopen (tmp2, "w"))) { sprintf (tmp, "%s: can't open %s\n", PROGNAME, tmp2); error (1, tmp); fclose (in); exit (1); } nfilespec++; break; /* 2 files specified already - error! */ default: sprintf (tmp, "Too many files specified!\n"); error (1, tmp); fclose (in); fclose (out); usage (); exit (1); } /* switch */ } /* else */ } /* for */ #ifndef OMIT_DGA_MODS /* Now that we've finished parsing the command line and dealing with it, * flag any macros defined in the macros[] table so that we can avoid * overwriting them if the "command-line macros take precedence" option * was set. */ { int DGActr; /* Declared here while code is provisional; will be moved */ /* to top of function if code is accepted into main distro */ for ( DGActr=0 ; DGActr < nmacros ; DGActr++ ) { macros[DGActr].cmdline = TRUE; } } /* (Possible future mod here: allow specification of precedence on a * per macro basis, maybe "-dFOO=zoink" for one that can be overriden by * a "#define" in the input and "-DBAR=whee" for one that overrides the * "#define"s?) */ #endif while (fgets (line, LINE_LENGTH, in) != NULL) preprocess_line (in, out, line); fclose (in); fclose (out); exit (0); } /* ----- */ void usage () { fprintf (stderr, "%s %s, %s\n", PROGNAME, VERSION, DATE); fprintf (stderr, "Copyright 2001-2005 Guido Gonzato \n"); fprintf (stderr, "This is free software with ABSOLUTELY NO WARRANTY.\n\n"); fprintf (stderr, "Usage: %s [-s] [-c] [-p] [-h] [-def1 -def2 ...]" " [inputfile] [outputfile]\n\n", PROGNAME); fprintf (stderr, "-s:\tstrip input of w: fields and decorations\n"); fprintf (stderr, "-c:\tstrip input of accompaniment chords\n"); fprintf (stderr, "-p:\tchange '+'-delimited chords to '[]'-delimited\n"); fprintf (stderr, "-b:\tremove single '!'\n"); fprintf (stderr, "-k:\tchange single '!' to !break!\n"); #ifndef OMIT_DGA_MODS fprintf (stderr, "-o:\tcommand line macros override defines in input\n"); #endif fprintf (stderr, "-h:\tshow usage\n"); exit (0); } /* void usage () */ /* ----- */ /* delete num characters from string s, starting from pos. */ void strdel (char s[], int pos, int num) { int i, len = strlen (s); /* move characters to the left */ for (i = pos + num; i < len; i++) s [i - num] = s [i]; s [len - num] = '\0'; } /* strdel () */ /* ----- */ /* insert string 'ins' in string 's', starting from 'pos' */ void strins (char s[], char *ins, int pos) { int i, inslen = strlen (ins), slen = strlen (s); /* move chars to the right */ for (i = slen - 1; i >= pos; i--) s [i + inslen] = s [i]; s [slen + inslen] = '\0'; /* insert 'ins' */ for (i = 0; i < inslen; i++) s [i + pos] = ins [i]; } /* strins () */ /* ----- */ void replace (char line [], char *orig, char *repl) { int ind, len = strlen (orig), offset = 0; char *tmp; /* replace all occurrances of 'orig' in 'line' with 'repl'. The * character '\' can be used to prevent 'orig' from being replaced, * if need be. For example, if 'do' is #defined as 'c', * Guido -> Guic, Gui\do -> Guido. */ while (NULL != (tmp = strstr (line + offset, orig))) { ind = tmp - line; /* position of text to replace */ /* if the text to replace isn't preceded by '\', go ahead */ if ('\\' != line [ind - 1]) { strdel (line, ind, len); strins (line, repl, ind); } else { /* remove the '\' */ strdel (line, ind - 1, 1); offset = ind + 1; /* start searching from this new position */ } } } /* replace () */ /* ----- */ void define_macro (char newmacroname [], char newdefinition []) { struct macro *itemptr; char tmp [TOKEN_LENGHT]; /* replace '~' with ' ' in macro definitions */ replace (newmacroname, "~", " "); replace (newdefinition, "~", " "); /* sort macros, find newmacroname [] to see if it's already there */ qsort (macros, nmacros, sizeof (struct macro), (int (*)(const void *, const void *)) strcmp); itemptr = bsearch (newmacroname, macros, nmacros, sizeof (struct macro), (int (*)(const void *, const void *)) strcmp); /* found - already defined */ if (NULL != itemptr) { #ifndef OMIT_DGA_MODS if ( itemptr->cmdline && DGAoverride_defines ) { sprintf (tmp, "Macro %s is superseded by command-line definition.", newmacroname); warning (1, tmp); } else { strcpy (itemptr->macroname, newmacroname); strcpy (itemptr->replacement, newdefinition); sprintf (tmp, "Macro %s already defined.", newmacroname); warning (1, tmp); } #else strcpy (itemptr->macroname, newmacroname); strcpy (itemptr->replacement, newdefinition); sprintf (tmp, "Macro %s already defined.", newmacroname); warning (1, tmp); #endif } else { strcpy (macros [nmacros].macroname, newmacroname); strcpy (macros [nmacros].replacement, newdefinition); #ifndef OMIT_DGA_MODS macros[nmacros].cmdline = FALSE; #endif nmacros++; } } /* ----- */ void undefine_macro (char macro []) { char *itemptr; char tmp [TOKEN_LENGHT]; if (0 == nmacros) { warning (1, "No macros #defined yet - ignored."); return; } /* sort macros, find macro [] and remove it */ qsort (macros, nmacros, sizeof (struct macro), (int (*)(const void *, const void *)) strcmp); itemptr = bsearch (macro, macros, nmacros, sizeof (struct macro), (int (*)(const void *, const void *)) strcmp); /* not found */ if (NULL == itemptr) { sprintf (tmp, "Macro %s non found in definition table - ignored.", macro); warning (1, tmp); return; } else { strcpy (itemptr, macros [nmacros - 1].macroname); strcpy (itemptr + TOKEN_LENGHT, macros [nmacros - 1].replacement); nmacros--; } } /* undefine_macro () */ /* ----- */ void remove_deco (char line [], char what) { char *beg_deco, *end_deco; char drop_me [] = " "; int i, j; drop_me [0] = what; while (NULL != (beg_deco = strstr (line, drop_me))) { /* first '!' found */ i = beg_deco - line; if (NULL != (end_deco = strstr (beg_deco + 1, drop_me))) { /* second '!' found */ j = end_deco - beg_deco + 1; strdel (line, i, j); } else { warning (i + 1, "Unbalanced char found - the line is probably wrong."); return; } } } /* remove_deco () */ /* ----- */ void replace_plus (char line []) { char *beg_chord, *end_chord; int i, j; while (NULL != (beg_chord = strstr (line, "+"))) { /* first '+' found */ i = beg_chord - line; if (NULL != (end_chord = strstr (beg_chord + 1, "+"))) { /* second '+' found */ j = end_chord - line; /* replace the first '+' with '[', then the second '+' with ']' */ strdel (line, i, 1); strins (line, "[", i); strdel (line, j, 1); strins (line, "]", j); } else { warning (i, "Unbalanced char found - the line is probably wrong."); return; } } } /* replace_plus () */ /* ----- */ void remove_bang (char line[], short int write_break) { char *bang; int i, skip = 0; short int was_deco = FALSE; while (NULL != (bang = strstr (line + skip, "!"))) { /* '!' found */ i = bang - line; if ( ('\n' == line [i + 1]) || (' ' == line [i + 1]) ) { was_deco = FALSE; /* remove it, it cannot be a !decoration! */ strdel (line, i, 1); if (write_break) { strins (line, "!break!", i); /* skip 7 characters */ skip = skip + i + 7; } } else was_deco = TRUE; /* it's a !decoration! */ } } /* remove_bang () */ /* ----- */ void output_line (FILE * out, char *line) { int i; if (condition) { /* !!! ADD OPTIONS HERE !!! */ if (TRUE == strip) remove_deco (line, '!'); if (TRUE == strip_chords) remove_deco (line, '"'); if (TRUE == strip_plus) replace_plus (line); if (TRUE == strip_bang) remove_bang (line, FALSE); if (TRUE == change_bang) remove_bang (line, TRUE); else { /* replace macros and notes */ if (!undefine) for (i = 0; i < nmacros; i++) replace (line, macros[i].macroname, macros[i].replacement); if (doremi) for (i = 0; i < NUM_NOTES; i++) replace (line, notes[i][0], notes[i][1]); } /* replace (line, "\\", ""); */ if ((FALSE == strip) || (NULL == strstr (line, "w:"))) fprintf (out, "%s", line); } } /* void output_line () */ /* ----- */ void preprocess_line (FILE * in, FILE * out, char *line) { line_num++; if (line [0] != '#') output_line (out, line); else handle_directive (line, out); } /* void preprocess_line () */ /* ----- */ #define FILENAME_LENGTH 256 void include_file (char *file, FILE * out) { FILE *in; char line [LINE_LENGTH], tmp [FILENAME_LENGTH], filename [FILENAME_LENGTH]; /* if the file name starts with '<', then search for it in LIB_DIR */ if ('<' == file[0]) { strdel (file, 0, 1); strdel (file, strlen (file) - 1, 1); strcpy (filename, LIB_DIR); strcat (filename, file); } else strcpy (filename, file); if (NULL == (in = fopen (filename, "r"))) { sprintf (tmp, "%s: can't open %s\n", PROGNAME, filename); error (1, tmp); } while (fgets (line, LINE_LENGTH, in) != NULL) preprocess_line (in, out, line); fclose (in); } /* include_file () */ /* ----- */ void warning (int col, char *line) { fprintf (stderr, "\a%s: *** warning in line %d:%d\n", PROGNAME, line_num, col); fprintf (stderr, "%s\n", line); } /* warning () */ /* ----- */ void error (int col, char *line) { fprintf (stderr, "\a%s: *** error in line %d:%d\n", PROGNAME, line_num, col); fprintf (stderr, "%s\n", line); exit (1); } /* error () */ /* ----- */ #define N_DIRECTIVES 15 /* supported directives */ void handle_directive (char *line, FILE * out) { int esc, ch, i, j, ntoken; char tokens [MAX_TOKENS][TOKEN_LENGHT], token [TOKEN_LENGHT], tmp [LINE_LENGTH]; enum { COMMENT, ABC, DEFINE, DOREMI, ELIFDEF, ELIFNDEF, ELSE, ENDIF, IFDEF, IFNDEF, INCLUDE, REDEFINE, RESUME, SUSPEND, UNDEFINE } directive; char *directives [N_DIRECTIVES] = { "#", "#abc", "#define", "#doremi", "#elifdef", "#elifndef", "#else", "#endif", "#ifdef", "#ifndef", "#include", "#redefine", "#resume", "#suspend", "#undefine", }; ch = line[0]; i = j = ntoken = 0; esc = FALSE; while ('\0' != line[i]) { if ('\\' == line[i]) { esc = TRUE; i++; } /* the token starts and ends with " not preceded by \ */ if (('"' == line[i]) && (FALSE == esc)) { i++; while (('"' != line[i]) && ('\0' != line[i])) { token[j++] = line[i++]; } i++; } else while ((!isspace (line[i])) && ('\0' != line[i])) { if ('\\' == line[i]) i++; token[j++] = line[i++]; } token [j] = '\0'; esc = FALSE; strcpy (tokens [ntoken], token); while (isspace (line [i])) i++; j = 0; if (MAX_TOKENS == ++ntoken) warning (1, "Too many tokens on directive line - line truncated."); } /* while */ /* ok, now find out the directive and decide what to do */ /* no binary search for so few directives... */ directive = -1; for (i = 0; i < N_DIRECTIVES; i++) if (!strcmp (tokens [0], directives [i])) { directive = i; break; } switch (directive) { case IFDEF: case IFNDEF: if (1 == ntoken) error (1, "#if(n)def must be followed by at least 1 symbol."); if (TRUE == ifdef) error (1, "Cannot nest #if(n)def."); ifdef = TRUE; condition = FALSE; cond_else = TRUE; /* if any of the tokens are defined, then TRUE */ for (i = 1; i < ntoken; i++) for (j = 0; j < ndefines; j++) if (!strcmp (tokens[i], defines[j])) { condition = TRUE; cond_else = FALSE; } if (IFNDEF == directive) { condition = !condition; cond_else = !cond_else; } break; case ELIFDEF: case ELIFNDEF: if (1 == ntoken) error (1, "#elif(n)def must be followed by at least 1 symbol."); if (FALSE == ifdef) warning (1, "#elif(n)def without #ifdef - unpredictable behaviour."); if (FALSE == condition) { /* there was an #ifdef or an #elifdef */ for (i = 1; i < ntoken; i++) for (j = 0; j < ndefines; j++) if (!strcmp (tokens[i], defines[j])) { condition = TRUE; cond_else = FALSE; } } else condition = FALSE; /* leave cond_else alone */ if (ELIFNDEF == directive) condition = !condition; break; case ELSE: if (ntoken != 1) warning (1, "#else should not be followed by symbols - extra ignored."); if (FALSE == ifdef) error (1, "#else without #ifdef."); if (TRUE == cond_else) condition = TRUE; else condition = FALSE; break; case ENDIF: if (ntoken != 1) warning (1, "#endif should not be followed by symbols - extra ignored."); if (FALSE == ifdef) warning (1, "#endif without #ifdef - ignored."); condition = TRUE; ifdef = cond_else = FALSE; break; case DEFINE: if (condition) { if (ntoken > 3) error (1, "#define must be followed by exactly 1 string."); if (MAX_MACROS == nmacros) { warning (1, "Max. number of macros reached - new definition ignored."); break; } if (2 == ntoken) { undefine_macro (tokens [1]); break; } define_macro (tokens [1], tokens [2]); } break; case INCLUDE: if (condition) include_file (tokens[1], out); break; case UNDEFINE: case SUSPEND: if (condition) undefine = TRUE; break; case REDEFINE: case RESUME: if (condition) undefine = FALSE; break; case ABC: if (condition) doremi = FALSE; break; case DOREMI: if (condition) doremi = TRUE; break; case COMMENT: ; break; default: sprintf (tmp, "%s: Unknown preprocessor directive", tokens [0]); warning (1, tmp); ; } /* switch (directive) */ } /* void handle_directive () */ /* ----- */ /* --- End of file abcpp.c --- */