1 /* Custom preprocessor for LUA pkg files by C. Blue
2  * Use:
3  *   Enable usage of #if..#else..#endif control structures in LUA pkg files
4  *   by making use of the C preprocessor.
5  * Note:
6  *   The LUA file (.pre) will need a #parse statement that includes a header
7  *   file containing all #define directives that ought to be used for
8  *   evaluating control structures (#if..) within the .pre file.
9  *
10  *   Local #define directives within the LUA (.pre) file itself will be ignored
11  *   for evaluating local control structures!
12  *   The reason for this is that 'tolua' actually can and does process
13  *   local #define statements already, so they must not be eliminated by
14  *   the C preprocessor.
15  *
16  *   Local #include directives will be processed normally and insert the
17  *   according files into the LUA file (.pre) source code.
18  *
19  *   Local define- and include-statements prefixed with a '$' will not be
20  *   evaluated by either the preprocessor or tolua and will instead be passed
21  *   down to become actual define- and include-statements in the resulting
22  *   w_xxxx.c file!
23  */
24 
25 
26 /* debug mode: Don't remove temporary files */
27 //#define DEBUG
28 
29 /* Use custom #parse and #include directives:
30    #parse does what #include actually did, while #include is now a statement
31    to pseudo-include further lua sources, without actually parsing them with
32    the preprocessor - ie it's just for basic text inclusion.
33    This allows to include #define lists such as skills required for spells. */
34 #define CUSTOM_INCLUDE
35 
36 
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 
main(int argc,char * argv[])41 int main(int argc, char *argv[]) {
42 	FILE *f_in, *f_out;
43 	char tmp_file[160 + 3], line[320 + 1], *line_ptr, line_mod[320 + 1 + 6];
44 	/* 320 + 1 + 6 -> allow +6 extra marker chars (originally '//', but interferes
45 	   with additionally included header files. So now a 'non-C' sequence instead,
46 	   ie a character sequence that doesn't occur in valid C code at line beginning.
47 	   It must also end on a C comment tag if we want the preprocessor to not remove
48 	   'obsolete' whitespace. This isn't required, but allows for better comparability
49 	   if the files are viewed by a human. Current sequence therefore: */
50 #ifdef CUSTOM_INCLUDE
51 	int included;//bool
52 	FILE *f_included;
53 	char included_file[160 + 3], included_line[320 + 1], *included_file_ptr, *included_line_ptr;
54 #endif
55 	char seq[7] = {'1', '=', '/', '0', '/', '/', 0};
56 	int cpp_opts;
57 
58 	char *cptr, *in_comment = NULL, *out_comment = NULL, *prev_in_comment;
59         int in_comment_block = 0;
60 
61 
62 	/* check command-line arguments */
63 
64 	if (argc < 4) {
65 		printf("Usage: preproc <input file> <output file> <name of C preprocessor> [<C preprocessor options>..]\n");
66 		printf("       C preprocessor options should usually (cpp) be: -C -P\n");
67 		return -1;
68 	}
69 	if (strlen(argv[1]) == 0) { /* paranoia */
70 		printf("Error: No input filename specified.\n");
71 		return -2;
72 	}
73 	if (strlen(argv[1]) > 160) {
74 		printf("Error: Input filename must not be longer than 160 characters.\n");
75 		return -3;
76 	}
77 	if (strlen(argv[2]) == 0) { /* paranoia */
78 		printf("Error: No output filename specified.\n");
79 		return -4;
80 	}
81 
82 
83 	/* open file */
84 	f_in = fopen(argv[1], "r");
85 	if (f_in == NULL) {
86 		printf("Error: Couldn't open input file.\n");
87 		return -5;
88 	}
89 
90 	sprintf(tmp_file, "%s_", argv[1]);
91 	f_out = fopen(tmp_file, "w");
92 	if (f_out == NULL) {
93 		printf("Error: Couldn't create temporary file.\n");
94 		return -6;
95 	}
96 
97 	/* create temporary working copy */
98 	while (fgets(line, 320, f_in)) {
99 		/* skip whitespaces at the beginning */
100 		line_ptr = line;
101 		while (*line_ptr == ' ' || *line_ptr == '\t') line_ptr++;
102 		/* copy it */
103 		fputs(line_ptr, f_out);
104 	}
105 	fclose(f_in);
106 	fclose(f_out);
107 
108 
109 #ifdef CUSTOM_INCLUDE
110 	do {
111 		included = 0;
112 
113 		/* pseudo-#include other source files recursively until no more #includes are found */
114 		sprintf(tmp_file, "%s_", argv[1]);
115 		f_in = fopen(tmp_file, "r");
116 		if (f_in == NULL) {
117 			printf("Error: Couldn't open custom-including input file.\n");
118 			return -11;
119 		}
120 
121 		sprintf(tmp_file, "%s__", argv[1]);
122 		f_out = fopen(tmp_file, "w");
123 		if (f_out == NULL) {
124 			printf("Error: Couldn't create custom-including temporary file.\n");
125 			return -12;
126 		}
127 
128 		/* read a line */
129 		while (fgets(line, 320, f_in)) {
130 			line_ptr = line;
131 
132 			/* actually process #include directive in our own, special way, same as all existing #defines:
133 			   we just add/insert them to the LUA (.pre) source! */
134 			if (strstr(line_ptr, "#include") != line_ptr) {
135 				/* copy normal lines without any change */
136 				fputs(line, f_out);
137 				continue;
138 			}
139 
140 			/* discard line, since WE do the work here, cpp won't get to see this #include at all */
141 			//hardcore!
142 			included = 1;
143 
144 			/* extract file name from #include directive, doesn't matter if in quotations or <..> */
145 			strcpy(included_file, line_ptr + 8);
146 			included_file_ptr = included_file;
147 			/* strip trailing spaces and tabs and newline */
148 			while (included_file[strlen(included_file) - 1] == ' ' ||
149 			    included_file[strlen(included_file) - 1] == '\t' ||
150 			    included_file[strlen(included_file) - 1] == '\n')
151 				included_file[strlen(included_file) - 1] = '\0';
152 			/* strip final " or > */
153 			if (included_file[strlen(included_file) - 1] == '"' ||
154 			    included_file[strlen(included_file) - 1] == '>')
155 				included_file[strlen(included_file) - 1] = '\0';
156 			/* strip leading tabs and spaces */
157 			while (*included_file_ptr == ' ' ||
158 			    *included_file_ptr == '\t')
159 				included_file_ptr++;
160 			/* strip first " or < */
161 			if (*included_file_ptr == '"' ||
162 			    *included_file_ptr == '<')
163 				included_file_ptr++;
164 
165 			f_included = fopen(included_file_ptr, "r");
166 			if (f_included == NULL) {
167 				printf("Error: Couldn't open included file: %s\n", included_file_ptr);
168 				return -10;
169 			}
170 
171 			fputs("\n", f_out);
172 			sprintf(tmp_file, "/* ---------------- #include '%s' ---------------- */\n", included_file_ptr);
173 			fputs(tmp_file, f_out);
174 
175 			/* copy included file line by line */
176 			while (fgets(included_line, 320, f_included)) {
177 				/* skip whitespaces at the beginning */
178 				included_line_ptr = included_line;
179 				while (*included_line_ptr == ' ' || *included_line_ptr == '\t') included_line_ptr++;
180 				/* copy it */
181 				fputs(included_line_ptr, f_out);
182 			}
183 
184 			sprintf(tmp_file, "/* ------------ end of #include '%s'. ------------ */\n", included_file);
185 			fputs(tmp_file, f_out);
186 			fputs("\n", f_out);
187 		}
188 
189 		fclose(f_in);
190 		fclose(f_out);
191 
192 		/* the dest file becomes the src file, for next pass (recursion) */
193 		sprintf(line, "%s_", argv[1]);
194 		remove(line);
195 		sprintf(tmp_file, "%s__", argv[1]);
196 		rename(tmp_file, line);
197 	} while (included);
198 #endif
199 
200 	/* create temporary file that can be preprocessed */
201 
202 	/* open file */
203 	sprintf(tmp_file, "%s_", argv[1]);
204 	f_in = fopen(tmp_file, "r");
205 	if (f_in == NULL) {
206 		printf("Error: Couldn't open input file.\n");
207 		return -5;
208 	}
209 
210 	sprintf(tmp_file, "%s__", argv[1]);
211 	f_out = fopen(tmp_file, "w");
212 	if (f_out == NULL) {
213 		printf("Error: Couldn't create temporary file.\n");
214 		return -6;
215 	}
216 
217 	/* read a line */
218 	while (fgets(line, 320, f_in)) {
219 		/* trim newline (at the end) */
220 		line_ptr = strchr(line, '\n');
221 		if (line_ptr) *line_ptr = '\0';
222 
223 		/* skip whitespaces at the beginning (redundant, done in 'working copy') */
224 		line_ptr = line;
225 		while (*line_ptr == ' ' || *line_ptr == '\t') line_ptr++;
226 
227 #ifndef CUSTOM_INCLUDE
228 		/* forward #parse directives as #include directives to the preprocessor
229 		   so it can process #if/#else/#endif structures accordingly */
230 		if (strstr(line_ptr, "#include") == line_ptr) {
231 			/* keep line as it is, so it will be processed by the C preprocessor */
232 			sprintf(line_mod, "%s\n", line);
233 #else
234 		/* forward #parse directives as #include directives to the preprocessor
235 		   so it can process #if/#else/#endif structures accordingly */
236 		if (strstr(line_ptr, "#parse") == line_ptr) {
237 			/* replace "#parse" by "#include" */
238 			sprintf(line_mod, "%s\n", line);
239 			cptr = strstr(line_mod, "#parse");
240 			strcpy(cptr, "#include");
241 			strcat(cptr, line_ptr + 6);
242 			strcat(cptr, "\n");
243 #endif
244 #if 0 /* don't include these, way too messy/can't work */
245 		} else if (strstr(line_ptr, "$#include") == line_ptr) {
246 			/* turn it into a normal #include so it will be processed by the C preprocessor */
247 			sprintf(line_mod, "%s\n", line_ptr + 1);
248 #endif
249 
250 		/* test for #if / #else / #endif at the beginning
251 		   of the line (after whitespaces have been trimmed).
252 		   Any other preprocessor directives could be added. */
253 		} else if (strstr(line_ptr, "#if") == line_ptr ||
254 		    strstr(line_ptr, "#else") == line_ptr ||
255 		    strstr(line_ptr, "#endif") == line_ptr) {
256 			/* keep line as it is, so it will get treated by the C preprocessor */
257 			sprintf(line_mod, "%s\n", line);
258 
259 		/* normal line - protect it from being changed by the C preprocessor */
260 		} else {
261 			/* add the marker that indicates a line that is not to be touched */
262 			sprintf(line_mod, "%s%s\n", seq, line);
263 		}
264 
265 		/* write modified line to temporary file */
266 		fputs(line_mod, f_out);
267 	}
268 
269 	fclose(f_out);
270 	fclose(f_in);
271 
272 
273 	/* call C preprocessor on temporary file */
274 
275 	sprintf(line, "%s ", argv[3]);
276 	for (cpp_opts = 5; cpp_opts <= argc; cpp_opts++) {
277 		strcat(line, argv[cpp_opts - 1]);
278 		strcat(line, " ");
279 	}
280 	strcat(line, tmp_file);
281 	strcat(line, " ");
282 #if 0 /* This works if we just invoke 'cpp', but it doesn't if the system doesn't \
283          have a 'cpp' command and we need to fallback to 'gcc', because gcc \
284          doesn't take another file name parm in the same syntax as cpp. */
285 	strcat(line, tmp_file);
286 	strcat(line, "_");
287 #else /* So instead we'll just grab the stdout output, which works fine with both */
288 	strcat(line, "> ");
289 	strcat(line, tmp_file);
290 	strcat(line, "_");
291 #endif
292 
293 	if (system(line) == -1) {
294 		printf("Error: Couldn't execute C preprocessor.\n");
295 		return -7;
296 	}
297 
298 #ifndef DEBUG
299 	remove(tmp_file);
300 #endif
301 
302 
303 	/* clean up preprocessed file to generate output file */
304 
305 	sprintf(tmp_file, "%s___", argv[1]);
306 
307 	f_in = fopen(tmp_file, "r");
308 	if (f_in == NULL) {
309 		printf("Error: Couldn't open preprocessed temporary file.\n");
310 		return -8;
311 	}
312 
313 	f_out = fopen(argv[2], "w");
314 	if (f_out == NULL) {
315 		printf("Error: Couldn't create output file.\n");
316 		return -9;
317 	}
318 
319 	/* read a line */
320 	while (fgets(line_mod, 320 + 2, f_in)) {
321                 /* preprocessor directives that were hidden _inside_ comments
322                    because silly tolua will choke on them */
323                 prev_in_comment = line_mod;
324                 do {
325                         if (out_comment) in_comment = out_comment = NULL;
326 
327                         if (!in_comment) {
328                                 if (in_comment_block) in_comment = line_mod;
329                                 else {
330                                         in_comment = strstr(prev_in_comment, "/*");
331                                         if (in_comment) prev_in_comment = in_comment + 2;
332                                 }
333                         }
334                         if (in_comment) {
335                                 if (in_comment_block) out_comment = strstr(line_mod, "*/");
336                                 else out_comment = strstr(in_comment, "*/");
337 
338                                 while ((cptr = strstr(in_comment, "#"))) {
339 					if (!out_comment || cptr < out_comment) {
340 						*cptr = ' ';
341 			                } else break;
342 				}
343 
344 			        if (out_comment) in_comment_block = 0;
345 		        }
346 		} while (in_comment && out_comment);
347                 if (in_comment) in_comment_block = 1;
348 
349 		/* aaand also strip comments that are adjacent, silyl tolua */
350 		if ((cptr = strstr(line_mod, "*//*"))) {
351 			cptr[0] = ' ';
352 			cptr[1] = ' ';
353 			cptr[2] = ' ';
354 			cptr[3] = ' ';
355                 }
356 
357 		/* gcc 4.8.0 now puts an URL in the top comment, on which tolua
358 		   chokes, sigh. */
359 		if ((cptr = strstr(line_mod, "http://www.gnu.org"))) cptr[5] = ':';
360 
361 		/* on to the actual work.. */
362 
363 
364 		/* strip prefixed marker sequence again and write line to output file */
365 		if (line_mod[0] == seq[0] &&
366 		    line_mod[1] == seq[1] &&
367 		    line_mod[2] == seq[2] &&
368 		    line_mod[3] == seq[3] &&
369 		    line_mod[4] == seq[4] &&
370 		    line_mod[5] == seq[5])
371 			fputs(line_mod + 6, f_out);
372 		/* lines that were eliminated by the preprocessor don't need any treatment */
373 		else fputs(line_mod, f_out);
374 	}
375 
376 	fclose(f_out);
377 	fclose(f_in);
378 
379 #ifndef DEBUG
380 	remove(tmp_file);
381 #endif
382 
383 
384 	/* all done */
385 
386 	return 0;
387 }
388 
389 #ifdef DUMB_WIN
390 int FAR PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrevInst,
391                        LPSTR lpCmdLine, int nCmdShow) {
392 	MSG      msg;
393 
394 	/* Process messages forever */
395 	while (GetMessage(&msg, NULL, 0, 0)) {
396 		TranslateMessage(&msg);
397 		DispatchMessage(&msg);
398 	}
399 
400 	main(0, NULL);
401 	return (0);
402 }
403 #endif
404