Implemented #endif preprocessor directive.
/**
* MojoShader; generate shader programs from bytecode of compiled
* Direct3D shaders.
*
* Please see the file LICENSE.txt in the source's root directory.
*
* This file written by Ryan C. Gordon.
*/
#define __MOJOSHADER_INTERNAL__ 1
#include "mojoshader_internal.h"
#if DEBUG_PREPROCESSOR
#define print_debug_token(token, len, val) \
MOJOSHADER_print_debug_token("PREPROCESSOR", token, len, val)
#else
#define print_debug_token(token, len, val)
#endif
typedef struct DefineHash
{
MOJOSHADER_preprocessorDefine define;
struct DefineHash *next;
} DefineHash;
// Simple linked list to cache source filenames, so we don't have to copy
// the same string over and over for each opcode.
typedef struct FilenameCache
{
char *filename;
struct FilenameCache *next;
} FilenameCache;
typedef struct Context
{
int isfail;
int out_of_memory;
char failstr[256];
Conditional *conditional_pool;
IncludeState *include_stack;
DefineHash *define_hashtable[256];
FilenameCache *filename_cache;
MOJOSHADER_includeOpen open_callback;
MOJOSHADER_includeClose close_callback;
MOJOSHADER_malloc malloc;
MOJOSHADER_free free;
void *malloc_data;
} Context;
// Convenience functions for allocators...
static inline void out_of_memory(Context *ctx)
{
ctx->out_of_memory = 1;
} // out_of_memory
static inline void *Malloc(Context *ctx, const size_t len)
{
void *retval = ctx->malloc((int) len, ctx->malloc_data);
if (retval == NULL)
out_of_memory(ctx);
return retval;
} // Malloc
static inline void Free(Context *ctx, void *ptr)
{
if (ptr != NULL) // check for NULL in case of dumb free() impl.
ctx->free(ptr, ctx->malloc_data);
} // Free
static inline char *StrDup(Context *ctx, const char *str)
{
char *retval = (char *) Malloc(ctx, strlen(str) + 1);
if (retval != NULL)
strcpy(retval, str);
return retval;
} // StrDup
static void failf(Context *ctx, const char *fmt, ...) ISPRINTF(2,3);
static void failf(Context *ctx, const char *fmt, ...)
{
ctx->isfail = 1;
va_list ap;
va_start(ap, fmt);
vsnprintf(ctx->failstr, sizeof (ctx->failstr), fmt, ap);
va_end(ap);
} // failf
static inline void fail(Context *ctx, const char *reason)
{
failf(ctx, "%s", reason);
} // fail
#if DEBUG_TOKENIZER
void MOJOSHADER_print_debug_token(const char *subsystem, const char *token,
const unsigned int tokenlen,
const Token tokenval)
{
printf("%s TOKEN: \"", subsystem);
unsigned int i;
for (i = 0; i < tokenlen; i++)
{
if (token[i] == '\n')
printf("\\n");
else
printf("%c", token[i]);
} // for
printf("\" (");
switch (tokenval)
{
#define TOKENCASE(x) case x: printf("%s", #x); break
TOKENCASE(TOKEN_UNKNOWN);
TOKENCASE(TOKEN_IDENTIFIER);
TOKENCASE(TOKEN_INT_LITERAL);
TOKENCASE(TOKEN_FLOAT_LITERAL);
TOKENCASE(TOKEN_STRING_LITERAL);
TOKENCASE(TOKEN_ADDASSIGN);
TOKENCASE(TOKEN_SUBASSIGN);
TOKENCASE(TOKEN_MULTASSIGN);
TOKENCASE(TOKEN_DIVASSIGN);
TOKENCASE(TOKEN_MODASSIGN);
TOKENCASE(TOKEN_XORASSIGN);
TOKENCASE(TOKEN_ANDASSIGN);
TOKENCASE(TOKEN_ORASSIGN);
TOKENCASE(TOKEN_INCREMENT);
TOKENCASE(TOKEN_DECREMENT);
TOKENCASE(TOKEN_RSHIFT);
TOKENCASE(TOKEN_LSHIFT);
TOKENCASE(TOKEN_ANDAND);
TOKENCASE(TOKEN_OROR);
TOKENCASE(TOKEN_LEQ);
TOKENCASE(TOKEN_GEQ);
TOKENCASE(TOKEN_EQL);
TOKENCASE(TOKEN_NEQ);
TOKENCASE(TOKEN_HASHHASH);
TOKENCASE(TOKEN_PP_INCLUDE);
TOKENCASE(TOKEN_PP_LINE);
TOKENCASE(TOKEN_PP_DEFINE);
TOKENCASE(TOKEN_PP_UNDEF);
TOKENCASE(TOKEN_PP_IF);
TOKENCASE(TOKEN_PP_IFDEF);
TOKENCASE(TOKEN_PP_IFNDEF);
TOKENCASE(TOKEN_PP_ELSE);
TOKENCASE(TOKEN_PP_ELIF);
TOKENCASE(TOKEN_PP_ENDIF);
TOKENCASE(TOKEN_PP_ERROR);
TOKENCASE(TOKEN_INCOMPLETE_COMMENT);
TOKENCASE(TOKEN_BAD_CHARS);
TOKENCASE(TOKEN_EOI);
TOKENCASE(TOKEN_PREPROCESSING_ERROR);
#undef TOKENCASE
case ((Token) '\n'):
printf("'\\n'");
break;
default:
assert(((int)tokenval) < 256);
printf("'%c'", (char) tokenval);
break;
} // switch
printf(")\n");
} // MOJOSHADER_print_debug_token
#endif
#if !MOJOSHADER_FORCE_INCLUDE_CALLBACKS
// !!! FIXME: most of these _MSC_VER should probably be _WINDOWS?
#ifdef _MSC_VER
#define WIN32_LEAN_AND_MEAN 1
#include <windows.h> // GL headers need this for WINGDIAPI definition.
#else
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#endif
int MOJOSHADER_internal_include_open(MOJOSHADER_includeType inctype,
const char *fname, const char *parent,
const char **outdata,
unsigned int *outbytes,
MOJOSHADER_malloc m, MOJOSHADER_free f,
void *d)
{
#ifdef _MSC_VER
#error Write me.
#else
struct stat statbuf;
if (stat(fname, &statbuf) == -1)
return 0;
char *data = (char *) m(statbuf.st_size, d);
if (data == NULL)
return 0;
const int fd = open(fname, O_RDONLY);
if (fd == -1)
{
f(data, d);
return 0;
} // if
if (read(fd, data, statbuf.st_size) != statbuf.st_size)
{
f(data, d);
close(fd);
return 0;
} // if
close(fd);
*outdata = data;
*outbytes = (unsigned int) statbuf.st_size;
return 1;
#endif
} // MOJOSHADER_internal_include_open
void MOJOSHADER_internal_include_close(const char *data, MOJOSHADER_malloc m,
MOJOSHADER_free f, void *d)
{
f((void *) data, d);
} // MOJOSHADER_internal_include_close
#endif // !MOJOSHADER_FORCE_INCLUDE_CALLBACKS
// Conditional pool stuff...
static void free_conditional_pool(Context *ctx)
{
Conditional *item = ctx->conditional_pool;
while (item != NULL)
{
Conditional *next = item->next;
Free(ctx, item);
item = next;
} // while
} // free_conditional_pool
static Conditional *get_conditional(Context *ctx)
{
Conditional *retval = ctx->conditional_pool;
if (retval != NULL)
ctx->conditional_pool = retval->next;
else
retval = (Conditional *) Malloc(ctx, sizeof (Conditional));
if (retval != NULL)
memset(retval, '\0', sizeof (Conditional));
return retval;
} // get_conditional
static void put_conditionals(Context *ctx, Conditional *item)
{
while (item != NULL)
{
Conditional *next = item->next;
item->next = ctx->conditional_pool;
ctx->conditional_pool = item;
item = next;
} // while
} // put_conditionals
// Preprocessor define hashtable stuff...
static unsigned char hash_define(const char *sym)
{
unsigned char retval = 0;
while (sym)
retval += *(sym++);
return retval;
} // hash_define
static int add_define(Context *ctx, const char *sym, const char *val)
{
char *identifier = NULL;
char *definition = NULL;
const unsigned char hash = hash_define(sym);
DefineHash *bucket = ctx->define_hashtable[hash];
while (bucket)
{
if (strcmp(bucket->define.identifier, sym) == 0)
{
failf(ctx, "'%s' already defined", sym);
return 0;
} // if
bucket = bucket->next;
} // while
bucket = (DefineHash *) Malloc(ctx, sizeof (DefineHash));
if (bucket == NULL)
return 0;
identifier = (char *) Malloc(ctx, strlen(sym) + 1);
definition = (char *) Malloc(ctx, strlen(val) + 1);
if ((identifier == NULL) || (definition == NULL))
{
Free(ctx, identifier);
Free(ctx, definition);
Free(ctx, bucket);
return 0;
} // if
strcpy(identifier, sym);
strcpy(definition, val);
bucket->define.identifier = identifier;
bucket->define.definition = definition;
bucket->next = ctx->define_hashtable[hash];
ctx->define_hashtable[hash] = bucket;
return 1;
} // add_define
static int remove_define(Context *ctx, const char *sym)
{
const unsigned char hash = hash_define(sym);
DefineHash *bucket = ctx->define_hashtable[hash];
DefineHash *prev = NULL;
while (bucket)
{
if (strcmp(bucket->define.identifier, sym) == 0)
{
if (prev == NULL)
ctx->define_hashtable[hash] = bucket->next;
else
prev->next = bucket->next;
Free(ctx, (void *) bucket->define.identifier);
Free(ctx, (void *) bucket->define.definition);
Free(ctx, bucket);
return 1;
} // if
prev = bucket;
bucket = bucket->next;
} // while
return 0;
} // remove_define
static const char *find_define(Context *ctx, const char *sym)
{
const unsigned char hash = hash_define(sym);
DefineHash *bucket = ctx->define_hashtable[hash];
while (bucket)
{
if (strcmp(bucket->define.identifier, sym) == 0)
return bucket->define.definition;
bucket = bucket->next;
} // while
return NULL;
} // find_define
static void free_all_defines(Context *ctx)
{
int i;
for (i = 0; i < STATICARRAYLEN(ctx->define_hashtable); i++)
{
DefineHash *bucket = ctx->define_hashtable[i];
ctx->define_hashtable[i] = NULL;
while (bucket)
{
DefineHash *next = bucket->next;
Free(ctx, (void *) bucket->define.identifier);
Free(ctx, (void *) bucket->define.definition);
Free(ctx, bucket);
bucket = next;
} // while
} // for
} // find_define
// filename cache stuff...
static const char *cache_filename(Context *ctx, const char *fname)
{
if (fname == NULL)
return NULL;
// !!! FIXME: this could be optimized into a hash table, but oh well.
FilenameCache *item = ctx->filename_cache;
while (item != NULL)
{
if (strcmp(item->filename, fname) == 0)
return item->filename;
item = item->next;
} // while
// new cache item.
item = (FilenameCache *) Malloc(ctx, sizeof (FilenameCache));
if (item == NULL)
return NULL;
item->filename = StrDup(ctx, fname);
if (item->filename == NULL)
{
Free(ctx, item);
return NULL;
} // if
item->next = ctx->filename_cache;
ctx->filename_cache = item;
return item->filename;
} // cache_filename
static void free_filename_cache(Context *ctx)
{
FilenameCache *item = ctx->filename_cache;
while (item != NULL)
{
FilenameCache *next = item->next;
Free(ctx, item->filename);
Free(ctx, item);
item = next;
} // while
} // free_filename_cache
static int push_source(Context *ctx, const char *fname, const char *source,
unsigned int srclen, int included)
{
IncludeState *state = (IncludeState *) Malloc(ctx, sizeof (IncludeState));
if (state == NULL)
return 0;
memset(state, '\0', sizeof (IncludeState));
if (fname != NULL)
{
state->filename = cache_filename(ctx, fname);
if (state->filename == NULL)
{
Free(ctx, state);
return 0;
} // if
} // if
state->included = included;
state->source_base = source;
state->source = source;
state->token = source;
state->bytes_left = srclen;
state->line = 1;
state->next = ctx->include_stack;
ctx->include_stack = state;
return 1;
} // push_source
static void pop_source(Context *ctx)
{
IncludeState *state = ctx->include_stack;
if (state == NULL)
return;
if (state->included)
{
ctx->close_callback(state->source_base, ctx->malloc,
ctx->free, ctx->malloc_data);
} // if
// state->filename is a pointer to the filename cache; don't free it here!
put_conditionals(ctx, state->conditional_stack);
ctx->include_stack = state->next;
Free(ctx, state);
} // pop_source
Preprocessor *preprocessor_start(const char *fname, const char *source,
unsigned int sourcelen,
MOJOSHADER_includeOpen open_callback,
MOJOSHADER_includeClose close_callback,
const MOJOSHADER_preprocessorDefine **defines,
unsigned int define_count,
MOJOSHADER_malloc m, MOJOSHADER_free f, void *d)
{
int okay = 1;
int i = 0;
// the preprocessor is internal-only, so we verify all these are != NULL.
assert(m != NULL);
assert(f != NULL);
assert(open_callback != NULL);
assert(close_callback != NULL);
Context *ctx = (Context *) m(sizeof (Context), d);
if (ctx == NULL)
return 0;
memset(ctx, '\0', sizeof (Context));
ctx->malloc = m;
ctx->free = f;
ctx->malloc_data = d;
ctx->open_callback = open_callback;
ctx->close_callback = close_callback;
for (i = 0; i < define_count; i++)
{
if (!add_define(ctx, defines[i]->identifier, defines[i]->definition))
{
okay = 0;
break;
} // if
} // for
if ((okay) && (!push_source(ctx, fname, source, sourcelen, 0)))
okay = 0;
if (!okay)
{
preprocessor_end((Preprocessor *) ctx);
return NULL;
} // if
return (Preprocessor *) ctx;
} // preprocessor_start
void preprocessor_end(Preprocessor *_ctx)
{
Context *ctx = (Context *) _ctx;
if (ctx == NULL)
return;
while (ctx->include_stack != NULL)
pop_source(ctx);
free_all_defines(ctx);
free_filename_cache(ctx);
free_conditional_pool(ctx);
Free(ctx, ctx);
} // preprocessor_end
int preprocessor_outofmemory(Preprocessor *_ctx)
{
Context *ctx = (Context *) _ctx;
return ctx->out_of_memory;
} // preprocessor_outofmemory
static int require_newline(IncludeState *state)
{
const char *source = state->source;
const Token token = preprocessor_internal_lexer(state);
if (token == TOKEN_INCOMPLETE_COMMENT)
{
state->source = source; // pick this up later.
return 1; // call it an eol.
} // if
return ( (token == ((Token) '\n')) || (token == TOKEN_EOI) );
} // require_newline
static void handle_pp_include(Context *ctx)
{
IncludeState *state = ctx->include_stack;
Token token = preprocessor_internal_lexer(state);
MOJOSHADER_includeType incltype;
char *filename = NULL;
int bogus = 0;
if (token == TOKEN_STRING_LITERAL)
incltype = MOJOSHADER_INCLUDETYPE_LOCAL;
else if (token == ((Token) '<'))
{
incltype = MOJOSHADER_INCLUDETYPE_SYSTEM;
// can't use lexer, since every byte between the < > pair is
// considered part of the filename. :/
while (!bogus)
{
if ( !(bogus = (state->bytes_left == 0)) )
{
const char ch = *state->source;
if ( !(bogus = ((ch == '\r') || (ch == '\n'))) )
{
state->source++;
state->bytes_left--;
if (ch == '>')
break;
} // if
} // if
} // while
} // else if
else
{
bogus = 1;
} // else
if (!bogus)
{
state->token++; // skip '<' or '\"'...
const unsigned int len = ((unsigned int) (state->source-state->token));
filename = (char *) alloca(len);
memcpy(filename, state->token, len-1);
filename[len-1] = '\0';
bogus = !require_newline(state);
} // if
if (bogus)
{
fail(ctx, "Invalid #include directive");
return;
} // else
const char *newdata = NULL;
unsigned int newbytes = 0;
if (!ctx->open_callback(incltype, filename, state->source_base,
&newdata, &newbytes, ctx->malloc,
ctx->free, ctx->malloc_data))
{
fail(ctx, "Include callback failed"); // !!! FIXME: better error
return;
} // if
if (!push_source(ctx, filename, newdata, newbytes, 1))
{
assert(ctx->out_of_memory);
ctx->close_callback(newdata, ctx->malloc, ctx->free, ctx->malloc_data);
} // if
} // handle_pp_include
static void handle_pp_line(Context *ctx)
{
IncludeState *state = ctx->include_stack;
char *filename = NULL;
int linenum = 0;
int bogus = 0;
if (preprocessor_internal_lexer(state) != TOKEN_INT_LITERAL)
bogus = 1;
else
{
const unsigned int len = ((unsigned int) (state->source-state->token));
char *buf = (char *) alloca(len+1);
memcpy(buf, state->token, len);
buf[len] = '\0';
linenum = atoi(buf);
} // else
if (!bogus)
bogus = (preprocessor_internal_lexer(state) != TOKEN_STRING_LITERAL);
if (!bogus)
{
state->token++; // skip '\"'...
const unsigned int len = ((unsigned int) (state->source-state->token));
filename = (char *) alloca(len);
memcpy(filename, state->token, len-1);
filename[len-1] = '\0';
bogus = !require_newline(state);
} // if
if (bogus)
{
fail(ctx, "Invalid #line directive");
return;
} // if
const char *cached = cache_filename(ctx, filename);
assert((cached != NULL) || (ctx->out_of_memory));
state->filename = cached;
state->line = linenum;
} // handle_pp_line
static void handle_pp_error(Context *ctx)
{
IncludeState *state = ctx->include_stack;
const char *data = NULL;
int done = 0;
const char *source = NULL;
while (!done)
{
source = state->source;
const Token token = preprocessor_internal_lexer(state);
switch (token)
{
case ((Token) '\n'):
state->line--; // make sure error is on the right line.
// fall through!
case TOKEN_INCOMPLETE_COMMENT:
case TOKEN_EOI:
done = 1;
break;
default:
if (data == NULL)
data = state->token; // skip #error token.
break;
} // switch
} // while
state->source = source; // move back so we catch this later.
const char *prefix = "#error ";
const size_t prefixlen = strlen(prefix);
const int len = (int) (state->source - data);
const int cpy = Min(len, sizeof (ctx->failstr) - prefixlen);
strcpy(ctx->failstr, prefix);
if (cpy > 0)
memcpy(ctx->failstr + prefixlen, data, cpy);
ctx->failstr[cpy + prefixlen] = '\0';
ctx->isfail = 1;
} // handle_pp_error
static void handle_pp_undef(Context *ctx)
{
IncludeState *state = ctx->include_stack;
if (preprocessor_internal_lexer(state) != TOKEN_IDENTIFIER)
{
fail(ctx, "Macro names must be indentifiers");
return;
} // if
const unsigned int len = ((unsigned int) (state->source-state->token));
char *sym = (char *) alloca(len);
memcpy(sym, state->token, len-1);
sym[len-1] = '\0';
if (!require_newline(state))
{
fail(ctx, "Invalid #undef directive");
return;
} // if
remove_define(ctx, sym);
} // handle_pp_undef
static void handle_pp_endif(Context *ctx)
{
IncludeState *state = ctx->include_stack;
Conditional *cond = state->conditional_stack;
if (!require_newline(state))
fail(ctx, "Invalid #endif directive");
else if (cond == NULL)
fail(ctx, "Unmatched #endif");
else
{
state->conditional_stack = cond->next; // pop it.
cond->next = NULL;
put_conditionals(ctx, cond);
} // else
} // handle_pp_endif
static void unterminated_pp_condition(Context *ctx)
{
IncludeState *state = ctx->include_stack;
Conditional *cond = state->conditional_stack;
// !!! FIXME: report the line number where the #if is, not the EOI.
switch (cond->type)
{
case TOKEN_PP_IF: fail(ctx, "Unterminated #if"); break;
case TOKEN_PP_IFDEF: fail(ctx, "Unterminated #ifdef"); break;
case TOKEN_PP_IFNDEF: fail(ctx, "Unterminated #ifndef"); break;
case TOKEN_PP_ELSE: fail(ctx, "Unterminated #else"); break;
case TOKEN_PP_ELIF: fail(ctx, "Unterminated #elif"); break;
default: assert(0 && "Shouldn't hit this case"); break;
} // switch
// pop this conditional, we'll report the next error next time...
state->conditional_stack = cond->next; // pop it.
cond->next = NULL;
put_conditionals(ctx, cond);
} // unterminated_pp_condition
static inline const char *_preprocessor_nexttoken(Preprocessor *_ctx,
unsigned int *_len, Token *_token)
{
Context *ctx = (Context *) _ctx;
while (1)
{
if (ctx->isfail)
{
ctx->isfail = 0;
*_token = TOKEN_PREPROCESSING_ERROR;
*_len = strlen(ctx->failstr);
return ctx->failstr;
} // if
IncludeState *state = ctx->include_stack;
if (state == NULL)
{
*_token = TOKEN_EOI;
*_len = 0;
return NULL; // we're done!
} // if
// !!! FIXME: todo.
// TOKEN_PP_DEFINE,
// TOKEN_PP_IF,
// TOKEN_PP_IFDEF,
// TOKEN_PP_IFNDEF,
// TOKEN_PP_ELSE,
// TOKEN_PP_ELIF,
const Conditional *cond = state->conditional_stack;
const int skipping = ((cond != NULL) && (cond->skipping));
Token token = preprocessor_internal_lexer(state);
if (token == TOKEN_EOI)
{
assert(state->bytes_left == 0);
if (state->conditional_stack != NULL)
{
unterminated_pp_condition(ctx);
continue; // returns an error.
} // if
pop_source(ctx);
continue; // pick up again after parent's #include line.
} // if
else if (token == TOKEN_INCOMPLETE_COMMENT)
{
fail(ctx, "Incomplete multiline comment");
continue; // will return at top of loop.
} // else if
else if (token == TOKEN_PP_ENDIF)
{
handle_pp_endif(ctx);
continue; // get the next thing.
} // else if
// !!! FIXME: #else, #elif and #endif must be above (skipping) test.
else if (skipping)
{
continue; // just keep dumping tokens until we get end of block.
} // else if
else if (token == TOKEN_PP_INCLUDE)
{
handle_pp_include(ctx);
continue; // will return error or use new top of include_stack.
} // else if
else if (token == TOKEN_PP_LINE)
{
handle_pp_line(ctx);
continue; // get the next thing.
} // else if
else if (token == TOKEN_PP_ERROR)
{
handle_pp_error(ctx);
continue; // will return at top of loop.
} // else if
else if (token == TOKEN_PP_UNDEF)
{
handle_pp_undef(ctx);
continue; // will return at top of loop.
} // else if
else if (!skipping)
{
*_token = token;
*_len = (unsigned int) (state->source - state->token);
return state->token;
} // else if
// !!! FIXME: check for ((Token) '\n'), so we know if a preprocessor
// !!! FIXME: directive started a line.
} // while
assert(0 && "shouldn't hit this code");
*_token = TOKEN_UNKNOWN;
*_len = 0;
return NULL;
} // _preprocessor_nexttoken
const char *preprocessor_nexttoken(Preprocessor *ctx, unsigned int *len,
Token *token)
{
const char *retval = _preprocessor_nexttoken(ctx, len, token);
print_debug_token(retval, *len, *token);
return retval;
} // preprocessor_nexttoken
const char *preprocessor_sourcepos(Preprocessor *_ctx, unsigned int *pos)
{
Context *ctx = (Context *) _ctx;
if (ctx->include_stack == NULL)
{
*pos = 0;
return NULL;
} // if
*pos = ctx->include_stack->line;
return ctx->include_stack->filename;
} // preprocessor_sourcepos
// public API...
static const MOJOSHADER_preprocessData out_of_mem_data_preprocessor = {
1, &MOJOSHADER_out_of_mem_error, 0, 0, 0, 0, 0
};
#define BUFFER_LEN (64 * 1024)
typedef struct BufferList
{
char buffer[BUFFER_LEN];
size_t bytes;
struct BufferList *next;
} BufferList;
typedef struct Buffer
{
size_t total_bytes;
BufferList head;
BufferList *tail;
} Buffer;
static void buffer_init(Buffer *buffer)
{
buffer->total_bytes = 0;
buffer->head.bytes = 0;
buffer->head.next = NULL;
buffer->tail = &buffer->head;
} // buffer_init
static int add_to_buffer(Buffer *buffer, const char *data,
size_t len, MOJOSHADER_malloc m, void *d)
{
buffer->total_bytes += len;
while (len > 0)
{
const size_t avail = BUFFER_LEN - buffer->tail->bytes;
const size_t cpy = (avail > len) ? len : avail;
memcpy(buffer->tail->buffer + buffer->tail->bytes, data, cpy);
len -= cpy;
data += cpy;
buffer->tail->bytes += cpy;
assert(buffer->tail->bytes <= BUFFER_LEN);
if (buffer->tail->bytes == BUFFER_LEN)
{
BufferList *item = (BufferList *) m(sizeof (BufferList), d);
if (item == NULL)
return 0;
item->bytes = 0;
item->next = NULL;
buffer->tail->next = item;
buffer->tail = item;
} // if
} // while
return 1;
} // add_to_buffer
static int indent_buffer(Buffer *buffer, int n, int newline,
MOJOSHADER_malloc m, void *d)
{
static char spaces[4] = { ' ', ' ', ' ', ' ' };
if (newline)
{
while (n--)
{
if (!add_to_buffer(buffer, spaces, sizeof (spaces), m, d))
return 0;
} // while
} // if
else
{
if (!add_to_buffer(buffer, spaces, 1, m, d))
return 0;
} // else
return 1;
} // indent_buffer
static char *flatten_buffer(Buffer *buffer, MOJOSHADER_malloc m, void *d)
{
char *retval = m(buffer->total_bytes + 1, d);
if (retval == NULL)
return NULL;
BufferList *item = &buffer->head;
char *ptr = retval;
while (item != NULL)
{
BufferList *next = item->next;
memcpy(ptr, item->buffer, item->bytes);
ptr += item->bytes;
item = next;
} // while
*ptr = '\0';
assert(ptr == (retval + buffer->total_bytes));
return retval;
} // flatten_buffer
static void free_buffer(Buffer *buffer, MOJOSHADER_free f, void *d)
{
// head is statically allocated, so start with head.next...
BufferList *item = buffer->head.next;
while (item != NULL)
{
BufferList *next = item->next;
f(item, d);
item = next;
} // while
buffer_init(buffer);
} // free_buffer
// !!! FIXME: cut and paste.
static void free_error_list(ErrorList *item, MOJOSHADER_free f, void *d)
{
while (item != NULL)
{
ErrorList *next = item->next;
f((void *) item->error.error, d);
f((void *) item->error.filename, d);
f(item, d);
item = next;
} // while
} // free_error_list
// !!! FIXME: cut and paste.
static MOJOSHADER_error *build_errors(ErrorList **errors, const int count,
MOJOSHADER_malloc m, MOJOSHADER_free f, void *d)
{
int total = 0;
MOJOSHADER_error *retval = (MOJOSHADER_error *)
m(sizeof (MOJOSHADER_error) * count, d);
if (retval == NULL)
return NULL;
ErrorList *item = *errors;
while (item != NULL)
{
ErrorList *next = item->next;
// reuse the string allocations
memcpy(&retval[total], &item->error, sizeof (MOJOSHADER_error));
f(item, d);
item = next;
total++;
} // while
*errors = NULL;
assert(total == count);
return retval;
} // build_errors
const MOJOSHADER_preprocessData *MOJOSHADER_preprocess(const char *filename,
const char *source, unsigned int sourcelen,
const MOJOSHADER_preprocessorDefine **defines,
unsigned int define_count,
MOJOSHADER_includeOpen include_open,
MOJOSHADER_includeClose include_close,
MOJOSHADER_malloc m, MOJOSHADER_free f, void *d)
{
#ifdef _WINDOWS
static const char endline[] = { '\r', '\n' };
#else
static const char endline[] = { '\n' };
#endif
ErrorList *errors = NULL;
int error_count = 0;
if (!m) m = MOJOSHADER_internal_malloc;
if (!f) f = MOJOSHADER_internal_free;
if (!include_open) include_open = MOJOSHADER_internal_include_open;
if (!include_close) include_close = MOJOSHADER_internal_include_close;
Preprocessor *pp = preprocessor_start(filename, source, sourcelen,
include_open, include_close,
defines, define_count, m, f, d);
if (pp == NULL)
return &out_of_mem_data_preprocessor;
Token token = TOKEN_UNKNOWN;
const char *tokstr = NULL;
Buffer buffer;
buffer_init(&buffer);
int nl = 1;
int indent = 0;
unsigned int len = 0;
int out_of_memory = 0;
while ((tokstr = preprocessor_nexttoken(pp, &len, &token)) != NULL)
{
int isnewline = 0;
assert(token != TOKEN_EOI);
if (!out_of_memory)
out_of_memory = preprocessor_outofmemory(pp);
// Microsoft's preprocessor is weird.
// It ignores newlines, and then inserts its own around certain
// tokens. For example, after a semicolon. This allows HLSL code to
// be mostly readable, instead of a stream of tokens.
if (token == ((Token) '\n'))
isnewline = nl; // this doesn't actually care about '\n' ...
else if ( (token == ((Token) '}')) || (token == ((Token) ';')) )
{
if (!out_of_memory)
{
if ( (token == ((Token) '}')) && (indent > 0) )
indent--;
out_of_memory =
(!indent_buffer(&buffer, indent, nl, m, d)) ||
(!add_to_buffer(&buffer, tokstr, len, m, d)) ||
(!add_to_buffer(&buffer, endline, sizeof (endline), m, d));
isnewline = 1;
} // if
} // if
else if (token == ((Token) '{'))
{
if (!out_of_memory)
{
out_of_memory =
(!add_to_buffer(&buffer,endline,sizeof (endline),m,d)) ||
(!indent_buffer(&buffer, indent, 1, m, d)) ||
(!add_to_buffer(&buffer, "{", 1, m, d)) ||
(!add_to_buffer(&buffer,endline,sizeof (endline),m,d));
indent++;
isnewline = 1;
} // if
} // else if
else if (token == TOKEN_PREPROCESSING_ERROR)
{
if (!out_of_memory)
{
ErrorList *error = (ErrorList *) m(sizeof (ErrorList), d);
unsigned int pos = 0;
char *fname = NULL;
const char *str = preprocessor_sourcepos(pp, &pos);
if (str != NULL)
{
fname = (char *) m(strlen(str) + 1, d);
if (fname != NULL)
strcpy(fname, str);
} // if
// !!! FIXME: cut and paste with other error handlers.
char *errstr = (char *) m(len + 1, d);
if (errstr != NULL)
strcpy(errstr, tokstr);
out_of_memory = ((!error) || ((!fname) && (str)) || (!errstr));
if (out_of_memory)
{
if (errstr) f(errstr, d);
if (fname) f(fname, d);
if (error) f(error, d);
} // if
else
{
error->error.error = errstr;
error->error.filename = fname;
error->error.error_position = pos;
error->next = NULL;
ErrorList *prev = NULL;
ErrorList *item = errors;
while (item != NULL)
{
prev = item;
item = item->next;
} // while
if (prev == NULL)
errors = error;
else
prev->next = error;
error_count++;
} // else
} // if
} // else if
else
{
if (!out_of_memory)
{
out_of_memory = (!indent_buffer(&buffer, indent, nl, m, d)) ||
(!add_to_buffer(&buffer, tokstr, len, m, d));
} // if
} // else
nl = isnewline;
if (out_of_memory)
{
preprocessor_end(pp);
free_buffer(&buffer, f, d);
free_error_list(errors, f, d);
return &out_of_mem_data_preprocessor;
} // if
} // while
assert((token == TOKEN_EOI) || (out_of_memory));
preprocessor_end(pp);
const size_t total_bytes = buffer.total_bytes;
char *output = flatten_buffer(&buffer, m, d);
free_buffer(&buffer, f, d);
if (output == NULL)
{
free_error_list(errors, f, d);
return &out_of_mem_data_preprocessor;
} // if
MOJOSHADER_preprocessData *retval = (MOJOSHADER_preprocessData *)
m(sizeof (MOJOSHADER_preprocessData), d);
if (retval == NULL)
{
free_error_list(errors, f, d);
f(output, d);
return &out_of_mem_data_preprocessor;
} // if
retval->errors = build_errors(&errors, error_count, m, f, d);
if (retval->errors == NULL)
{
free_error_list(errors, f, d);
f(retval, d);
f(output, d);
return &out_of_mem_data_preprocessor;
} // if
retval->error_count = error_count;
retval->output = output;
retval->output_len = total_bytes;
retval->malloc = m;
retval->free = f;
retval->malloc_data = d;
return retval;
} // MOJOSHADER_preprocess
void MOJOSHADER_freePreprocessData(const MOJOSHADER_preprocessData *_data)
{
MOJOSHADER_preprocessData *data = (MOJOSHADER_preprocessData *) _data;
if ((data == NULL) || (data == &out_of_mem_data_preprocessor))
return;
MOJOSHADER_free f = (data->free == NULL) ? MOJOSHADER_internal_free : data->free;
void *d = data->malloc_data;
int i;
if (data->output != NULL)
f((void *) data->output, d);
if (data->errors != NULL)
{
for (i = 0; i < data->error_count; i++)
{
if (data->errors[i].error != NULL)
f((void *) data->errors[i].error, d);
if (data->errors[i].filename != NULL)
f((void *) data->errors[i].filename, d);
} // for
f(data->errors, d);
} // if
f(data, d);
} // MOJOSHADER_freePreprocessData
// end of mojoshader_preprocessor.c ...