/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2012.
This file is part of mxmpp.
mxmpp 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 3 of the License, or (at your option) any later
version.
mxmpp 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
mxmpp. If not, see .
mxmpp.cpp
A simple macro preprocessor.
*******************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#ifndef S_IRGRP
#define S_IRGRP (0)
#endif
#ifndef S_IWGRP
#define S_IWGRP (0)
#endif
#ifndef S_IROTH
#define S_IROTH (0)
#endif
#ifndef S_IWOTH
#define S_IWOTH (0)
#endif
#define writeall mxmpp_writeall
bool writeall(int fd, const void* p, size_t size)
{
const uint8_t* buffer = (const uint8_t*) p;
size_t bytesWritten = 0;
while ( bytesWritten < size )
{
ssize_t written = write(fd, buffer + bytesWritten, size - bytesWritten);
if ( written < 0 ) { perror("write"); return false; }
bytesWritten += written;
}
return true;
}
void usage(int /*argc*/, char* argv[])
{
printf("usage: %s [OPTIONS] [FILE]...\n", argv[0]);
printf("Preprocess FILE(s), or standard input.");
printf("\n");
printf("Options:\n");
printf(" -o Write output to this file\n");
printf(" Default = Standard Output\n");
printf(" -I Add this directory to the include search paths\n");
printf(" If no paths are set, include from working dir\n");
printf(" -q Surpress normal output\n");
printf(" -v Be verbose\n");
printf(" --usage Display this screen\n");
printf(" --help Display this screen\n");
printf(" --version Display version information\n");
}
void version()
{
printf("The Maxsi Macro PreProcessor 0.1\n");
printf("Copyright (C) 2011 Jonas 'Sortie' Termansen\n");
printf("This is free software; see the source for copying conditions. There is NO\n");
printf("warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n");
printf("website: http://www.maxsi.org/software/mxmpp/\n");
}
struct searchpath
{
const char* path;
searchpath* next;
};
struct inputfile
{
const char* path;
inputfile* next;
};
int outfd = -1;
searchpath* firstpath = NULL;
inputfile* firstfile = NULL;
const size_t CONSTRUCT_SIZE = 511;
const size_t BUFFER_SIZE = 4096;
bool verbose = false;
char* search(const char* filename);
bool include(const char* parameter);
bool expand(const char* command, const char* parameter);
bool process(int fd);
bool process(const char* path);
char* search(const char* filename)
{
size_t filenamelen = strlen(filename);
for ( searchpath* path = firstpath; path != NULL; path = path->next )
{
size_t searchpathlen = strlen(path->path);
size_t filepathlen = searchpathlen + 1 + filenamelen;
char* filepath = new char[filepathlen + 1];
strcpy(filepath, path->path);
strcpy(filepath + searchpathlen, "/");
strcpy(filepath + searchpathlen + 1, filename);
struct stat sts;
int statrs = stat(filepath, &sts);
if ( statrs < 0 && errno != ENOENT )
{
fprintf(stderr, "error: could not stat file '%s': %s\n", filepath, strerror(errno)); return NULL;
}
bool found = ( statrs != -1 );
if ( verbose )
{
fprintf(stderr, "info: searching for '%s': %s\n", filepath, ( found ) ? "found" : "not found");
}
if ( found )
{
return filepath;
}
delete[] filepath;
}
return NULL;
}
bool include(const char* parameter)
{
if ( parameter[0] == '\0' )
{
fprintf(stderr, "error: @include expects a non-empty parameter\n");
return false;
}
if ( parameter[0] == '/' )
{
return process(parameter);
}
char* included = search(parameter);
if ( included == NULL )
{
fprintf(stderr, "error: could not find included file '%s'\n", parameter);
return false;
}
bool result = process(included);
delete[] included;
return result;
}
bool expand(const char* command, const char* parameter)
{
if ( strcmp(command, "@include") == 0 )
{
return include(parameter);
}
else
{
fprintf(stderr, "error: unknown macro command %s\n", command);
return false;
}
}
bool process(int fd)
{
char construct[CONSTRUCT_SIZE+1];
char buffer[BUFFER_SIZE];
size_t constructed = 0;
int phase = 0;
const char* command = NULL;
const char* parameter = NULL;
bool quote = false;
bool backslash = false;
while ( true )
{
ssize_t numread = read(fd, buffer, BUFFER_SIZE);
if ( numread == 0 ) { break; }
if ( numread < 0 ) { perror("read"); return false; }
size_t writefrom = 0;
for ( ssize_t i = 0; i < numread; i++ )
{
if ( constructed == 0 )
{
if ( buffer[i] == '\\' ) { backslash = !backslash; continue; }
if ( buffer[i] == '"' && !backslash ) { quote = !quote; }
if ( buffer[i] == '@' && !quote )
{
if ( i - writefrom > 0 )
{
if ( !writeall(outfd, buffer + writefrom, i - writefrom) ) { return false; }
}
construct[0] = '@'; constructed++;
}
backslash = false;
}
else
{
if ( phase == 0 )
{
if ( ( ('a' <= buffer[i] && buffer[i] <= 'z' ) ||
('A' <= buffer[i] && buffer[i] <= 'Z' ) ) &&
( constructed < CONSTRUCT_SIZE) )
{
construct[constructed] = buffer[i]; constructed++;
}
else if ( buffer[i] == '(' )
{
construct[constructed] = '\0'; constructed++;
command = construct;
parameter = construct + constructed;
phase++;
continue;
}
else
{
construct[constructed] = '\0';
fprintf(stderr, "error: expected '(' after '%s'\n", construct);
return false;
}
}
if ( phase == 1 )
{
if ( buffer[i] == ')' )
{
construct[constructed] = '\0';
if ( !expand(command, parameter) ) { return false; }
phase = 0;
constructed = 0;
writefrom = i + 1;
}
else if ( buffer[i] != '\n' && buffer[i] != '\r' && constructed < CONSTRUCT_SIZE )
{
construct[constructed] = buffer[i]; constructed++;
}
else
{
construct[constructed] = '\0';
fprintf(stderr, "error: expected ')' after '%s'\n", parameter);
return false;
}
}
}
}
if ( constructed == 0 && numread - writefrom > 0 )
{
if ( !writeall(outfd, buffer + writefrom, numread - writefrom) ) { return false; }
}
}
return true;
}
bool process(const char* path)
{
int fd;
if ( strcmp(path, "-") == 0 ) { fd = 0; }
else if ( (fd = open(path, O_RDONLY) ) < 0 )
{
fprintf(stderr, "error: couldn't open file '%s'\n", path); return false;
}
if ( verbose )
{
fprintf(stderr, "info: including file '%s'\n", path);
}
bool result = process(fd);
if ( verbose )
{
fprintf(stderr, "info: end of file '%s'\n", path);
}
if ( close(fd) < 0 )
{
perror("close"); return false;
}
return result;
}
int main(int argc, char* argv[])
{
const char* dest = NULL;
searchpath* lastpath = NULL;
inputfile* lastfile = NULL;
for ( int i = 1; i < argc; i++ )
{
if ( strcmp(argv[i], "-I") == 0 )
{
if ( 2 <= argc - i )
{
searchpath* path = new searchpath();
path->path = argv[i+1];
path->next = NULL;
if ( firstpath == NULL ) { firstpath = path; } else { lastpath->next = path; }
lastpath = path;
i++;
}
}
else if ( strcmp(argv[i], "-o") == 0 )
{
if ( 2 <= argc - i )
{
dest = argv[i+1];
i++;
}
}
else if ( strcmp(argv[i], "-v") == 0 )
{
verbose = true;
}
else if ( strcmp(argv[i], "-q") == 0 )
{
verbose = false;
}
else if ( strcmp(argv[i], "--help") == 0 )
{
usage(argc, argv);
return 0;
}
else if ( strcmp(argv[i], "--usage") == 0 )
{
usage(argc, argv);
return 0;
}
else if ( strcmp(argv[i], "--version") == 0 )
{
version();
return 0;
}
else
{
inputfile* file = new inputfile();
file->path = argv[i];
file->next = NULL;
if ( firstfile == NULL ) { firstfile = file; } else { lastfile->next = file; }
lastfile = file;
}
}
if ( dest == NULL || strcmp(dest, "-") == 0 ) { outfd = 1; }
else if ( (outfd = open(dest, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) ) < 0 )
{
fprintf(stderr, "error: couldn't open file '%s'\n", dest); return 1;
}
if ( firstpath == NULL )
{
firstpath = new searchpath;
firstpath->path = ".";
firstpath->next = NULL;
lastpath = firstpath;
}
if ( firstfile == NULL )
{
firstfile = new inputfile;
firstfile->path = "-";
firstfile->next = NULL;
lastfile = firstfile;
}
inputfile* tmp = firstfile;
while ( tmp != NULL )
{
if ( !process(tmp->path) ) { return 1; }
tmp = tmp->next;
}
if ( close(outfd) < 0 )
{
perror("close"); return 1;
}
return 0;
}