1/*
2 * Copyright (c) 2003 Matthijs Hollemans
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 * DEALINGS IN THE SOFTWARE.
21 */
22
23#include <Entry.h>
24#include <File.h>
25#include <Path.h>
26
27#include <setjmp.h>
28#include <stdarg.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32
33#include "rdef.h"
34#include "compile.h"
35#include "private.h"
36#include "parser.hpp"
37
38char lexfile[B_PATH_NAME_LENGTH];
39
40static BEntry entry;
41static BFile file;
42BResources rsrc;
43const char* rsrc_file;
44
45static jmp_buf abort_jmp;  // for aborting compilation
46
47// When it encounters an error, the parser immediately aborts (we don't try
48// error recovery). But its parse stack may still contain malloc'ed objects.
49// We keep track of these memory blocks in the mem_list, so we can properly
50// free them in case of an error (which is the polite thing to do since we
51// are a shared library). If the compilation was successful, then mem_list
52// should be empty. In DEBUG mode we dump some statistics to verify that the
53// parser properly frees up objects when it is done with them.
54
55#ifdef DEBUG
56struct mem_t {
57	void* ptr;
58	char* file;
59	int32 line;
60};
61
62typedef std::list<mem_t> mem_list_t;
63typedef mem_list_t::iterator mem_iter_t;
64
65static mem_list_t mem_list;
66#else
67static ptr_list_t mem_list;
68#endif
69
70
71class AddIncludeDir {
72	public:
73		AddIncludeDir(const char *file);
74		~AddIncludeDir();
75
76	private:
77		BPath fPath;
78};
79
80
81AddIncludeDir::AddIncludeDir(const char *file)
82{
83	// ignore the special stdin file
84	if (!strcmp(file, "-"))
85		return;
86
87	if (fPath.SetTo(file) != B_OK
88		|| fPath.GetParent(&fPath) != B_OK) {
89		fPath.Unset();
90		return;
91	}
92
93	rdef_add_include_dir(fPath.Path(), false);
94}
95
96
97AddIncludeDir::~AddIncludeDir()
98{
99	if (fPath.InitCheck() == B_OK)
100		rdef_remove_include_dir(fPath.Path());
101}
102
103
104//	#pragma mark -
105
106
107void *
108alloc_mem(size_t size)
109{
110	void *ptr = malloc(size);  // can be 0
111	if (ptr == NULL)
112		abort_compile(B_NO_MEMORY, "out of memory");
113
114#ifdef DEBUG
115	mem_t mem;
116	mem.ptr = ptr;
117	mem.file = strdup(lexfile);
118	mem.line = yylineno;
119	mem_list.push_front(mem);
120#else
121	mem_list.push_front(ptr);
122#endif
123
124	return ptr;
125}
126
127
128void
129free_mem(void *ptr)
130{
131	if (ptr != NULL) {
132#ifdef DEBUG
133		for (mem_iter_t i = mem_list.begin(); i != mem_list.end(); ++i) {
134			if (i->ptr == ptr) {
135				free(i->ptr);
136				free(i->file);
137				mem_list.erase(i);
138				return;
139			}
140		}
141#else
142		mem_list.remove(ptr);
143		free(ptr);
144#endif
145	}
146}
147
148
149static void
150clean_up_mem()
151{
152#ifdef DEBUG
153	if (mem_list.size() != 0)
154		printf("mem_list leaks %ld objects\n", mem_list.size());
155
156	for (mem_iter_t i = mem_list.begin(); i != mem_list.end(); ) {
157		printf("%p allocated at %s:%ld\n", i->ptr, i->file, i->line);
158		free(i->ptr);
159		free(i->file);
160		i = mem_list.erase(i);
161	}
162#else
163	free_ptr_list(mem_list);
164#endif
165}
166
167
168void
169abort_compile(status_t err, const char *format, ...)
170{
171	va_list ap;
172
173	rdef_err = err;
174	rdef_err_line = yylineno;
175	strcpy(rdef_err_file, lexfile);
176
177	va_start(ap, format);
178	vsprintf(rdef_err_msg, format, ap);
179	va_end(ap);
180
181	abort_compile();
182}
183
184
185void
186abort_compile()
187{
188	longjmp(abort_jmp, 1);
189}
190
191
192static void
193compile_file(char *file)
194{
195	strcpy(lexfile, file);
196
197	// "-" means reading from stdin
198	if (strcmp(file, "-"))
199		yyin = fopen(lexfile, "r");
200	else
201		yyin = stdin;
202
203	if (yyin == NULL) {
204		strcpy(rdef_err_file, lexfile);
205		rdef_err = RDEF_FILE_NOT_FOUND;
206		return;
207	}
208
209	init_lexer();
210	init_parser();
211
212	if (setjmp(abort_jmp) == 0) {
213		yyparse();
214	}
215
216	// About error handling: If the bison-generated parser encounters
217	// a syntax error, it calls yyerror(), aborts parsing, and returns
218	// from yyparse(). For other kinds of errors (semantics, problem
219	// writing to BResources, etc), we bail out with a longjmp(). From
220	// then on, we can tell success or failure by looking at rdef_err.
221
222	clean_up_lexer();
223	clean_up_parser();
224	clean_up_mem();
225}
226
227
228static status_t
229open_output_file()
230{
231	status_t err = entry.SetTo(rsrc_file, true);
232	if (err == B_OK) {
233		uint32 openMode = B_READ_WRITE | B_CREATE_FILE;
234		bool clobber = false;
235
236		if (!(flags & RDEF_MERGE_RESOURCES)) {
237			openMode |= B_ERASE_FILE;
238			clobber   = true;
239		}
240
241		err = file.SetTo(&entry, openMode);
242		if (err == B_OK)
243			err = rsrc.SetTo(&file, clobber);
244	}
245
246	return err;
247}
248
249
250static void
251close_output_file()
252{
253	if (rdef_err == B_OK || (flags & RDEF_MERGE_RESOURCES) != 0)
254		rsrc.Sync();
255	else
256		entry.Remove();  // throw away output file
257
258	file.Unset();
259	entry.Unset();
260}
261
262
263status_t
264rdef_compile(const char *outputFile)
265{
266	clear_error();
267
268	if (outputFile == NULL || outputFile[0] == '\0') {
269		rdef_err = B_BAD_VALUE;
270		return rdef_err;
271	}
272
273	rsrc_file = outputFile;
274	rdef_err = open_output_file();
275	if (rdef_err != B_OK)
276		return rdef_err;
277
278	for (ptr_iter_t i = input_files.begin();
279			(i != input_files.end()) && (rdef_err == B_OK); ++i) {
280		char *path = (char *)*i;
281
282		AddIncludeDir add(path);
283		compile_file(path);
284	}
285
286	close_output_file();
287	return rdef_err;
288}
289
290