#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "main.h"
#include "mml_macros.h"
#include "blob.h"
#include "text.h"
// Macro limits
#define MACRO_MAX_NUMBER 999
#define MACRO_UPPER_START (MACRO_MAX_NUMBER+1)
#define MACRO_LOWER_START (MACRO_UPPER_START+26)
#define NUM_MACROS (MACRO_LOWER_START+26)
// Where every macro is stored
char *macros[NUM_MACROS] = { 0 };
//***************************************************************************
// parse_macro_id
// Parses the name of a macro following the ! and returns its internal ID.
//---------------------------------------------------------------------------
// param text ....... pointer to pointer to string
// param filename ... file name (for error reporting)
// param line_num ... current line (for error reporting)
// param col_num .... current column (for error reporting)
//---------------------------------------------------------------------------
// return ........... macro ID on success (0 or larger), -1 on failure
//---------------------------------------------------------------------------
// *text should point to the first character after the ! and on return will
// be advanced to point to the first character following the macro's name.
// Valid macro names are !0 to !999, !A to !Z and !a to !z.
//***************************************************************************
int parse_macro_id(const char **text,
const char *filename,
unsigned line_num,
unsigned col_num)
{
int id;
const char *ptr = *text;
// No spaces allowed between ! and macro name
if (*ptr == ' ') {
fprintf(stderr, ERRORMSG_NOMACROID,
filename, line_num, col_num);
return -1;
}
// Macro named by letter?
if (*ptr >= 'A' && *ptr <= 'Z') {
id = *ptr - 'A' + MACRO_UPPER_START;
*text = ptr + 1;
return id;
}
if (*ptr >= 'a' && *ptr <= 'z') {
id = *ptr - 'a' + MACRO_LOWER_START;
*text = ptr + 1;
return id;
}
// Macro named by number?
int result = get_number(text, &id);
if (result) {
fprintf(stderr, ERRORMSG_NOMACROID,
filename, line_num, col_num);
return -1;
}
if (id < 0 || id > MACRO_MAX_NUMBER) {
fprintf(stderr, ERRORMSG_MACRORANGE,
filename, line_num, col_num, id);
return -1;
}
return id;
}
//***************************************************************************
// set_macro
// Changes the definition for a MML macro.
//---------------------------------------------------------------------------
// param id ..... macro ID (see parse_macro_id())
// param text ... pointer to new text
//***************************************************************************
void set_macro(int id, const char *text)
{
if (id < 0 || id >= NUM_MACROS) abort();
// Get rid of previous definition, if any
// (free() does nothing if pointer was NULL)
free(macros[id]);
// Allocate room to store a copy of the text to store
macros[id] = malloc(strlen(text)+1);
if (macros[id] == NULL) {
fprintf(stderr, ERRORMSG_NOMEMORY);
abort();
}
// Copy it over
strcpy(macros[id], text);
}
//***************************************************************************
// get_macro
// Retrieves the definition for a MML macro.
//---------------------------------------------------------------------------
// param id ... macro ID (see parse_macro_id())
//---------------------------------------------------------------------------
// return ..... pointer to macro's text (NULL if not defined)
//***************************************************************************
const char *get_macro(int id)
{
if (id < 0 || id >= NUM_MACROS) abort();
return macros[id];
}
//***************************************************************************
// expand_macros
// Parses a string and expands all the macros in it and returns a pointer to
// a new string with all the macros replaced. You should call free() when
// you're done with this string.
//---------------------------------------------------------------------------
// param text ....... pointer to string
// param filename ... file name (for error reporting)
// param line_num ... current line (for error reporting)
// param col_num .... current column (for error reporting)
//---------------------------------------------------------------------------
// return ........... pointer to new string
//***************************************************************************
char *expand_macros(const char *text,
const char *filename,
unsigned line_num,
unsigned col_num)
{
const char *text_start = text;
Blob *blob = create_blob();
while (*text != '\0') {
// Compute real column number at this point in the string
unsigned real_col_num = (text - text_start) + col_num;
// Text to copy as-is
if (*text != '!') {
const char *start = text;
while (*text != '\0' && *text != '!') text++;
size_t length = text - start;
append_to_blob(blob, (const uint8_t *)(start), length);
}
// Macro replacement
else {
text++;
int id = parse_macro_id(&text, filename, line_num, real_col_num);
if (id < 0) continue;
const char *data = macros[id];
if (data == NULL) {
fprintf(stderr, ERRORMSG_NOMACRODEF,
filename, line_num, real_col_num);
continue;
}
size_t length = strlen(data);
append_to_blob(blob, (const uint8_t *)(data), length);
}
}
char *result = malloc(blob->len + 1);
memcpy(result, blob->data, blob->len);
result[blob->len] = '\0';
delete_blob(blob);
//printf("\"%s\" -> \"%s\"\n", text_start, result);
return result;
}