/************************************************************************** * text.c: Functions for text input/output. * Part of MP, the MIDI Playground. * * Author: Daniel Barrett * Version: See the file "version.h". * Copyright: None! This program is in the Public Domain. * Please share it with others. ***************************************************************************/ #include "mp.h" /* Given the currently read character and the current state, compute * the new state and the MIDI value. */ STATE_T NewState(int c, STATE_T state, MIDI_VALUE *answer) { static BOOL inString = FALSE; if (inString) return(DoInString(c, state, answer, &inString)); else if (c == HELP_SYMBOL) { InputHelp(); return(state); } else if (state == STATE_NORMAL && c == '\"') { inString = TRUE; return(STATE_NORMAL); /* Doesn't matter. */ } else return(NonStringState(c, state, answer)); } /**************************************************************************** * Our finite automaton for non-strings. Process the character and return * the new state. ****************************************************************************/ STATE_T NonStringState(int c, STATE_T state, MIDI_VALUE *answer) { switch (state) { case STATE_NORMAL: return(DoNormal(c, answer)); case STATE_INCOMMENT: return(DoComment(c)); case STATE_INDECIMAL: return(DoDecimal(c, answer)); case STATE_INOCTAL: return(DoOctal(c, answer)); case STATE_STARTHEX: return(DoHex(c, answer)); case STATE_INHEX: return(DoInHex(c, answer)); case STATE_LEADZERO: return(DoLeadZero(c, answer)); case STATE_INCHAR: return(DoInChar(c, answer)); case STATE_STARTBINARY: return(DoBinary(c, answer)); case STATE_INBINARY: return(DoInBinary(c, answer)); case STATE_EXPECTQUOTE: return(DoExpectQuote(c, answer)); case STATE_BACKSLASHINCHAR: return(DoBackslash(c, answer, STATE_EXPECTQUOTE)); case STATE_SUCCESS: case STATE_OVERFLOW: case STATE_ERROR: return(state); } } /**************************************************************************** * We are not currently in a number. A character is read; decide what to * do with it. ****************************************************************************/ STATE_T DoNormal(int c, MIDI_VALUE *answer) { if (isspace(c)) return(STATE_NORMAL); else if (c == START_COMMENT) return(STATE_INCOMMENT); else if (c == START_BINARY) return(STATE_STARTBINARY); else if (c == '\'') return(STATE_INCHAR); else if (c == '0') return(STATE_LEADZERO); else if (toupper(c) == 'H') return(STATE_STARTHEX); else if (isdigit(c)) { *answer = D_TO_INT(c); return(STATE_INDECIMAL); } else if (isxdigit(c)) { *answer = H_TO_INT(c); return(STATE_INHEX); } else return(STATE_ERROR); } /**************************************************************************** * Base 10 (decimal) numbers. ****************************************************************************/ STATE_T DoDecimal(int c, MIDI_VALUE *answer) { if (isdigit(c)) return(IncreaseIfPossible(answer, D_TO_INT(c), 10, STATE_INDECIMAL)); else if (isspace(c)) return(STATE_SUCCESS); else return(STATE_ERROR); } /**************************************************************************** * Octal numbers. ****************************************************************************/ STATE_T DoOctal(int c, MIDI_VALUE *answer) { if (isoctal(c)) return(IncreaseIfPossible(answer, D_TO_INT(c), 8, STATE_INOCTAL)); else if (isspace(c)) return(STATE_SUCCESS); else return(STATE_ERROR); } /**************************************************************************** * Hexadecimal numbers. ****************************************************************************/ STATE_T DoHex(int c, MIDI_VALUE *answer) { if (isxdigit(c)) { *answer = H_TO_INT(c); return(STATE_INHEX); } else return(STATE_ERROR); } STATE_T DoInHex(int c, MIDI_VALUE *answer) { if (isxdigit(c)) return(IncreaseIfPossible(answer, H_TO_INT(c), 16, STATE_INHEX)); else if (isspace(c)) return(STATE_SUCCESS); else return(STATE_ERROR); } /**************************************************************************** * Binary numbers. ****************************************************************************/ STATE_T DoBinary(int c, MIDI_VALUE *answer) { if ((c == '0') || (c == '1')) { *answer = D_TO_INT(c); return(STATE_INBINARY); } else return(STATE_ERROR); } STATE_T DoInBinary(int c, MIDI_VALUE *answer) { if ((c == '0') || (c == '1')) return(IncreaseIfPossible(answer, D_TO_INT(c), 2, STATE_INBINARY)); else if (isspace(c)) return(STATE_SUCCESS); else return(STATE_ERROR); } /**************************************************************************** * Hook for negative numbers. Commented out right now. ****************************************************************************/ #ifdef ALLOW_NEGATIVE STATE_T DoNegative(int c, MIDI_VALUE *answer) { if (c == '0') return(STATE_LEADZERO); else if (isdigit(c)) { return(STATE_INDECIMAL); } else return(STATE_ERROR); } #endif /* ALLOW_NEGATIVE */ /**************************************************************************** * Leading zero was found: Do octal or hexadecimal as required. ****************************************************************************/ STATE_T DoLeadZero(int c, MIDI_VALUE *answer) { if (toupper(c) == 'X') return(STATE_STARTHEX); else if (isoctal(c)) { *answer = D_TO_INT(c); return(STATE_INOCTAL); } else if (isspace(c)) return(STATE_SUCCESS); else return(STATE_ERROR); } /**************************************************************************** * Append the digit "newNum" onto the right of the number *answer. * Don't allow overflow. Works for any base. Return newState on success. ****************************************************************************/ STATE_T IncreaseIfPossible(MIDI_VALUE *answer, int newNum, int base, STATE_T newState) { if ((*answer) > (LARGEST_VALUE / base)) return(STATE_OVERFLOW); else { *answer *= base; if ((*answer) > (LARGEST_VALUE - newNum)) return(STATE_OVERFLOW); else { *answer += newNum; return(newState); } } } /**************************************************************************** * Character-oriented routines. ****************************************************************************/ STATE_T DoInChar(int c, MIDI_VALUE *answer) { if (c == '\\') return(STATE_BACKSLASHINCHAR); else { *answer = c; return(STATE_EXPECTQUOTE); } } STATE_T DoExpectQuote(int c, MIDI_VALUE *answer) { return((c == '\'') ? STATE_SUCCESS : STATE_ERROR); } STATE_T DoBackslash(int c, MIDI_VALUE *answer, STATE_T newState) { switch (c) { case '0': *answer = '\0'; break; case 'a': *answer = '\a'; break; case 'b': *answer = '\b'; break; case 'f': *answer = '\f'; break; case 'n': *answer = '\n'; break; case 'r': *answer = '\r'; break; case 't': *answer = '\t'; break; case 'v': *answer = '\v'; break; case '\\': case '\'': case '\"': *answer = c; break; default: return(STATE_ERROR); } return(newState); } /**************************************************************************** * String-oriented routines. ****************************************************************************/ STATE_T DoInString(int c, STATE_T state, MIDI_VALUE *answer, BOOL *inString) { switch (state) { case STATE_NORMAL: if (c == '\"') { *inString = FALSE; return(STATE_NORMAL); } else if (c == '\\') return(STATE_BACKSLASHINSTRING); else { *answer = isspace(c) ? ' ' : c; return(STATE_SUCCESS); } break; case STATE_BACKSLASHINSTRING: return(DoBackslash(c, answer, STATE_SUCCESS)); } } /**************************************************************************** * Handling comments. Everything from comment symbol to the end of the * line is a comment. ****************************************************************************/ STATE_T DoComment(int c) { return( (c == '\n') ? STATE_NORMAL : STATE_INCOMMENT ); } /**************************************************************************** * Getting help. ****************************************************************************/ void InputHelp() { fprintf(stderr, "INPUT\tMEANING\n" "1-9...\tDecimal number.\n" "0...\tOctal number.\n" "0x...\tHexadecimal number (case-insensitive).\n" "H...\tHexadecimal number (case-insensitive).\n" "A-F...\tHexadecimal number (case-insensitive).\n" "#...\tBinary number.\n" "'x'\tThe character 'x'." " C character syntax like \\n and \\t is valid.\n" "\"...\"\tA text string." " (Newlines turn into space characters.)\n" "Largest legal input value is %ld (base 10).\n" "All values must be separated by whitespace.\n\n", LARGEST_VALUE); } /**************************************************************************** * Text output functions. ****************************************************************************/ void PrintNumber(MIDI_VALUE number, FILE *out) { fprintf(out, "Dec %3ld, Oct %3lo, Hex %3lX", number, number, number); PrintBinary(number, out); if (isprint((char)number)) fprintf(out, ", Char '%c'", number); putc('\n', out); } void PrintBinary(MIDI_VALUE number, FILE *out) { char buf[BITS_IN_MIDI_VALUE + 1]; int i, on; for (i=0; i