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