// stb_include.h - v0.02 - parse and process #include directives - public domain // // To build this, in one source file that includes this file do // #define STB_INCLUDE_IMPLEMENTATION // // This program parses a string and replaces lines of the form // #include "foo" // with the contents of a file named "foo". It also embeds the // appropriate #line directives. Note that all include files must // reside in the location specified in the path passed to the API; // it does not check multiple directories. // // If the string contains a line of the form // #inject // then it will be replaced with the contents of the string 'inject' passed to the API. // // Options: // // Define STB_INCLUDE_LINE_GLSL to get GLSL-style #line directives // which use numbers instead of filenames. // // Define STB_INCLUDE_LINE_NONE to disable output of #line directives. // // Standard libraries: // // stdio.h FILE, fopen, fclose, fseek, ftell // stdlib.h malloc, realloc, free // string.h strcpy, strncmp, memcpy // // Credits: // // Written by Sean Barrett. // // Fixes: // Michal Klos #ifndef STB_INCLUDE_STB_INCLUDE_H #define STB_INCLUDE_STB_INCLUDE_H // Do include-processing on the string 'str'. To free the return value, pass it to free() char *stb_include_string(char *str, char *inject, char *path_to_includes, char *filename_for_line_directive, char error[256]); // Concatenate the strings 'strs' and do include-processing on the result. To free the return value, pass it to free() char *stb_include_strings(char **strs, int count, char *inject, char *path_to_includes, char *filename_for_line_directive, char error[256]); // Load the file 'filename' and do include-processing on the string therein. note that // 'filename' is opened directly; 'path_to_includes' is not used. To free the return value, pass it to free() char *stb_include_file(char *filename, char *inject, char *path_to_includes, char error[256]); #endif #ifdef STB_INCLUDE_IMPLEMENTATION #include #include #include static char *stb_include_load_file(char *filename, size_t *plen) { char *text; size_t len; FILE *f = fopen(filename, "rb"); if (f == 0) return 0; fseek(f, 0, SEEK_END); len = (size_t) ftell(f); if (plen) *plen = len; text = (char *) malloc(len+1); if (text == 0) return 0; fseek(f, 0, SEEK_SET); fread(text, 1, len, f); fclose(f); text[len] = 0; return text; } typedef struct { int offset; int end; char *filename; int next_line_after; } include_info; static include_info *stb_include_append_include(include_info *array, int len, int offset, int end, char *filename, int next_line) { include_info *z = (include_info *) realloc(array, sizeof(*z) * (len+1)); z[len].offset = offset; z[len].end = end; z[len].filename = filename; z[len].next_line_after = next_line; return z; } static void stb_include_free_includes(include_info *array, int len) { int i; for (i=0; i < len; ++i) free(array[i].filename); free(array); } static int stb_include_isspace(int ch) { return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'); } // find location of all #include and #inject static int stb_include_find_includes(char *text, include_info **plist) { int line_count = 1; int inc_count = 0; char *s = text, *start; include_info *list = NULL; while (*s) { // parse is always at start of line when we reach here start = s; while (*s == ' ' || *s == '\t') ++s; if (*s == '#') { ++s; while (*s == ' ' || *s == '\t') ++s; if (0==strncmp(s, "include", 7) && stb_include_isspace(s[7])) { s += 7; while (*s == ' ' || *s == '\t') ++s; if (*s == '"') { char *t = ++s; while (*t != '"' && *t != '\n' && *t != '\r' && *t != 0) ++t; if (*t == '"') { char *filename = (char *) malloc(t-s+1); memcpy(filename, s, t-s); filename[t-s] = 0; s=t; while (*s != '\r' && *s != '\n' && *s != 0) ++s; // s points to the newline, so s-start is everything except the newline list = stb_include_append_include(list, inc_count++, start-text, s-text, filename, line_count+1); } } } else if (0==strncmp(s, "inject", 6) && (stb_include_isspace(s[6]) || s[6]==0)) { while (*s != '\r' && *s != '\n' && *s != 0) ++s; list = stb_include_append_include(list, inc_count++, start-text, s-text, NULL, line_count+1); } } while (*s != '\r' && *s != '\n' && *s != 0) ++s; if (*s == '\r' || *s == '\n') { s = s + (s[0] + s[1] == '\r' + '\n' ? 2 : 1); } ++line_count; } *plist = list; return inc_count; } // avoid dependency on sprintf() static void stb_include_itoa(char str[9], int n) { int i; for (i=0; i < 8; ++i) str[i] = ' '; str[i] = 0; for (i=1; i < 8; ++i) { str[7-i] = '0' + (n % 10); n /= 10; if (n == 0) break; } } static char *stb_include_append(char *str, size_t *curlen, char *addstr, size_t addlen) { str = (char *) realloc(str, *curlen + addlen); memcpy(str + *curlen, addstr, addlen); *curlen += addlen; return str; } char *stb_include_string(char *str, char *inject, char *path_to_includes, char *filename, char error[256]) { char temp[4096]; include_info *inc_list; int i, num = stb_include_find_includes(str, &inc_list); size_t source_len = strlen(str); char *text=0; size_t textlen=0, last=0; for (i=0; i < num; ++i) { text = stb_include_append(text, &textlen, str+last, inc_list[i].offset - last); // write out line directive for the include #ifndef STB_INCLUDE_LINE_NONE #ifdef STB_INCLUDE_LINE_GLSL if (textlen != 0) // GLSL #version must appear first, so don't put a #line at the top #endif { strcpy(temp, "#line "); stb_include_itoa(temp+6, 1); strcat(temp, " "); #ifdef STB_INCLUDE_LINE_GLSL stb_include_itoa(temp+15, i+1); #else strcat(temp, "\""); if (inc_list[i].filename == 0) strcmp(temp, "INJECT"); else strcat(temp, inc_list[i].filename); strcat(temp, "\""); #endif strcat(temp, "\n"); text = stb_include_append(text, &textlen, temp, strlen(temp)); } #endif if (inc_list[i].filename == 0) { if (inject != 0) text = stb_include_append(text, &textlen, inject, strlen(inject)); } else { char *inc; strcpy(temp, path_to_includes); strcat(temp, "/"); strcat(temp, inc_list[i].filename); inc = stb_include_file(temp, inject, path_to_includes, error); if (inc == NULL) { stb_include_free_includes(inc_list, num); return NULL; } text = stb_include_append(text, &textlen, inc, strlen(inc)); free(inc); } // write out line directive #ifndef STB_INCLUDE_LINE_NONE strcpy(temp, "\n#line "); stb_include_itoa(temp+6, inc_list[i].next_line_after); strcat(temp, " "); #ifdef STB_INCLUDE_LINE_GLSL stb_include_itoa(temp+15, 0); #else strcat(temp, filename != 0 ? filename : "source-file"); #endif text = stb_include_append(text, &textlen, temp, strlen(temp)); // no newlines, because we kept the #include newlines, which will get appended next #endif last = inc_list[i].end; } text = stb_include_append(text, &textlen, str+last, source_len - last + 1); // append '\0' stb_include_free_includes(inc_list, num); return text; } char *stb_include_strings(char **strs, int count, char *inject, char *path_to_includes, char *filename, char error[256]) { char *text; char *result; int i; size_t length=0; for (i=0; i < count; ++i) length += strlen(strs[i]); text = (char *) malloc(length+1); length = 0; for (i=0; i < count; ++i) { strcpy(text + length, strs[i]); length += strlen(strs[i]); } result = stb_include_string(text, inject, path_to_includes, filename, error); free(text); return result; } char *stb_include_file(char *filename, char *inject, char *path_to_includes, char error[256]) { size_t len; char *result; char *text = stb_include_load_file(filename, &len); if (text == NULL) { strcpy(error, "Error: couldn't load '"); strcat(error, filename); strcat(error, "'"); return 0; } result = stb_include_string(text, inject, path_to_includes, filename, error); free(text); return result; } #if 0 // @TODO, GL_ARB_shader_language_include-style system that doesn't touch filesystem char *stb_include_preloaded(char *str, char *inject, char *includes[][2], char error[256]) { } #endif #endif // STB_INCLUDE_IMPLEMENTATION