1 /*************************************************************************/
2 /* Copyright (c) 2004                                                    */
3 /* Daniel Sleator, David Temperley, and John Lafferty                    */
4 /* Copyright (c) 2009 Linas Vepstas                                      */
5 /* All rights reserved                                                   */
6 /*                                                                       */
7 /* Use of the link grammar parsing system is subject to the terms of the */
8 /* license set forth in the LICENSE file included with this software.    */
9 /* This license allows free redistribution and use in source and binary  */
10 /* forms, with or without modification, subject to certain conditions.   */
11 /*                                                                       */
12 /*************************************************************************/
13 
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <stdarg.h>
18 
19 #include "error.h"
20 #include "api-structures.h"   // For Sentence_s
21 #include "print/print.h"      // For print_sentence_context()
22 #include "print/print-util.h" // For append_string()
23 
24 static void default_error_handler(lg_errinfo *, void *);
25 static TLS struct
26 {
27 	lg_error_handler handler;
28 	void *handler_data;
29 	lg_errinfo *errmsg;
30 } lg_error = { default_error_handler };
31 
32 /* This list should match enum lg_error_severity. */
33 #define MAX_SEVERITY_LABEL_SIZE 64 /* In bytes. */
34 const char *severity_label_by_level[] =
35 {
36 	"Fatal error", "Error", "Warning", "Info", "Debug", "Trace", /*lg_None*/"",
37 	NULL
38 };
39 
40 /* Name to prepend to messages. */
41 static const char libname[] = "link-grammar";
42 
43 /* === Error queue utilities ======================================== */
error_queue_resize(lg_errinfo * lge,int len)44 static lg_errinfo *error_queue_resize(lg_errinfo *lge, int len)
45 {
46 	lge = realloc(lge, (len+2) * sizeof(lg_errinfo));
47 	lge[len+1].text = NULL;
48 	return lge;
49 }
50 
error_queue_len(lg_errinfo * lge)51 static int error_queue_len(lg_errinfo *lge)
52 {
53 	size_t len = 0;
54 	if (lge)
55 		while (NULL != lge[len].text) len++;
56 	return len;
57 }
58 
error_queue_append(lg_errinfo ** lge,lg_errinfo * current_error)59 static void error_queue_append(lg_errinfo **lge, lg_errinfo *current_error)
60 {
61 	int n = error_queue_len(*lge);
62 
63 	*lge = error_queue_resize(*lge, n);
64 	current_error->text = strdup(current_error->text);
65 	(*lge)[n] = *current_error;
66 }
67 /* ==================================================================*/
68 
69 /**
70  * Return the error severity according to the start of the error string.
71  * If an error severity is not found - return None.
72  */
message_error_severity(const char * msgtext)73 static lg_error_severity message_error_severity(const char *msgtext)
74 {
75 	for (const char **llp = severity_label_by_level; NULL != *llp; llp++)
76 	{
77 		for (const char *s = *llp, *t = msgtext; ; s++, t++)
78 		{
79 			if ((':' == *t) && (t > msgtext))
80 			{
81 				return (int)(llp - severity_label_by_level + 1);
82 			}
83 			if ((*s != *t) || ('\0' == *s)) break;
84 		}
85 	}
86 
87 	return lg_None;
88 }
89 
lg_error_msg_free(lg_errinfo * lge)90 static void lg_error_msg_free(lg_errinfo *lge)
91 {
92 		free((void *)lge->text);
93 		free((void *)lge->severity_label);
94 }
95 
96 /* === API functions ================================================*/
97 /**
98  * Set the error handler function to the given one.
99  * @param lg_error_handler New error handler function
100  * @param data Argument for the error handler function
101  */
lg_error_set_handler(lg_error_handler f,void * data)102 lg_error_handler lg_error_set_handler(lg_error_handler f, void *data)
103 {
104 	const lg_error_handler oldf = lg_error.handler;
105 	lg_error.handler = f;
106 	lg_error.handler_data = data;
107 	return oldf;
108 }
109 
lg_error_set_handler_data(void * data)110 const void *lg_error_set_handler_data(void * data)
111 {
112 	const char *old_data = lg_error.handler_data;
113 
114 	lg_error.handler_data = data;
115 	return old_data;
116 }
117 
118 /**
119  * Print the error queue and free it.
120  * @param f Error handler function
121  * @param data Argument for the error handler function
122  * @return Number of errors
123  */
lg_error_printall(lg_error_handler f,void * data)124 int lg_error_printall(lg_error_handler f, void *data)
125 {
126 	int n = error_queue_len(lg_error.errmsg);
127 	if (0 == n) return 0;
128 
129 	for (lg_errinfo *lge = &lg_error.errmsg[n-1]; lge >= lg_error.errmsg; lge--)
130 	{
131 		if (NULL == f)
132 			default_error_handler(lge, data);
133 		else
134 			f(lg_error.errmsg, data);
135 		lg_error_msg_free(lge);
136 	}
137 	free(lg_error.errmsg);
138 	lg_error.errmsg = NULL;
139 
140 	return n;
141 }
142 
143 /**
144  * Clear the error queue. Free all of its memory.
145  * @return Number of errors
146  */
lg_error_clearall(void)147 int lg_error_clearall(void)
148 {
149 	if (NULL == lg_error.errmsg) return 0;
150 	int nerrors = 0;
151 
152 	for (lg_errinfo *lge = lg_error.errmsg; NULL != lge->text; lge++)
153 	{
154 		nerrors++;
155 		lg_error_msg_free(lge);
156 	}
157 	free(lg_error.errmsg);
158 	lg_error.errmsg = NULL;
159 
160 	return nerrors;
161 }
162 
163 /**
164  * Format the given raw error message.
165  * Create a complete error message, ready to be printed.
166  * If the severity is not lg_None, add the library name.
167  * Also add the severity label.
168  * @param lge The raw error message.
169  * @return The complete error message. The caller needs to free the memory.
170  */
lg_error_formatmsg(lg_errinfo * lge)171 char *lg_error_formatmsg(lg_errinfo *lge)
172 {
173 	dyn_str *s = dyn_str_new();
174 
175 	/* Prepend libname to messages with higher severity than Debug. */
176 	if (lge->severity < lg_Debug)
177 		append_string(s, "%s: ", libname);
178 
179 	if ((NULL != lge->severity_label) && ('\0' != lge->severity_label[0]))
180 		append_string(s, "%s: ", lge->severity_label);
181 
182 	append_string(s, "%s", lge->text);
183 
184 	return dyn_str_take(s);
185 }
186 
187 static TLS dyn_str *outbuf = NULL;
188 
189 /**
190  * Flush a partial error message if exists.
191  * Return true iff a message has been actually flushed.
192  *
193  * (Just using prt_error("\n") also flushes a buffered partial error
194  * message, but if there is no such message an empty message is generated).
195  */
lg_error_flush(void)196 bool lg_error_flush(void)
197 {
198 	if (outbuf == NULL) return false;
199 	prt_error("\n");
200 	return true;
201 }
202 /* ================================================================== */
203 
204 /**
205  * The default error handler callback function.
206  * @param lge The raw error message.
207  */
default_error_handler(lg_errinfo * lge,void * data)208 static void default_error_handler(lg_errinfo *lge, void *data)
209 {
210 	FILE *outfile = stdout;
211 
212 	if (((NULL == data) && (lge->severity < lg_Debug)) ||
213 	    ((NULL != data) && (lge->severity < *(lg_error_severity *)(int *)data) &&
214 	     (lg_None !=  lge->severity)))
215 	{
216 		fflush(stdout); /* Make sure that stdout has been written out first. */
217 		outfile = stderr;
218 	}
219 
220 	char *msgtext = lg_error_formatmsg(lge);
221 
222 	/* On MINGW64, fprintf() somehow doesn't follow fd redirection in Python
223 	 * (see divert_start() in tests.py), but there is no such a problem with
224 	 * fputs(). */
225 #if 0
226 	fprintf(outfile, "%s", msgtext);
227 #else
228 	fputs(msgtext, outfile);
229 #endif
230 
231 	free(msgtext);
232 
233 	fflush(outfile); /* Also stderr, in case some OS does some strange thing */
234 }
235 
236 /**
237  * Convert a numerical severity level to its corresponding string.
238  */
error_severity_label(lg_error_severity sev)239 static const char *error_severity_label(lg_error_severity sev)
240 {
241 	char *sevlabel = alloca(MAX_SEVERITY_LABEL_SIZE);
242 
243 	if (lg_None == sev)
244 	{
245 		sevlabel[0] = '\0';
246 	}
247 	else if ((sev < 1) || (sev > lg_None))
248 	{
249 		snprintf(sevlabel, MAX_SEVERITY_LABEL_SIZE, "Message severity %d", (int)sev);
250 	}
251 	else
252 	{
253 		sevlabel = (char *)severity_label_by_level[sev-1];
254 	}
255 
256 	return strdup(sevlabel);
257 }
258 
259 static void verr_msg(err_ctxt *ec, lg_error_severity sev, const char *fmt, va_list args)
260 	GNUC_PRINTF(3,0);
261 
verr_msg(err_ctxt * ec,lg_error_severity sev,const char * fmt,va_list args)262 static void verr_msg(err_ctxt *ec, lg_error_severity sev, const char *fmt, va_list args)
263 {
264 	if (NULL == outbuf) outbuf = dyn_str_new();
265 
266 	/*
267 	 * If the message is a complete one, it ends with a newline.  Else the
268 	 * message is buffered in msg_buf until it is complete. A complete line
269 	 * which is not a complete message is marked with a \ at its end (after
270 	 * its newline), which is removed here. The newline and \ should be
271 	 * specified only in the format string.
272 	 */
273 	char *nfmt;
274 	bool partline = false;
275 	const int fmtlen = strlen(fmt);
276 
277 	if ('\n' != fmt[fmtlen-1])
278 	{
279 		partline = true;
280 		if ('\\' == fmt[fmtlen-1])
281 		{
282 			nfmt = strdupa(fmt);
283 			nfmt[fmtlen-1] = '\0';
284 			fmt = nfmt;
285 		}
286 	}
287 	vappend_string(outbuf, fmt, args);
288 	if (partline) return;
289 
290 	if ((NULL != ec) && (NULL != ec->sent))
291 		print_sentence_context(ec->sent, outbuf);
292 
293 	lg_errinfo current_error;
294 	/* current_error.ec = *ec; */
295 	const char *error_text = outbuf->str;
296 	lg_error_severity msg_sev = message_error_severity(error_text);
297 	if (lg_None != msg_sev)
298 	{
299 		/* Strip off the error severity label, for consistency.
300 		 * lg_error_format() will reconstruct it. */
301 		error_text = strchr(error_text, ':') + 1;
302 		error_text += strspn(error_text, " \t");
303 	}
304 	current_error.text = error_text;
305 	current_error.severity = ((lg_None == msg_sev) && (0 != sev)) ? sev : msg_sev;
306 	current_error.severity_label = error_severity_label(current_error.severity);
307 
308 	if (NULL == lg_error.handler)
309 	{
310 		error_queue_append(&lg_error.errmsg, &current_error);
311 	}
312 	else
313 	{
314 		lg_error.handler(&current_error, lg_error.handler_data);
315 		free((void *)current_error.severity_label);
316 	}
317 
318 	dyn_str_delete(outbuf);
319 	outbuf = NULL;
320 }
321 
err_msgc(err_ctxt * ec,lg_error_severity sev,const char * fmt,...)322 void err_msgc(err_ctxt *ec, lg_error_severity sev, const char *fmt, ...)
323 {
324 	va_list args;
325 	va_start(args, fmt);
326 	verr_msg(ec, sev, fmt, args);
327 	va_end(args);
328 }
329 
330 /**
331  * Issue the given message.
332  * This is an API function.
333  *
334  * Usage notes:
335  * The severity can be specified as an initial string in the message,
336  * such as "Error: Rest of message".  For known severity names see
337  * \link severity_label_by_level List of severity strings. \endlink.
338  * See \link verr_msg \endlink for how the severity is handled
339  * if it is not specified.
340  *
341  * @param fmt printf()-like format.
342  * @param ... printf()-like arguments.
343  * @return Always 0, not to be used. This is needed so prt_error()
344  * can be used in complex macros that have to use the comma operator.
345  */
prt_error(const char * fmt,...)346 int prt_error(const char *fmt, ...)
347 {
348 	va_list args;
349 
350 	va_start(args, fmt);
351 	verr_msg(NULL, 0, fmt, args);
352 	va_end(args);
353 
354 	return 0;
355 }
356 
357 /**
358  * Check whether the given feature is enabled. It is considered
359  * enabled if it is found in the comma delimited list of features.
360  * This list, if not empty, has a leading and a trailing commas.
361  * Return NULL if not enabled, else ",". If the feature appears
362  * as "feature:param", return a pointer to param.
363  * @param    list Comma delimited list of features (start/end commas too).
364  * @param    ... List of features to check.
365  * @return   If not enabled - NULL; Else "," or the feature param if exists.
366  */
feature_enabled(const char * list,...)367 const char *feature_enabled(const char * list, ...)
368 {
369 
370 	const char *feature;
371 	va_list given_features;
372 	va_start(given_features, list);
373 
374 	while (NULL != (feature = va_arg(given_features, char *)))
375 	{
376 		if ('\0' == feature[0]) continue;
377 		size_t len = strlen(feature);
378 		char *buff = alloca(len + 2 + 1); /* leading comma + comma/colon + NUL */
379 
380 		/* The "feature" variable may contain a full/relative file path.
381 		 * If so, extract the file name from it. On Windows first try the
382 		 * native separator \, but also try /. */
383 		const char *dir_sep = NULL;
384 #ifdef _WIN32
385 		dir_sep = strrchr(feature, '\\');
386 #endif
387 		if (NULL == dir_sep) dir_sep = strrchr(feature, '/');
388 		if (NULL != dir_sep) feature = dir_sep + 1;
389 
390 		buff[0] = ',';
391 		strcpy(buff+1, feature);
392 		strcat(buff, ",");
393 
394 		if (NULL != strstr(list, buff))
395 		{
396 			va_end(given_features);
397 			return ",";
398 		}
399 		buff[len+1] = ':'; /* check for "feature:param" */
400 		if (NULL != strstr(list, buff))
401 		{
402 			va_end(given_features);
403 			return strstr(list, buff) + len + 1;
404 		}
405 	}
406 	va_end(given_features);
407 
408 	return NULL;
409 }
410