1 /* liblouis Braille Translation and Back-Translation Library
2 
3    Copyright (C) 2015, 2016 Christian Egli, Swiss Library for the Blind, Visually Impaired
4    and Print Disabled
5    Copyright (C) 2017 Bert Frees
6 
7    This program is free software: you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation, either version 3 of the License, or
10    (at your option) any later version.
11 
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16 
17    You should have received a copy of the GNU General Public License
18    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 #include <config.h>
22 #include <assert.h>
23 #include <getopt.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <unistd.h>
27 #include <string.h>
28 #include "internal.h"
29 #include "error.h"
30 #include "errno.h"
31 #include "progname.h"
32 #include "version-etc.h"
33 #include "brl_checks.h"
34 
35 static int verbose = 0;
36 static const struct option longopts[] = {
37 	{ "help", no_argument, NULL, 'h' },
38 	{ "version", no_argument, NULL, 'v' },
39 	{ "verbose", no_argument, &verbose, 1 },
40 	{ NULL, 0, NULL, 0 },
41 };
42 
43 const char version_etc_copyright[] =
44 		"Copyright %s %d Swiss Library for the Blind, Visually Impaired and Print "
45 		"Disabled";
46 
47 #define AUTHORS "Christian Egli"
48 
49 #define MODE_TRANSLATION_FORWARD 0
50 #define MODE_TRANSLATION_BACKWARD 1
51 #define MODE_TRANSLATION_BOTH_DIRECTIONS 2
52 #define MODE_HYPHENATION 3
53 #define MODE_HYPHENATION_BRAILLE 4
54 #define MODE_DISPLAY 5
55 #define MODE_DEFAULT MODE_TRANSLATION_FORWARD
56 
57 static void
print_help(void)58 print_help(void) {
59 	printf("\
60 Usage: %s YAML_TEST_FILE\n",
61 			program_name);
62 
63 	fputs("\
64 Run the tests defined in the YAML_TEST_FILE. Return 0 if all tests pass\n\
65 or 1 if any of the tests fail. The details of failing tests are printed\n\
66 to stderr.\n\n",
67 			stdout);
68 
69 	fputs("\
70   -h, --help          display this help and exit\n\
71   -v, --version       display version information and exit\n\
72       --verbose       report expected failures\n",
73 			stdout);
74 
75 	printf("\n");
76 	printf("Report bugs to %s.\n", PACKAGE_BUGREPORT);
77 
78 #ifdef PACKAGE_PACKAGER_BUG_REPORTS
79 	printf("Report %s bugs to: %s\n", PACKAGE_PACKAGER, PACKAGE_PACKAGER_BUG_REPORTS);
80 #endif
81 #ifdef PACKAGE_URL
82 	printf("%s home page: <%s>\n", PACKAGE_NAME, PACKAGE_URL);
83 #endif
84 }
85 
86 #define EXIT_SKIPPED 77
87 
88 #ifdef HAVE_LIBYAML
89 #include <yaml.h>
90 
91 typedef struct {
92 	const char *name;
93 	const char *content;  // table content in case of an inline table; NULL means name is
94 						  // a file
95 	int location;		  // location in YAML file (line number) where table is defined
96 	int is_display;		  // whether the table is a display table or a translation table
97 } table_value;
98 
99 const char *event_names[] = { "YAML_NO_EVENT", "YAML_STREAM_START_EVENT",
100 	"YAML_STREAM_END_EVENT", "YAML_DOCUMENT_START_EVENT", "YAML_DOCUMENT_END_EVENT",
101 	"YAML_ALIAS_EVENT", "YAML_SCALAR_EVENT", "YAML_SEQUENCE_START_EVENT",
102 	"YAML_SEQUENCE_END_EVENT", "YAML_MAPPING_START_EVENT", "YAML_MAPPING_END_EVENT" };
103 const char *encoding_names[] = { "YAML_ANY_ENCODING", "YAML_UTF8_ENCODING",
104 	"YAML_UTF16LE_ENCODING", "YAML_UTF16BE_ENCODING" };
105 
106 const char *inline_table_prefix = "checkyaml_inline_table_at_line_";
107 
108 char *file_name;
109 
110 int errors = 0;
111 int count = 0;
112 
113 static char const **emph_classes = NULL;
114 
115 static void
simple_error(const char * msg,yaml_parser_t * parser,yaml_event_t * event)116 simple_error(const char *msg, yaml_parser_t *parser, yaml_event_t *event) {
117 	error_at_line(EXIT_FAILURE, 0, file_name,
118 			event->start_mark.line ? event->start_mark.line + 1
119 								   : parser->problem_mark.line + 1,
120 			"%s", msg);
121 }
122 
123 static void
yaml_parse_error(yaml_parser_t * parser)124 yaml_parse_error(yaml_parser_t *parser) {
125 	error_at_line(EXIT_FAILURE, 0, file_name, parser->problem_mark.line + 1, "%s",
126 			parser->problem);
127 }
128 
129 static void
yaml_error(yaml_event_type_t expected,yaml_event_t * event)130 yaml_error(yaml_event_type_t expected, yaml_event_t *event) {
131 	error_at_line(EXIT_FAILURE, 0, file_name, event->start_mark.line + 1,
132 			"Expected %s (actual %s)", event_names[expected], event_names[event->type]);
133 }
134 
135 static char *
read_table_query(yaml_parser_t * parser,const char ** table_file_name_check)136 read_table_query(yaml_parser_t *parser, const char **table_file_name_check) {
137 	yaml_event_t event;
138 	char *query_as_string = malloc(sizeof(char) * MAXSTRING);
139 	char *p = query_as_string;
140 	query_as_string[0] = '\0';
141 	while (1) {
142 		if (!yaml_parser_parse(parser, &event)) yaml_error(YAML_SCALAR_EVENT, &event);
143 		if (event.type == YAML_SCALAR_EVENT) {
144 
145 			// (temporary) feature to check whether the table query matches an expected
146 			// table
147 			if (!strcmp((const char *)event.data.scalar.value, "__assert-match")) {
148 				yaml_event_delete(&event);
149 				if (!yaml_parser_parse(parser, &event) ||
150 						(event.type != YAML_SCALAR_EVENT))
151 					yaml_error(YAML_SCALAR_EVENT, &event);
152 				*table_file_name_check = strdup((const char *)event.data.scalar.value);
153 				yaml_event_delete(&event);
154 			} else {
155 				if (query_as_string != p) strcat(p++, " ");
156 				strcat(p, (const char *)event.data.scalar.value);
157 				p += event.data.scalar.length;
158 				strcat(p++, ":");
159 				yaml_event_delete(&event);
160 				if (!yaml_parser_parse(parser, &event) ||
161 						(event.type != YAML_SCALAR_EVENT))
162 					yaml_error(YAML_SCALAR_EVENT, &event);
163 				strcat(p, (const char *)event.data.scalar.value);
164 				p += event.data.scalar.length;
165 				yaml_event_delete(&event);
166 			}
167 		} else if (event.type == YAML_MAPPING_END_EVENT) {
168 			yaml_event_delete(&event);
169 			break;
170 		} else
171 			yaml_error(YAML_SCALAR_EVENT, &event);
172 	}
173 	return query_as_string;
174 }
175 
176 static void
compile_inline_table(const table_value * table)177 compile_inline_table(const table_value *table) {
178 	int location = table->location;
179 	char *p = (char *)table->content;
180 	char *line_start = p;
181 	int line_len = 0;
182 	while (*p) {
183 		if (*p == 10 || *p == 13) {
184 			char *line = strndup((const char *)line_start, line_len);
185 			int error = 0;
186 			if (!table->is_display)
187 				error = !_lou_compileTranslationRule(table->name, line);
188 			else
189 				error = !_lou_compileDisplayRule(table->name, line);
190 			if (error)
191 				error_at_line(EXIT_FAILURE, 0, file_name, location, "Error in table %s",
192 						table->name);
193 			location++;
194 			free(line);
195 			line_start = p + 1;
196 			line_len = 0;
197 		} else {
198 			line_len++;
199 		}
200 		p++;
201 	}
202 }
203 
204 static table_value *
read_table_value(yaml_parser_t * parser,int start_line,int is_display)205 read_table_value(yaml_parser_t *parser, int start_line, int is_display) {
206 	table_value *table;
207 	char *table_name = malloc(sizeof(char) * MAXSTRING);
208 	char *table_content = NULL;
209 	yaml_event_t event;
210 	table_name[0] = '\0';
211 	if (!yaml_parser_parse(parser, &event) ||
212 			!(event.type == YAML_SEQUENCE_START_EVENT ||
213 					event.type == YAML_SCALAR_EVENT ||
214 					event.type == YAML_MAPPING_START_EVENT))
215 		error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
216 				"Expected %s, %s or %s (actual %s)",
217 				event_names[YAML_SEQUENCE_START_EVENT], event_names[YAML_SCALAR_EVENT],
218 				event_names[YAML_MAPPING_START_EVENT], event_names[event.type]);
219 	if (event.type == YAML_SEQUENCE_START_EVENT) {
220 		yaml_event_delete(&event);
221 		int done = 0;
222 		char *p = table_name;
223 		while (!done) {
224 			if (!yaml_parser_parse(parser, &event)) {
225 				yaml_parse_error(parser);
226 			}
227 			if (event.type == YAML_SEQUENCE_END_EVENT) {
228 				done = 1;
229 			} else if (event.type == YAML_SCALAR_EVENT) {
230 				if (table_name != p) strcat(p++, ",");
231 				strcat(p, (const char *)event.data.scalar.value);
232 				p += event.data.scalar.length;
233 			}
234 			yaml_event_delete(&event);
235 		}
236 	} else if (event.type == YAML_SCALAR_EVENT) {
237 		yaml_char_t *p = event.data.scalar.value;
238 		if (*p)
239 			while (p[1]) p++;
240 		if (*p == 10 || *p == 13) {
241 			// If the scalar ends with a newline, assume it is a block
242 			// scalar, so treat as an inline table. (Is there a proper way
243 			// to check for a block scalar?)
244 			sprintf(table_name, "%s%d", inline_table_prefix, start_line);
245 			table_content = strdup((const char *)event.data.scalar.value);
246 		} else {
247 			strcat(table_name, (const char *)event.data.scalar.value);
248 		}
249 		yaml_event_delete(&event);
250 	} else {  // event.type == YAML_MAPPING_START_EVENT
251 		char *query;
252 		const char *table_file_name_check = NULL;
253 		yaml_event_delete(&event);
254 		query = read_table_query(parser, &table_file_name_check);
255 		table_name = lou_findTable(query);
256 		free(query);
257 		if (!table_name)
258 			error_at_line(EXIT_FAILURE, 0, file_name, start_line,
259 					"Table query did not match a table");
260 		if (table_file_name_check) {
261 			const char *table_file_name = table_name;
262 			do {
263 				table_file_name++;
264 			} while (*table_file_name);
265 			while (table_file_name >= table_name && *table_file_name != '/' &&
266 					*table_file_name != '\\')
267 				table_file_name--;
268 			if (strcmp(table_file_name_check, table_file_name + 1))
269 				error_at_line(EXIT_FAILURE, 0, file_name, start_line,
270 						"Table query did not match expected table: expected '%s' but got "
271 						"'%s'",
272 						table_file_name_check, table_file_name + 1);
273 		}
274 	}
275 	table = malloc(sizeof(table_value));
276 	table->name = table_name;
277 	table->content = table_content;
278 	table->location = start_line + 1;
279 	table->is_display = is_display;
280 	return table;
281 }
282 
283 static void
free_table_value(table_value * table)284 free_table_value(table_value *table) {
285 	if (table) {
286 		free((char *)table->name);
287 		if (table->content) free((char *)table->content);
288 		free(table);
289 	}
290 }
291 
292 static char *
read_table(yaml_event_t * start_event,yaml_parser_t * parser,const char * display_table)293 read_table(yaml_event_t *start_event, yaml_parser_t *parser, const char *display_table) {
294 	table_value *v = NULL;
295 	char *table_name = NULL;
296 	if (start_event->type != YAML_SCALAR_EVENT ||
297 			strcmp((const char *)start_event->data.scalar.value, "table"))
298 		return 0;
299 	v = read_table_value(parser, start_event->start_mark.line + 1, 0);
300 	if (v->content)
301 		compile_inline_table(v);
302 	else if (!_lou_getTranslationTable(v->name))
303 		error_at_line(EXIT_FAILURE, 0, file_name, start_event->start_mark.line + 1,
304 				"Table %s not valid", v->name);
305 	emph_classes = lou_getEmphClasses(v->name);  // get declared emphasis classes
306 	table_name = strdup((char *)v->name);
307 	if (!display_table) {
308 		if (v->content) {
309 			v->is_display = 1;
310 			compile_inline_table(v);
311 		} else if (!_lou_getDisplayTable(v->name))
312 			error_at_line(EXIT_FAILURE, 0, file_name, start_event->start_mark.line + 1,
313 					"Table %s not valid", v->name);
314 	}
315 	free_table_value(v);
316 	return table_name;
317 }
318 
319 static void
read_flags(yaml_parser_t * parser,int * testmode)320 read_flags(yaml_parser_t *parser, int *testmode) {
321 	yaml_event_t event;
322 	int parse_error = 1;
323 
324 	*testmode = MODE_DEFAULT;
325 
326 	if (!yaml_parser_parse(parser, &event) || (event.type != YAML_MAPPING_START_EVENT))
327 		yaml_error(YAML_MAPPING_START_EVENT, &event);
328 
329 	yaml_event_delete(&event);
330 
331 	while ((parse_error = yaml_parser_parse(parser, &event)) &&
332 			(event.type == YAML_SCALAR_EVENT)) {
333 		if (!strcmp((const char *)event.data.scalar.value, "testmode")) {
334 			yaml_event_delete(&event);
335 			if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT))
336 				yaml_error(YAML_SCALAR_EVENT, &event);
337 			if (!strcmp((const char *)event.data.scalar.value, "forward")) {
338 				*testmode = MODE_TRANSLATION_FORWARD;
339 			} else if (!strcmp((const char *)event.data.scalar.value, "backward")) {
340 				*testmode = MODE_TRANSLATION_BACKWARD;
341 			} else if (!strcmp((const char *)event.data.scalar.value, "bothDirections")) {
342 				*testmode = MODE_TRANSLATION_BOTH_DIRECTIONS;
343 			} else if (!strcmp((const char *)event.data.scalar.value, "hyphenate")) {
344 				*testmode = MODE_HYPHENATION;
345 			} else if (!strcmp((const char *)event.data.scalar.value,
346 							   "hyphenateBraille")) {
347 				*testmode = MODE_HYPHENATION_BRAILLE;
348 			} else if (!strcmp((const char *)event.data.scalar.value, "display")) {
349 				*testmode = MODE_DISPLAY;
350 			} else {
351 				error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
352 						"Testmode '%s' not supported\n", event.data.scalar.value);
353 			}
354 		} else {
355 			error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
356 					"Flag '%s' not supported\n", event.data.scalar.value);
357 		}
358 	}
359 	if (!parse_error) yaml_parse_error(parser);
360 	if (event.type != YAML_MAPPING_END_EVENT) yaml_error(YAML_MAPPING_END_EVENT, &event);
361 	yaml_event_delete(&event);
362 }
363 
364 static int
read_xfail(yaml_parser_t * parser)365 read_xfail(yaml_parser_t *parser) {
366 	yaml_event_t event;
367 	/* assume xfail true if there is an xfail key */
368 	int xfail = 1;
369 	if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT))
370 		yaml_error(YAML_SCALAR_EVENT, &event);
371 	if (!strcmp((const char *)event.data.scalar.value, "false") ||
372 			!strcmp((const char *)event.data.scalar.value, "off"))
373 		xfail = 0;
374 	yaml_event_delete(&event);
375 	return xfail;
376 }
377 
378 static translationModes
read_mode(yaml_parser_t * parser)379 read_mode(yaml_parser_t *parser) {
380 	yaml_event_t event;
381 	translationModes mode = 0;
382 	int parse_error = 1;
383 
384 	if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SEQUENCE_START_EVENT))
385 		yaml_error(YAML_SEQUENCE_START_EVENT, &event);
386 	yaml_event_delete(&event);
387 
388 	while ((parse_error = yaml_parser_parse(parser, &event)) &&
389 			(event.type == YAML_SCALAR_EVENT)) {
390 		if (!strcmp((const char *)event.data.scalar.value, "noContractions")) {
391 			mode |= noContractions;
392 		} else if (!strcmp((const char *)event.data.scalar.value, "compbrlAtCursor")) {
393 			mode |= compbrlAtCursor;
394 		} else if (!strcmp((const char *)event.data.scalar.value, "dotsIO")) {
395 			mode |= dotsIO;
396 		} else if (!strcmp((const char *)event.data.scalar.value, "compbrlLeftCursor")) {
397 			mode |= compbrlLeftCursor;
398 		} else if (!strcmp((const char *)event.data.scalar.value, "ucBrl")) {
399 			mode |= ucBrl;
400 		} else if (!strcmp((const char *)event.data.scalar.value, "noUndefined")) {
401 			mode |= noUndefined;
402 		} else if (!strcmp((const char *)event.data.scalar.value, "partialTrans")) {
403 			mode |= partialTrans;
404 		} else {
405 			error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
406 					"Mode '%s' not supported\n", event.data.scalar.value);
407 		}
408 		yaml_event_delete(&event);
409 	}
410 	if (!parse_error) yaml_parse_error(parser);
411 	if (event.type != YAML_SEQUENCE_END_EVENT)
412 		yaml_error(YAML_SEQUENCE_END_EVENT, &event);
413 	yaml_event_delete(&event);
414 	return mode;
415 }
416 
417 static int
parse_number(const char * number,const char * name,int file_line)418 parse_number(const char *number, const char *name, int file_line) {
419 	char *tail;
420 	errno = 0;
421 
422 	int val = strtol(number, &tail, 0);
423 	if (errno != 0)
424 		error_at_line(EXIT_FAILURE, 0, file_name, file_line,
425 				"Not a valid %s '%s'. Must be a number\n", name, number);
426 	if (number == tail)
427 		error_at_line(EXIT_FAILURE, 0, file_name, file_line,
428 				"No digits found in %s '%s'. Must be a number\n", name, number);
429 	return val;
430 }
431 
432 static int *
read_inPos(yaml_parser_t * parser,int translen)433 read_inPos(yaml_parser_t *parser, int translen) {
434 	int *pos = malloc(sizeof(int) * translen);
435 	int i = 0;
436 	yaml_event_t event;
437 	int parse_error = 1;
438 
439 	if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SEQUENCE_START_EVENT))
440 		yaml_error(YAML_SEQUENCE_START_EVENT, &event);
441 	yaml_event_delete(&event);
442 
443 	while ((parse_error = yaml_parser_parse(parser, &event)) &&
444 			(event.type == YAML_SCALAR_EVENT)) {
445 		if (i >= translen)
446 			error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
447 					"Too many input positions for translation of length %d.", translen);
448 
449 		pos[i++] = parse_number((const char *)event.data.scalar.value, "input position",
450 				event.start_mark.line + 1);
451 		yaml_event_delete(&event);
452 	}
453 	if (i < translen)
454 		error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
455 				"Too few input positions (%i) for translation of length %i\n", i,
456 				translen);
457 	if (!parse_error) yaml_parse_error(parser);
458 	if (event.type != YAML_SEQUENCE_END_EVENT)
459 		yaml_error(YAML_SEQUENCE_END_EVENT, &event);
460 	yaml_event_delete(&event);
461 	return pos;
462 }
463 
464 static int *
read_outPos(yaml_parser_t * parser,int wrdlen,int translen)465 read_outPos(yaml_parser_t *parser, int wrdlen, int translen) {
466 	int *pos = malloc(sizeof(int) * wrdlen);
467 	int i = 0;
468 	yaml_event_t event;
469 	int parse_error = 1;
470 
471 	if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SEQUENCE_START_EVENT))
472 		yaml_error(YAML_SEQUENCE_START_EVENT, &event);
473 	yaml_event_delete(&event);
474 
475 	while ((parse_error = yaml_parser_parse(parser, &event)) &&
476 			(event.type == YAML_SCALAR_EVENT)) {
477 		if (i >= wrdlen)
478 			error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
479 					"Too many output positions for input string of length %d.", translen);
480 
481 		pos[i++] = parse_number((const char *)event.data.scalar.value, "output position",
482 				event.start_mark.line + 1);
483 		yaml_event_delete(&event);
484 	}
485 	if (i < wrdlen)
486 		error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
487 				"Too few output positions (%i) for input string of length %i\n", i,
488 				wrdlen);
489 	if (!parse_error) yaml_parse_error(parser);
490 	if (event.type != YAML_SEQUENCE_END_EVENT)
491 		yaml_error(YAML_SEQUENCE_END_EVENT, &event);
492 	yaml_event_delete(&event);
493 	return pos;
494 }
495 
496 static void
read_cursorPos(yaml_parser_t * parser,int * cursorPos,int * expected_cursorPos,int wrdlen,int translen)497 read_cursorPos(yaml_parser_t *parser, int *cursorPos, int *expected_cursorPos, int wrdlen,
498 		int translen) {
499 	yaml_event_t event;
500 
501 	if (!yaml_parser_parse(parser, &event) ||
502 			!(event.type == YAML_SEQUENCE_START_EVENT || event.type == YAML_SCALAR_EVENT))
503 		error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
504 				"Expected %s or %s (actual %s)", event_names[YAML_SEQUENCE_START_EVENT],
505 				event_names[YAML_SCALAR_EVENT], event_names[event.type]);
506 
507 	if (event.type == YAML_SEQUENCE_START_EVENT) {
508 		/* it's a sequence: read the two cursor positions (before and after) */
509 		yaml_event_delete(&event);
510 		if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT))
511 			yaml_error(YAML_SCALAR_EVENT, &event);
512 		*cursorPos = parse_number((const char *)event.data.scalar.value,
513 				"cursor position", event.start_mark.line + 1);
514 		if ((0 > *cursorPos) || (*cursorPos >= wrdlen))
515 			error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
516 					"Cursor position (%i) outside of input string of length %i\n",
517 					*cursorPos, wrdlen);
518 		yaml_event_delete(&event);
519 		if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT))
520 			error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
521 					"Too few cursor positions, 2 are expected (before and after)\n");
522 		*expected_cursorPos = parse_number((const char *)event.data.scalar.value,
523 				"expected cursor position", event.start_mark.line + 1);
524 		if ((0 > *expected_cursorPos) || (*expected_cursorPos >= wrdlen))
525 			error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
526 					"Expected cursor position (%i) outside of output string of length "
527 					"%i\n",
528 					*expected_cursorPos, translen);
529 		yaml_event_delete(&event);
530 		if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SEQUENCE_END_EVENT))
531 			error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
532 					"Too many cursor positions, only 2 are expected (before and "
533 					"after)\n");
534 		yaml_event_delete(&event);
535 	} else {  // YAML_SCALAR_EVENT
536 		/* it's just a single value: just read the initial cursor position */
537 		*cursorPos = parse_number((const char *)event.data.scalar.value,
538 				"cursor position before", event.start_mark.line + 1);
539 		if ((0 > *cursorPos) || (*cursorPos >= wrdlen))
540 			error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
541 					"Cursor position (%i) outside of input string of length %i\n",
542 					*cursorPos, wrdlen);
543 		*expected_cursorPos = -1;
544 		yaml_event_delete(&event);
545 	}
546 }
547 
548 static void
read_typeform_string(yaml_parser_t * parser,formtype * typeform,typeforms kind,int len)549 read_typeform_string(yaml_parser_t *parser, formtype *typeform, typeforms kind, int len) {
550 	yaml_event_t event;
551 	int typeform_len;
552 
553 	if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT))
554 		yaml_error(YAML_SCALAR_EVENT, &event);
555 	typeform_len = strlen((const char *)event.data.scalar.value);
556 	if (typeform_len != len)
557 		error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
558 				"Too many or too few typeforms (%i) for word of length %i\n",
559 				typeform_len, len);
560 	update_typeform((const char *)event.data.scalar.value, typeform, kind);
561 	yaml_event_delete(&event);
562 }
563 
564 static formtype *
read_typeforms(yaml_parser_t * parser,int len)565 read_typeforms(yaml_parser_t *parser, int len) {
566 	yaml_event_t event;
567 	formtype *typeform = calloc(len, sizeof(formtype));
568 	int parse_error = 1;
569 
570 	if (!yaml_parser_parse(parser, &event) || (event.type != YAML_MAPPING_START_EVENT))
571 		yaml_error(YAML_MAPPING_START_EVENT, &event);
572 	yaml_event_delete(&event);
573 
574 	while ((parse_error = yaml_parser_parse(parser, &event)) &&
575 			(event.type == YAML_SCALAR_EVENT)) {
576 		if (strcmp((const char *)event.data.scalar.value, "computer_braille") == 0) {
577 			yaml_event_delete(&event);
578 			read_typeform_string(parser, typeform, computer_braille, len);
579 		} else if (strcmp((const char *)event.data.scalar.value, "no_translate") == 0) {
580 			yaml_event_delete(&event);
581 			read_typeform_string(parser, typeform, no_translate, len);
582 		} else if (strcmp((const char *)event.data.scalar.value, "no_contract") == 0) {
583 			yaml_event_delete(&event);
584 			read_typeform_string(parser, typeform, no_contract, len);
585 		} else {
586 			int i;
587 			typeforms kind = plain_text;
588 			for (i = 0; emph_classes[i]; i++) {
589 				if (strcmp((const char *)event.data.scalar.value, emph_classes[i]) == 0) {
590 					yaml_event_delete(&event);
591 					kind = italic << i;
592 					if (kind > emph_10)
593 						error_at_line(EXIT_FAILURE, 0, file_name,
594 								event.start_mark.line + 1,
595 								"Typeform '%s' was not declared\n",
596 								event.data.scalar.value);
597 					read_typeform_string(parser, typeform, kind, len);
598 					break;
599 				}
600 			}
601 		}
602 	}
603 	if (!parse_error) yaml_parse_error(parser);
604 
605 	if (event.type != YAML_MAPPING_END_EVENT) yaml_error(YAML_MAPPING_END_EVENT, &event);
606 	yaml_event_delete(&event);
607 	return typeform;
608 }
609 
610 static void
read_options(yaml_parser_t * parser,int testmode,int wordLen,int translationLen,int * xfail,translationModes * mode,formtype ** typeform,int ** inPos,int ** outPos,int * cursorPos,int * cursorOutPos,int * maxOutputLen,int * realInputLen)611 read_options(yaml_parser_t *parser, int testmode, int wordLen, int translationLen,
612 		int *xfail, translationModes *mode, formtype **typeform, int **inPos,
613 		int **outPos, int *cursorPos, int *cursorOutPos, int *maxOutputLen,
614 		int *realInputLen) {
615 	yaml_event_t event;
616 	char *option_name;
617 	int parse_error = 1;
618 
619 	*mode = 0;
620 	*xfail = 0;
621 	*typeform = NULL;
622 	*inPos = NULL;
623 	*outPos = NULL;
624 
625 	while ((parse_error = yaml_parser_parse(parser, &event)) &&
626 			(event.type == YAML_SCALAR_EVENT)) {
627 		option_name =
628 				strndup((const char *)event.data.scalar.value, event.data.scalar.length);
629 
630 		if (!strcmp(option_name, "xfail")) {
631 			yaml_event_delete(&event);
632 			*xfail = read_xfail(parser);
633 		} else if (!strcmp(option_name, "mode")) {
634 			yaml_event_delete(&event);
635 			*mode = read_mode(parser);
636 		} else if (!strcmp(option_name, "typeform")) {
637 			if (testmode != MODE_TRANSLATION_FORWARD) {
638 				error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
639 						"typeforms only supported with testmode 'forward'\n");
640 			}
641 			yaml_event_delete(&event);
642 			*typeform = read_typeforms(parser, wordLen);
643 		} else if (!strcmp(option_name, "inputPos")) {
644 			yaml_event_delete(&event);
645 			*inPos = read_inPos(parser, translationLen);
646 		} else if (!strcmp(option_name, "outputPos")) {
647 			yaml_event_delete(&event);
648 			*outPos = read_outPos(parser, wordLen, translationLen);
649 		} else if (!strcmp(option_name, "cursorPos")) {
650 			if (testmode == MODE_TRANSLATION_BOTH_DIRECTIONS) {
651 				error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
652 						"cursorPos not supported with testmode 'bothDirections'\n");
653 			}
654 			yaml_event_delete(&event);
655 			read_cursorPos(parser, cursorPos, cursorOutPos, wordLen, translationLen);
656 		} else if (!strcmp(option_name, "maxOutputLength")) {
657 			if (testmode == MODE_TRANSLATION_BOTH_DIRECTIONS) {
658 				error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
659 						"maxOutputLength not supported with testmode 'bothDirections'\n");
660 			}
661 			yaml_event_delete(&event);
662 			if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT))
663 				yaml_error(YAML_SCALAR_EVENT, &event);
664 			*maxOutputLen = parse_number((const char *)event.data.scalar.value,
665 					"Maximum output length", event.start_mark.line + 1);
666 			if (*maxOutputLen <= 0)
667 				error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
668 						"Maximum output length (%i) must be a positive number\n",
669 						*maxOutputLen);
670 			if (*maxOutputLen < translationLen)
671 				error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
672 						"Expected translation length (%i) must not exceed maximum output "
673 						"length (%i)\n",
674 						translationLen, *maxOutputLen);
675 			yaml_event_delete(&event);
676 		} else if (!strcmp(option_name, "realInputLength")) {
677 			yaml_event_delete(&event);
678 			if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT))
679 				yaml_error(YAML_SCALAR_EVENT, &event);
680 			*realInputLen = parse_number((const char *)event.data.scalar.value,
681 					"Real input length", event.start_mark.line + 1);
682 			if (*realInputLen < 0)
683 				error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
684 						"Real input length (%i) must not be a negative number\n",
685 						*realInputLen);
686 			if (*realInputLen > wordLen)
687 				error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
688 						"Real input length (%i) must not exceed total input "
689 						"length (%i)\n",
690 						*realInputLen, wordLen);
691 			yaml_event_delete(&event);
692 		} else {
693 			error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
694 					"Unsupported option %s", option_name);
695 		}
696 		free(option_name);
697 	}
698 	if (!parse_error) yaml_parse_error(parser);
699 	if (event.type != YAML_MAPPING_END_EVENT) yaml_error(YAML_MAPPING_END_EVENT, &event);
700 	yaml_event_delete(&event);
701 }
702 
703 /* see http://stackoverflow.com/questions/5117393/utf-8-strings-length-in-linux-c */
704 static int
my_strlen_utf8_c(char * s)705 my_strlen_utf8_c(char *s) {
706 	int i = 0, j = 0;
707 	while (s[i]) {
708 		if ((s[i] & 0xc0) != 0x80) j++;
709 		i++;
710 	}
711 	return j;
712 }
713 
714 /*
715  * String parsing is also done later in check_base. At this point we
716  * only need it to compute the actual string length in order to be
717  * able to provide error messages when parsing typeform and position arrays.
718  */
719 static int
parsed_strlen(char * s)720 parsed_strlen(char *s) {
721 	widechar *buf;
722 	int len, maxlen;
723 	maxlen = my_strlen_utf8_c(s);
724 	buf = malloc(sizeof(widechar) * maxlen);
725 	len = _lou_extParseChars(s, buf);
726 	free(buf);
727 	return len;
728 }
729 
730 static void
read_test(yaml_parser_t * parser,char ** tables,const char * display_table,int testmode)731 read_test(yaml_parser_t *parser, char **tables, const char *display_table, int testmode) {
732 	yaml_event_t event;
733 	char *description = NULL;
734 	char *word;
735 	char *translation;
736 	int xfail = 0;
737 	translationModes mode = 0;
738 	formtype *typeform = NULL;
739 	int *inPos = NULL;
740 	int *outPos = NULL;
741 	int cursorPos = -1;
742 	int cursorOutPos = -1;
743 	int maxOutputLen = -1;
744 	int realInputLen = -1;
745 
746 	if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT))
747 		simple_error("Word expected", parser, &event);
748 
749 	word = strndup((const char *)event.data.scalar.value, event.data.scalar.length);
750 	yaml_event_delete(&event);
751 
752 	if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT))
753 		simple_error("Translation expected", parser, &event);
754 
755 	translation =
756 			strndup((const char *)event.data.scalar.value, event.data.scalar.length);
757 	yaml_event_delete(&event);
758 
759 	if (!yaml_parser_parse(parser, &event)) yaml_parse_error(parser);
760 
761 	/* Handle an optional description */
762 	if (event.type == YAML_SCALAR_EVENT) {
763 		description = word;
764 		word = translation;
765 		translation =
766 				strndup((const char *)event.data.scalar.value, event.data.scalar.length);
767 		yaml_event_delete(&event);
768 
769 		if (!yaml_parser_parse(parser, &event)) yaml_parse_error(parser);
770 	}
771 
772 	if (event.type == YAML_MAPPING_START_EVENT) {
773 		yaml_event_delete(&event);
774 		read_options(parser, testmode, parsed_strlen(word), parsed_strlen(translation),
775 				&xfail, &mode, &typeform, &inPos, &outPos, &cursorPos, &cursorOutPos,
776 				&maxOutputLen, &realInputLen);
777 
778 		if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SEQUENCE_END_EVENT))
779 			yaml_error(YAML_SEQUENCE_END_EVENT, &event);
780 	} else if (event.type != YAML_SEQUENCE_END_EVENT) {
781 		error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
782 				"Expected %s or %s (actual %s)", event_names[YAML_MAPPING_START_EVENT],
783 				event_names[YAML_SEQUENCE_END_EVENT], event_names[event.type]);
784 	}
785 
786 	int result = 0;
787 	char **table = tables;
788 	while (*table) {
789 		int r;
790 		if (testmode == MODE_HYPHENATION || testmode == MODE_HYPHENATION_BRAILLE) {
791 			r = check_hyphenation(
792 					*table, word, translation, testmode == MODE_HYPHENATION_BRAILLE);
793 
794 		} else if (testmode == MODE_DISPLAY) {
795 			r = check_display(*table, word, translation);
796 		} else {
797 			int direction = DIRECTION_FORWARD;
798 			if (testmode == MODE_TRANSLATION_BACKWARD)
799 				direction = DIRECTION_BACKWARD;
800 			else if (testmode == MODE_TRANSLATION_BOTH_DIRECTIONS)
801 				direction = DIRECTION_BOTH;
802 			// FIXME: Note that the typeform array was constructed using the
803 			// emphasis classes mapping of the last compiled table. This
804 			// means that if we are testing multiple tables at the same time
805 			// they must have the same mapping (i.e. the emphasis classes
806 			// must be defined in the same order).
807 			r = check(*table, word, translation, .display_table = display_table,
808 					.typeform = typeform, .mode = mode, .expected_inputPos = inPos,
809 					.expected_outputPos = outPos, .cursorPos = cursorPos,
810 					.expected_cursorPos = cursorOutPos, .max_outlen = maxOutputLen,
811 					.real_inlen = realInputLen, .direction = direction,
812 					.diagnostics = !xfail);
813 		}
814 		if (xfail != r) {
815 			// FAIL or XPASS
816 			if (description) fprintf(stderr, "%s\n", description);
817 			error_at_line(0, 0, file_name, event.start_mark.line + 1,
818 					(xfail ? "Unexpected Pass" : "Failure"));
819 			errors++;
820 			// on error print the table name, as it isn't always clear
821 			// which table we are testing. You can can define a test
822 			// for multiple tables.
823 			fprintf(stderr, "Table: %s\n", *table);
824 			if (display_table) fprintf(stderr, "Display table: %s\n", display_table);
825 			// add an empty line after each error
826 			fprintf(stderr, "\n");
827 		} else if (xfail && r && verbose) {
828 			// XFAIL
829 			// in verbose mode print expected failures
830 			if (description) fprintf(stderr, "%s\n", description);
831 			error_at_line(0, 0, file_name, event.start_mark.line + 1, "Expected Failure");
832 			fprintf(stderr, "Table: %s\n", *table);
833 			if (display_table) fprintf(stderr, "Display table: %s\n", display_table);
834 			fprintf(stderr, "\n");
835 		}
836 		result |= r;
837 		table++;
838 		count++;
839 	}
840 	yaml_event_delete(&event);
841 
842 	free(description);
843 	free(word);
844 	free(translation);
845 	free(typeform);
846 	free(inPos);
847 	free(outPos);
848 }
849 
850 static void
read_tests(yaml_parser_t * parser,char ** tables,const char * display_table,int testmode)851 read_tests(
852 		yaml_parser_t *parser, char **tables, const char *display_table, int testmode) {
853 	yaml_event_t event;
854 	if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SEQUENCE_START_EVENT))
855 		yaml_error(YAML_SEQUENCE_START_EVENT, &event);
856 
857 	yaml_event_delete(&event);
858 
859 	int done = 0;
860 	while (!done) {
861 		if (!yaml_parser_parse(parser, &event)) {
862 			yaml_parse_error(parser);
863 		}
864 		if (event.type == YAML_SEQUENCE_END_EVENT) {
865 			done = 1;
866 			yaml_event_delete(&event);
867 		} else if (event.type == YAML_SEQUENCE_START_EVENT) {
868 			yaml_event_delete(&event);
869 			read_test(parser, tables, display_table, testmode);
870 		} else {
871 			error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
872 					"Expected %s or %s (actual %s)", event_names[YAML_SEQUENCE_END_EVENT],
873 					event_names[YAML_SEQUENCE_START_EVENT], event_names[event.type]);
874 		}
875 	}
876 }
877 
878 /*
879  * This custom table resolver handles magic table names that represent
880  * inline tables.
881  */
882 static char **
customTableResolver(const char * tableList,const char * base)883 customTableResolver(const char *tableList, const char *base) {
884 	static char *dummy_table[1];
885 	char *p = (char *)tableList;
886 	while (*p != '\0') {
887 		if (strncmp(p, inline_table_prefix, strlen(inline_table_prefix)) == 0)
888 			return dummy_table;
889 		while (*p != '\0' && *p != ',') p++;
890 		if (*p == ',') p++;
891 	}
892 	return _lou_defaultTableResolver(tableList, base);
893 }
894 
895 #endif  // HAVE_LIBYAML
896 
897 int
main(int argc,char * argv[])898 main(int argc, char *argv[]) {
899 	int optc;
900 
901 	set_program_name(argv[0]);
902 
903 	while ((optc = getopt_long(argc, argv, "hv", longopts, NULL)) != -1) switch (optc) {
904 		/* --help and --version exit immediately, per GNU coding standards.  */
905 		case 'v':
906 			version_etc(
907 					stdout, program_name, PACKAGE_NAME, VERSION, AUTHORS, (char *)NULL);
908 			exit(EXIT_SUCCESS);
909 			break;
910 		case 'h':
911 			print_help();
912 			exit(EXIT_SUCCESS);
913 			break;
914 		default:
915 			fprintf(stderr, "Try `%s --help' for more information.\n", program_name);
916 			exit(EXIT_FAILURE);
917 			break;
918 		}
919 
920 	if (optind != argc - 1) {
921 		/* Print error message and exit.  */
922 		if (optind < argc - 1)
923 			fprintf(stderr, "%s: extra operand: %s\n", program_name, argv[optind + 1]);
924 		else
925 			fprintf(stderr, "%s: no YAML test file specified\n", program_name);
926 
927 		fprintf(stderr, "Try `%s --help' for more information.\n", program_name);
928 		exit(EXIT_FAILURE);
929 	}
930 
931 #ifdef WITHOUT_YAML
932 	fprintf(stderr,
933 			"Skipping tests for %s as yaml was disabled in configure with "
934 			"--without-yaml\n",
935 			argv[1]);
936 	return EXIT_SKIPPED;
937 #else
938 #ifndef HAVE_LIBYAML
939 	fprintf(stderr, "Skipping tests for %s as libyaml was not found\n", argv[1]);
940 	return EXIT_SKIPPED;
941 #endif  // not HAVE_LIBYAML
942 #endif  // WITHOUT_YAML
943 
944 #ifndef WITHOUT_YAML
945 #ifdef HAVE_LIBYAML
946 
947 	FILE *file;
948 	yaml_parser_t parser;
949 	yaml_event_t event;
950 
951 	file_name = argv[1];
952 	file = fopen(file_name, "rb");
953 	if (!file) {
954 		fprintf(stderr, "%s: file not found: %s\n", program_name, file_name);
955 		exit(3);
956 	}
957 
958 	char *dir_name = strdup(file_name);
959 	int i = strlen(dir_name);
960 	while (i > 0) {
961 		if (dir_name[i - 1] == '/' || dir_name[i - 1] == '\\') {
962 			i--;
963 			break;
964 		}
965 		i--;
966 	}
967 	dir_name[i] = '\0';
968 	// FIXME: problem with this is that
969 	// LOUIS_TABLEPATH=$(top_srcdir)/tables,... does not work anymore because
970 	// $(top_srcdir) == .. (not an absolute path)
971 	if (i > 0)
972 		if (chdir(dir_name))
973 			error(EXIT_FAILURE, EIO, "Cannot change directory to %s", dir_name);
974 
975 	// register custom table resolver
976 	lou_registerTableResolver(&customTableResolver);
977 
978 	assert(yaml_parser_initialize(&parser));
979 
980 	yaml_parser_set_input_file(&parser, file);
981 
982 	if (!yaml_parser_parse(&parser, &event) || (event.type != YAML_STREAM_START_EVENT)) {
983 		yaml_error(YAML_STREAM_START_EVENT, &event);
984 	}
985 
986 	if (event.data.stream_start.encoding != YAML_UTF8_ENCODING)
987 		error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
988 				"UTF-8 encoding expected (actual %s)",
989 				encoding_names[event.data.stream_start.encoding]);
990 	yaml_event_delete(&event);
991 
992 	if (!yaml_parser_parse(&parser, &event) ||
993 			(event.type != YAML_DOCUMENT_START_EVENT)) {
994 		yaml_error(YAML_DOCUMENT_START_EVENT, &event);
995 	}
996 	yaml_event_delete(&event);
997 
998 	if (!yaml_parser_parse(&parser, &event) || (event.type != YAML_MAPPING_START_EVENT)) {
999 		yaml_error(YAML_MAPPING_START_EVENT, &event);
1000 	}
1001 	yaml_event_delete(&event);
1002 
1003 	if (!yaml_parser_parse(&parser, &event))
1004 		simple_error("table expected", &parser, &event);
1005 
1006 	int MAXTABLES = 150;
1007 	char *tables[MAXTABLES + 1];
1008 	char *display_table = NULL;
1009 	while (1) {
1010 		if (event.type == YAML_SCALAR_EVENT &&
1011 				!strcmp((const char *)event.data.scalar.value, "display")) {
1012 			table_value *v;
1013 			free(display_table);
1014 			v = read_table_value(&parser, event.start_mark.line + 1, 1);
1015 			display_table = strdup((char *)v->name);
1016 			if (v->content)
1017 				compile_inline_table(v);
1018 			else if (!_lou_getDisplayTable(display_table))
1019 				error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
1020 						"Display table %s not valid", display_table);
1021 			free_table_value(v);
1022 			yaml_event_delete(&event);
1023 			if (!yaml_parser_parse(&parser, &event))
1024 				simple_error("table expected", &parser, &event);
1025 		}
1026 		if (!(tables[0] = read_table(&event, &parser, display_table))) break;
1027 		yaml_event_delete(&event);
1028 		int k = 1;
1029 		while (1) {
1030 			if (!yaml_parser_parse(&parser, &event))
1031 				error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
1032 						"Expected table or %s (actual %s)",
1033 						event_names[YAML_SCALAR_EVENT], event_names[event.type]);
1034 			if ((tables[k++] = read_table(&event, &parser, display_table))) {
1035 				if (k == MAXTABLES)
1036 					error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
1037 							"Only %d tables in one YAML test supported", MAXTABLES);
1038 				yaml_event_delete(&event);
1039 			} else
1040 				break;
1041 		}
1042 
1043 		if (event.type != YAML_SCALAR_EVENT) yaml_error(YAML_SCALAR_EVENT, &event);
1044 
1045 		int haveRunTests = 0;
1046 		while (1) {
1047 			int testmode = MODE_DEFAULT;
1048 			if (!strcmp((const char *)event.data.scalar.value, "flags")) {
1049 				yaml_event_delete(&event);
1050 				read_flags(&parser, &testmode);
1051 
1052 				if (!yaml_parser_parse(&parser, &event) ||
1053 						(event.type != YAML_SCALAR_EVENT) ||
1054 						strcmp((const char *)event.data.scalar.value, "tests")) {
1055 					simple_error("tests expected", &parser, &event);
1056 				}
1057 				yaml_event_delete(&event);
1058 				read_tests(&parser, tables, display_table, testmode);
1059 				haveRunTests = 1;
1060 
1061 			} else if (!strcmp((const char *)event.data.scalar.value, "tests")) {
1062 				yaml_event_delete(&event);
1063 				read_tests(&parser, tables, display_table, testmode);
1064 				haveRunTests = 1;
1065 			} else {
1066 				if (haveRunTests) {
1067 					break;
1068 				} else {
1069 					simple_error("flags or tests expected", &parser, &event);
1070 				}
1071 			}
1072 			if (!yaml_parser_parse(&parser, &event))
1073 				error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1,
1074 						"Expected table, flags, tests or %s (actual %s)",
1075 						event_names[YAML_MAPPING_END_EVENT], event_names[event.type]);
1076 			if (event.type != YAML_SCALAR_EVENT) break;
1077 		}
1078 
1079 		char **p = tables;
1080 		while (*p) free(*(p++));
1081 	}
1082 	if (event.type != YAML_MAPPING_END_EVENT) yaml_error(YAML_MAPPING_END_EVENT, &event);
1083 	yaml_event_delete(&event);
1084 
1085 	if (!yaml_parser_parse(&parser, &event) || (event.type != YAML_DOCUMENT_END_EVENT)) {
1086 		yaml_error(YAML_DOCUMENT_END_EVENT, &event);
1087 	}
1088 	yaml_event_delete(&event);
1089 
1090 	if (!yaml_parser_parse(&parser, &event) || (event.type != YAML_STREAM_END_EVENT)) {
1091 		yaml_error(YAML_STREAM_END_EVENT, &event);
1092 	}
1093 	yaml_event_delete(&event);
1094 
1095 	yaml_parser_delete(&parser);
1096 
1097 	free(emph_classes);
1098 	free(display_table);
1099 	lou_free();
1100 
1101 	assert(!fclose(file));
1102 
1103 	printf("%s (%d tests, %d failure%s)\n", (errors ? "FAILURE" : "SUCCESS"), count,
1104 			errors, ((errors != 1) ? "s" : ""));
1105 
1106 	return errors ? 1 : 0;
1107 
1108 #endif  // HAVE_LIBYAML
1109 #endif  // not WITHOUT_YAML
1110 }
1111