1 // stb_include.h - v0.01 - parse and process #include directives - public domain
2 //
3 // To build this, in one source file that includes this file do
4 //      #define STB_INCLUDE_IMPLEMENTATION
5 //
6 // This program parses a string and replaces lines of the form
7 //         #include "foo"
8 // with the contents of a file named "foo". It also embeds the
9 // appropriate #line directives. Note that all include files must
10 // reside in the location specified in the path passed to the API;
11 // it does not check multiple directories.
12 //
13 // If the string contains a line of the form
14 //         #inject
15 // then it will be replaced with the contents of the string 'inject' passed to the API.
16 //
17 // Options:
18 //
19 //      Define STB_INCLUDE_LINE_GLSL to get GLSL-style #line directives
20 //      which use numbers instead of filenames.
21 //
22 //      Define STB_INCLUDE_LINE_NONE to disable output of #line directives.
23 //
24 // Standard libraries:
25 //
26 //      stdio.h     FILE, fopen, fclose, fseek, ftell
27 //      stdlib.h    malloc, realloc, free
28 //      string.h    strcpy, strncmp, memcpy
29 
30 #ifndef STB_INCLUDE_STB_INCLUDE_H
31 #define STB_INCLUDE_STB_INCLUDE_H
32 
33 // Do include-processing on the string 'str'. To free the return value, pass it to free()
34 char *stb_include_string(char *str, char *inject, char *path_to_includes, char *filename_for_line_directive, char error[256]);
35 
36 // Concatenate the strings 'strs' and do include-processing on the result. To free the return value, pass it to free()
37 char *stb_include_strings(char **strs, int count, char *inject, char *path_to_includes, char *filename_for_line_directive, char error[256]);
38 
39 // Load the file 'filename' and do include-processing on the string therein. note that
40 // 'filename' is opened directly; 'path_to_includes' is not used. To free the return value, pass it to free()
41 char *stb_include_file(char *filename, char *inject, char *path_to_includes, char error[256]);
42 
43 #endif
44 
45 
46 #ifdef STB_INCLUDE_IMPLEMENTATION
47 
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 
stb_include_load_file(char * filename,size_t * plen)52 static char *stb_include_load_file(char *filename, size_t *plen)
53 {
54    char *text;
55    size_t len;
56    FILE *f = fopen(filename, "rb");
57    if (f == 0) return 0;
58    fseek(f, 0, SEEK_END);
59    len = (size_t) ftell(f);
60    if (plen) *plen = len;
61    text = (char *) malloc(len+1);
62    if (text == 0) return 0;
63    fseek(f, 0, SEEK_SET);
64    fread(text, 1, len, f);
65    fclose(f);
66    text[len] = 0;
67    return text;
68 }
69 
70 typedef struct
71 {
72    int offset;
73    int end;
74    char *filename;
75    int next_line_after;
76 } include_info;
77 
stb_include_append_include(include_info * array,int len,int offset,int end,char * filename,int next_line)78 static include_info *stb_include_append_include(include_info *array, int len, int offset, int end, char *filename, int next_line)
79 {
80    include_info *z = (include_info *) realloc(array, sizeof(*z) * (len+1));
81    z[len].offset   = offset;
82    z[len].end      = end;
83    z[len].filename = filename;
84    z[len].next_line_after = next_line;
85    return z;
86 }
87 
stb_include_free_includes(include_info * array,int len)88 static void stb_include_free_includes(include_info *array, int len)
89 {
90    int i;
91    for (i=0; i < len; ++i)
92       free(array[i].filename);
93    free(array);
94 }
95 
stb_include_isspace(int ch)96 static int stb_include_isspace(int ch)
97 {
98    return (ch == ' ' || ch == '\t' || ch == '\r' || ch == 'n');
99 }
100 
101 // find location of all #include and #inject
stb_include_find_includes(char * text,include_info ** plist)102 static int stb_include_find_includes(char *text, include_info **plist)
103 {
104    int line_count = 1;
105    int inc_count = 0;
106    char *s = text, *start;
107    include_info *list = NULL;
108    while (*s) {
109       // parse is always at start of line when we reach here
110       start = s;
111       while (*s == ' ' || *s == '\t')
112          ++s;
113       if (*s == '#') {
114          ++s;
115          while (*s == ' ' || *s == '\t')
116             ++s;
117          if (0==strncmp(s, "include", 7) && stb_include_isspace(s[7])) {
118             s += 7;
119             while (*s == ' ' || *s == '\t')
120                ++s;
121             if (*s == '"') {
122                char *t = ++s;
123                while (*t != '"' && *t != '\n' && *t != '\r' && *t != 0)
124                   ++t;
125                if (*t == '"') {
126                   char *filename = (char *) malloc(t-s+1);
127                   memcpy(filename, s, t-s);
128                   filename[t-s] = 0;
129                   s=t;
130                   while (*s != '\r' && *s != '\n' && *s != 0)
131                      ++s;
132                   // s points to the newline, so s-start is everything except the newline
133                   list = stb_include_append_include(list, inc_count++, start-text, s-text, filename, line_count+1);
134                }
135             }
136          } else if (0==strncmp(s, "inject", 6) && (stb_include_isspace(s[6]) || s[6]==0)) {
137             while (*s != '\r' && *s != '\n' && *s != 0)
138                ++s;
139             list = stb_include_append_include(list, inc_count++, start-text, s-text, NULL, line_count+1);
140          }
141       }
142       while (*s != '\r' && *s != '\n' && *s != 0)
143          ++s;
144       if (*s == '\r' || *s == '\n') {
145          s = s + (s[0] + s[1] == '\r' + '\n' ? 2 : 1);
146       }
147       ++line_count;
148    }
149    *plist = list;
150    return inc_count;
151 }
152 
153 // avoid dependency on sprintf()
stb_include_itoa(char str[9],int n)154 static void stb_include_itoa(char str[9], int n)
155 {
156    int i;
157    for (i=0; i < 8; ++i)
158       str[i] = ' ';
159    str[i] = 0;
160 
161    for (i=1; i < 8; ++i) {
162       str[7-i] = '0' + (n % 10);
163       n /= 10;
164       if (n == 0)
165          break;
166    }
167 }
168 
stb_include_append(char * str,size_t * curlen,char * addstr,size_t addlen)169 static char *stb_include_append(char *str, size_t *curlen, char *addstr, size_t addlen)
170 {
171    str = (char *) realloc(str, *curlen + addlen);
172    memcpy(str + *curlen, addstr, addlen);
173    *curlen += addlen;
174    return str;
175 }
176 
stb_include_string(char * str,char * inject,char * path_to_includes,char * filename,char error[256])177 char *stb_include_string(char *str, char *inject, char *path_to_includes, char *filename, char error[256])
178 {
179    char temp[4096];
180    include_info *inc_list;
181    int i, num = stb_include_find_includes(str, &inc_list);
182    size_t source_len = strlen(str);
183    char *text=0;
184    size_t textlen=0, last=0;
185    for (i=0; i < num; ++i) {
186       text = stb_include_append(text, &textlen, str+last, inc_list[i].offset - last);
187       // write out line directive for the include
188       #ifndef STB_INCLUDE_LINE_NONE
189       #ifdef STB_INCLUDE_LINE_GLSL
190       if (textlen != 0)  // GLSL #version must appear first, so don't put a #line at the top
191       #endif
192       {
193          strcpy(temp, "#line ");
194          stb_include_itoa(temp+6, 1);
195          strcat(temp, " ");
196          #ifdef STB_INCLUDE_LINE_GLSL
197          stb_include_itoa(temp+15, i+1);
198          #else
199          strcat(temp, "\"");
200          if (inc_list[i].filename == 0)
201             strcmp(temp, "INJECT");
202          else
203             strcat(temp, inc_list[i].filename);
204          strcat(temp, "\"");
205          #endif
206          strcat(temp, "\n");
207          text = stb_include_append(text, &textlen, temp, strlen(temp));
208       }
209       #endif
210       if (inc_list[i].filename == 0) {
211          if (inject != 0)
212             text = stb_include_append(text, &textlen, inject, strlen(inject));
213       } else {
214          char *inc;
215          strcpy(temp, path_to_includes);
216          strcat(temp, "/");
217          strcat(temp, inc_list[i].filename);
218          inc = stb_include_file(temp, inject, path_to_includes, error);
219          if (inc == NULL) {
220             stb_include_free_includes(inc_list, num);
221             return NULL;
222          }
223          text = stb_include_append(text, &textlen, inc, strlen(inc));
224          free(inc);
225       }
226       // write out line directive
227       #ifndef STB_INCLUDE_LINE_NONE
228       strcpy(temp, "\n#line ");
229       stb_include_itoa(temp+6, inc_list[i].next_line_after);
230       strcat(temp, " ");
231       #ifdef STB_INCLUDE_LINE_GLSL
232       stb_include_itoa(temp+15, 0);
233       #else
234       strcat(temp, filename != 0 ? filename : "source-file");
235       #endif
236       text = stb_include_append(text, &textlen, temp, strlen(temp));
237       // no newlines, because we kept the #include newlines, which will get appended next
238       #endif
239       last = inc_list[i].end;
240    }
241    text = stb_include_append(text, &textlen, str+last, source_len - last + 1); // append '\0'
242    stb_include_free_includes(inc_list, num);
243    return text;
244 }
245 
stb_include_strings(char ** strs,int count,char * inject,char * path_to_includes,char * filename,char error[256])246 char *stb_include_strings(char **strs, int count, char *inject, char *path_to_includes, char *filename, char error[256])
247 {
248    char *text;
249    char *result;
250    int i;
251    size_t length=0;
252    for (i=0; i < count; ++count)
253       length += strlen(strs[i]);
254    text = (char *) malloc(length+1);
255    length = 0;
256    for (i=0; i < count; ++count) {
257       strcpy(text + length, strs[i]);
258       length += strlen(strs[i]);
259    }
260    result = stb_include_string(text, inject, path_to_includes, filename, error);
261    free(text);
262    return result;
263 }
264 
stb_include_file(char * filename,char * inject,char * path_to_includes,char error[256])265 char *stb_include_file(char *filename, char *inject, char *path_to_includes, char error[256])
266 {
267    size_t len;
268    char *result;
269    char *text = stb_include_load_file(filename, &len);
270    if (text == NULL) {
271       strcpy(error, "Error: couldn't load '");
272       strcat(error, filename);
273       strcat(error, "'");
274       return 0;
275    }
276    result = stb_include_string(text, inject, path_to_includes, filename, error);
277    free(text);
278    return result;
279 }
280 
281 #if 0 // @TODO, GL_ARB_shader_language_include-style system that doesn't touch filesystem
282 char *stb_include_preloaded(char *str, char *inject, char *includes[][2], char error[256])
283 {
284 
285 }
286 #endif
287 
288 #endif // STB_INCLUDE_IMPLEMENTATION
289