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, ¤t_error);
311 }
312 else
313 {
314 lg_error.handler(¤t_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