#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "main.h"
#include "text.h"
//***************************************************************************
// read_line
// Reads a single line from a text file. Handles all LF, CR and CRLF line
// endings. Used for reading the MML file.
//---------------------------------------------------------------------------
// return: pointer to string
//---------------------------------------------------------------------------
// note: you must deallocate the string with free() when you're done
//***************************************************************************
char *read_line(FILE *file)
{
// Allocate initial buffer
size_t bufpos = 0;
size_t buflen = 0x80;
char *buffer = (char*) malloc(buflen + 1);
if (buffer == NULL) {
fprintf(stderr, ERRORMSG_NOMEMORY);
exit(EXIT_FAILURE);
}
// Read until we find a newline
for (;;) {
// Read character
int ch = fgetc(file);
// Nul characters interfere with the parser
// You normally shouldn't find them in a MML file but just in case...
if (ch == '\0')
ch = ' ';
// End of line?
if (ch == '\n' || ch == EOF) {
break;
}
if (ch == '\r') {
ch = fgetc(file);
if (ch != '\n') ungetc(ch, file);
break;
}
// Convert all other blank characters into spaces
// This simplifies parsing the MML
if (ch == '\t' || ch == '\v' || ch == '\f')
ch = ' ';
// Do we need to allocate more space in the buffer?
if (bufpos == buflen) {
buflen *= 2;
buffer = (char*) realloc(buffer, buflen + 1);
if (buffer == NULL) {
fprintf(stderr, ERRORMSG_NOMEMORY);
exit(EXIT_FAILURE);
}
}
// Insert character into the buffer
buffer[bufpos] = ch;
bufpos++;
}
// Terminate the string and return it
buffer[bufpos] = '\0';
return buffer;
}
//***************************************************************************
// skip_spaces
// Returns a pointer to the first character in a string that isn't an ASCII
// space (U+0020). If there are no non-spaces left it returns a pointer to
// the end of the string (U+0000).
//---------------------------------------------------------------------------
// param text: pointer to string
//---------------------------------------------------------------------------
// return: pointer to first non-space character
//***************************************************************************
const char *skip_spaces(const char *text)
{
while (*text == ' ') text++;
return text;
}
//***************************************************************************
// skip_nonspaces
// The opposite of skip_spaces, returns a pointer to the first character in a
// string that's an ASCII space (U+0020). If there are no spaces left it
// returns a pointer to the end of the string (U+0000).
//---------------------------------------------------------------------------
// param text: pointer to string
//---------------------------------------------------------------------------
// return: pointer to first space character
//***************************************************************************
const char *skip_nonspaces(const char *text)
{
while (*text != ' ' && *text != '\0') text++;
return text;
}
//***************************************************************************
// get_number
// Parses an int-sized integer from a string.
//---------------------------------------------------------------------------
// param text: pointer to text (see notes)
// param result: pointer to variable where to store the result
//---------------------------------------------------------------------------
// return: 0 on success, -1 if no number
//---------------------------------------------------------------------------
// notes: this function takes a pointer to the pointer of the string. On
// success, that pointer is updated to point past the number. On failure,
// the pointer is left intact, and the stored result is 0.
//***************************************************************************
int get_number(const char **text, int *result)
{
const char *ptr = skip_spaces(*text);
// Where the pointer to the end of the number is stored
// Annoyingly, strtol() expects char* instead of const char* which means
// we have to drop the constness. Gotta be careful to make sure that we
// never attempt to write through it.
char *endptr;
// Try to read the number
// Make sure to check for overflow and such
errno = 0;
long number = strtol(ptr, &endptr, 10);
if (errno) goto error;
if (ptr == endptr) goto error;
if (sizeof(int) < sizeof(long)) {
if (number < INT_MIN) goto error;
if (number > INT_MAX) goto error;
}
// We're done
*text = endptr;
*result = number;
return 0;
error:
// Oops
*result = 0;
return -1;
}