| /* liblouis Braille Translation and Back-Translation Library |
| |
| Copyright (C) 2015, 2016 Christian Egli, Swiss Library for the Blind, Visually Impaired |
| and Print Disabled |
| Copyright (C) 2017 Bert Frees |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include <config.h> |
| #include <assert.h> |
| #include <getopt.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include "internal.h" |
| #include "error.h" |
| #include "errno.h" |
| #include "progname.h" |
| #include "version-etc.h" |
| #include "brl_checks.h" |
| |
| static int verbose = 0; |
| static const struct option longopts[] = { |
| { "help", no_argument, NULL, 'h' }, |
| { "version", no_argument, NULL, 'v' }, |
| { "verbose", no_argument, &verbose, 1 }, |
| { NULL, 0, NULL, 0 }, |
| }; |
| |
| const char version_etc_copyright[] = |
| "Copyright %s %d Swiss Library for the Blind, Visually Impaired and Print " |
| "Disabled"; |
| |
| #define AUTHORS "Christian Egli" |
| |
| #define DIRECTION_FORWARD 0 |
| #define DIRECTION_BACKWARD 1 |
| #define DIRECTION_BOTH 2 |
| #define DIRECTION_DEFAULT DIRECTION_FORWARD |
| |
| #define HYPHENATION_OFF 0 |
| #define HYPHENATION_ON 1 |
| #define HYPHENATION_BRAILLE 2 |
| #define HYPHENATION_DEFAULT HYPHENATION_OFF |
| |
| static void |
| print_help(void) { |
| printf("\ |
| Usage: %s YAML_TEST_FILE\n", |
| program_name); |
| |
| fputs("\ |
| Run the tests defined in the YAML_TEST_FILE. Return 0 if all tests pass\n\ |
| or 1 if any of the tests fail. The details of failing tests are printed\n\ |
| to stderr.\n\n", |
| stdout); |
| |
| fputs("\ |
| -h, --help display this help and exit\n\ |
| -v, --version display version information and exit\n\ |
| --verbose report expected failures\n", |
| stdout); |
| |
| printf("\n"); |
| printf("Report bugs to %s.\n", PACKAGE_BUGREPORT); |
| |
| #ifdef PACKAGE_PACKAGER_BUG_REPORTS |
| printf("Report %s bugs to: %s\n", PACKAGE_PACKAGER, PACKAGE_PACKAGER_BUG_REPORTS); |
| #endif |
| #ifdef PACKAGE_URL |
| printf("%s home page: <%s>\n", PACKAGE_NAME, PACKAGE_URL); |
| #endif |
| } |
| |
| #define EXIT_SKIPPED 77 |
| |
| #ifdef HAVE_LIBYAML |
| #include <yaml.h> |
| |
| typedef struct { |
| const char *name; |
| const char *content; // table content in case of an inline table; NULL means name is |
| // a file |
| int location; // location in YAML file (line number) where table is defined |
| int is_display; // whether the table is a display table or a translation table |
| } table_value; |
| |
| const char *event_names[] = { "YAML_NO_EVENT", "YAML_STREAM_START_EVENT", |
| "YAML_STREAM_END_EVENT", "YAML_DOCUMENT_START_EVENT", "YAML_DOCUMENT_END_EVENT", |
| "YAML_ALIAS_EVENT", "YAML_SCALAR_EVENT", "YAML_SEQUENCE_START_EVENT", |
| "YAML_SEQUENCE_END_EVENT", "YAML_MAPPING_START_EVENT", "YAML_MAPPING_END_EVENT" }; |
| const char *encoding_names[] = { "YAML_ANY_ENCODING", "YAML_UTF8_ENCODING", |
| "YAML_UTF16LE_ENCODING", "YAML_UTF16BE_ENCODING" }; |
| |
| const char *inline_table_prefix = "checkyaml_inline_table_at_line_"; |
| |
| char *file_name; |
| |
| int errors = 0; |
| int count = 0; |
| |
| static char const **emph_classes = NULL; |
| |
| static void |
| simple_error(const char *msg, yaml_parser_t *parser, yaml_event_t *event) { |
| error_at_line(EXIT_FAILURE, 0, file_name, |
| event->start_mark.line ? event->start_mark.line + 1 |
| : parser->problem_mark.line + 1, |
| "%s", msg); |
| } |
| |
| static void |
| yaml_parse_error(yaml_parser_t *parser) { |
| error_at_line(EXIT_FAILURE, 0, file_name, parser->problem_mark.line + 1, "%s", |
| parser->problem); |
| } |
| |
| static void |
| yaml_error(yaml_event_type_t expected, yaml_event_t *event) { |
| error_at_line(EXIT_FAILURE, 0, file_name, event->start_mark.line + 1, |
| "Expected %s (actual %s)", event_names[expected], event_names[event->type]); |
| } |
| |
| static char * |
| read_table_query(yaml_parser_t *parser, const char **table_file_name_check) { |
| yaml_event_t event; |
| char *query_as_string = malloc(sizeof(char) * MAXSTRING); |
| char *p = query_as_string; |
| query_as_string[0] = '\0'; |
| while (1) { |
| if (!yaml_parser_parse(parser, &event)) yaml_error(YAML_SCALAR_EVENT, &event); |
| if (event.type == YAML_SCALAR_EVENT) { |
| |
| // (temporary) feature to check whether the table query matches an expected |
| // table |
| if (!strcmp((const char *)event.data.scalar.value, "__assert-match")) { |
| yaml_event_delete(&event); |
| if (!yaml_parser_parse(parser, &event) || |
| (event.type != YAML_SCALAR_EVENT)) |
| yaml_error(YAML_SCALAR_EVENT, &event); |
| *table_file_name_check = strdup((const char *)event.data.scalar.value); |
| yaml_event_delete(&event); |
| } else { |
| if (query_as_string != p) strcat(p++, " "); |
| strcat(p, (const char *)event.data.scalar.value); |
| p += event.data.scalar.length; |
| strcat(p++, ":"); |
| yaml_event_delete(&event); |
| if (!yaml_parser_parse(parser, &event) || |
| (event.type != YAML_SCALAR_EVENT)) |
| yaml_error(YAML_SCALAR_EVENT, &event); |
| strcat(p, (const char *)event.data.scalar.value); |
| p += event.data.scalar.length; |
| yaml_event_delete(&event); |
| } |
| } else if (event.type == YAML_MAPPING_END_EVENT) { |
| yaml_event_delete(&event); |
| break; |
| } else |
| yaml_error(YAML_SCALAR_EVENT, &event); |
| } |
| return query_as_string; |
| } |
| |
| static void |
| compile_inline_table(const table_value *table) { |
| int location = table->location; |
| char *p = (char *)table->content; |
| char *line_start = p; |
| int line_len = 0; |
| while (*p) { |
| if (*p == 10 || *p == 13) { |
| char *line = strndup((const char *)line_start, line_len); |
| int error = 0; |
| if (!table->is_display) |
| error = !_lou_compileTranslationRule(table->name, line); |
| else |
| error = !_lou_compileDisplayRule(table->name, line); |
| if (error) |
| error_at_line(EXIT_FAILURE, 0, file_name, location, "Error in table %s", |
| table->name); |
| location++; |
| free(line); |
| line_start = p + 1; |
| line_len = 0; |
| } else { |
| line_len++; |
| } |
| p++; |
| } |
| } |
| |
| static table_value * |
| read_table_value(yaml_parser_t *parser, int start_line, int is_display) { |
| table_value *table; |
| char *table_name = malloc(sizeof(char) * MAXSTRING); |
| char *table_content = NULL; |
| yaml_event_t event; |
| table_name[0] = '\0'; |
| if (!yaml_parser_parse(parser, &event) || |
| !(event.type == YAML_SEQUENCE_START_EVENT || |
| event.type == YAML_SCALAR_EVENT || |
| event.type == YAML_MAPPING_START_EVENT)) |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "Expected %s, %s or %s (actual %s)", |
| event_names[YAML_SEQUENCE_START_EVENT], event_names[YAML_SCALAR_EVENT], |
| event_names[YAML_MAPPING_START_EVENT], event_names[event.type]); |
| if (event.type == YAML_SEQUENCE_START_EVENT) { |
| yaml_event_delete(&event); |
| int done = 0; |
| char *p = table_name; |
| while (!done) { |
| if (!yaml_parser_parse(parser, &event)) { |
| yaml_parse_error(parser); |
| } |
| if (event.type == YAML_SEQUENCE_END_EVENT) { |
| done = 1; |
| } else if (event.type == YAML_SCALAR_EVENT) { |
| if (table_name != p) strcat(p++, ","); |
| strcat(p, (const char *)event.data.scalar.value); |
| p += event.data.scalar.length; |
| } |
| yaml_event_delete(&event); |
| } |
| } else if (event.type == YAML_SCALAR_EVENT) { |
| yaml_char_t *p = event.data.scalar.value; |
| if (*p) |
| while (p[1]) p++; |
| if (*p == 10 || *p == 13) { |
| // If the scalar ends with a newline, assume it is a block |
| // scalar, so treat as an inline table. (Is there a proper way |
| // to check for a block scalar?) |
| sprintf(table_name, "%s%d", inline_table_prefix, start_line); |
| table_content = strdup((const char *)event.data.scalar.value); |
| } else { |
| strcat(table_name, (const char *)event.data.scalar.value); |
| } |
| yaml_event_delete(&event); |
| } else { // event.type == YAML_MAPPING_START_EVENT |
| char *query; |
| const char *table_file_name_check = NULL; |
| yaml_event_delete(&event); |
| query = read_table_query(parser, &table_file_name_check); |
| table_name = lou_findTable(query); |
| free(query); |
| if (!table_name) |
| error_at_line(EXIT_FAILURE, 0, file_name, start_line, |
| "Table query did not match a table"); |
| if (table_file_name_check) { |
| const char *table_file_name = table_name; |
| do { |
| table_file_name++; |
| } while (*table_file_name); |
| while (table_file_name >= table_name && *table_file_name != '/' && |
| *table_file_name != '\\') |
| table_file_name--; |
| if (strcmp(table_file_name_check, table_file_name + 1)) |
| error_at_line(EXIT_FAILURE, 0, file_name, start_line, |
| "Table query did not match expected table: expected '%s' but got " |
| "'%s'", |
| table_file_name_check, table_file_name + 1); |
| } |
| } |
| table = malloc(sizeof(table_value)); |
| table->name = table_name; |
| table->content = table_content; |
| table->location = start_line + 1; |
| table->is_display = is_display; |
| return table; |
| } |
| |
| static void |
| free_table_value(table_value *table) { |
| if (table) { |
| free((char *)table->name); |
| if (table->content) free((char *)table->content); |
| free(table); |
| } |
| } |
| |
| static char * |
| read_table(yaml_event_t *start_event, yaml_parser_t *parser, const char *display_table) { |
| table_value *v = NULL; |
| char *table_name = NULL; |
| if (start_event->type != YAML_SCALAR_EVENT || |
| strcmp((const char *)start_event->data.scalar.value, "table")) |
| return 0; |
| v = read_table_value(parser, start_event->start_mark.line + 1, 0); |
| if (!_lou_getTranslationTable(v->name)) |
| error_at_line(EXIT_FAILURE, 0, file_name, start_event->start_mark.line + 1, |
| "Table %s not valid", v->name); |
| if (v->content) compile_inline_table(v); |
| emph_classes = lou_getEmphClasses(v->name); // get declared emphasis classes |
| table_name = strdup((char *)v->name); |
| if (!display_table) { |
| if (!_lou_getDisplayTable(v->name)) |
| error_at_line(EXIT_FAILURE, 0, file_name, start_event->start_mark.line + 1, |
| "Table %s not valid", v->name); |
| if (v->content) { |
| v->is_display = 1; |
| compile_inline_table(v); |
| } |
| } |
| free_table_value(v); |
| return table_name; |
| } |
| |
| static void |
| read_flags(yaml_parser_t *parser, int *direction, int *hyphenation) { |
| yaml_event_t event; |
| int parse_error = 1; |
| |
| *direction = DIRECTION_DEFAULT; |
| *hyphenation = HYPHENATION_DEFAULT; |
| |
| if (!yaml_parser_parse(parser, &event) || (event.type != YAML_MAPPING_START_EVENT)) |
| yaml_error(YAML_MAPPING_START_EVENT, &event); |
| |
| yaml_event_delete(&event); |
| |
| while ((parse_error = yaml_parser_parse(parser, &event)) && |
| (event.type == YAML_SCALAR_EVENT)) { |
| if (!strcmp((const char *)event.data.scalar.value, "testmode")) { |
| yaml_event_delete(&event); |
| if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT)) |
| yaml_error(YAML_SCALAR_EVENT, &event); |
| if (!strcmp((const char *)event.data.scalar.value, "forward")) { |
| *direction = DIRECTION_FORWARD; |
| } else if (!strcmp((const char *)event.data.scalar.value, "backward")) { |
| *direction = DIRECTION_BACKWARD; |
| } else if (!strcmp((const char *)event.data.scalar.value, "bothDirections")) { |
| *direction = DIRECTION_BOTH; |
| } else if (!strcmp((const char *)event.data.scalar.value, "hyphenate")) { |
| *hyphenation = HYPHENATION_ON; |
| } else if (!strcmp((const char *)event.data.scalar.value, |
| "hyphenateBraille")) { |
| *hyphenation = HYPHENATION_BRAILLE; |
| } else { |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "Testmode '%s' not supported\n", event.data.scalar.value); |
| } |
| } else { |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "Flag '%s' not supported\n", event.data.scalar.value); |
| } |
| } |
| if (!parse_error) yaml_parse_error(parser); |
| if (event.type != YAML_MAPPING_END_EVENT) yaml_error(YAML_MAPPING_END_EVENT, &event); |
| yaml_event_delete(&event); |
| } |
| |
| static int |
| read_xfail(yaml_parser_t *parser) { |
| yaml_event_t event; |
| /* assume xfail true if there is an xfail key */ |
| int xfail = 1; |
| if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT)) |
| yaml_error(YAML_SCALAR_EVENT, &event); |
| if (!strcmp((const char *)event.data.scalar.value, "false") || |
| !strcmp((const char *)event.data.scalar.value, "off")) |
| xfail = 0; |
| yaml_event_delete(&event); |
| return xfail; |
| } |
| |
| static translationModes |
| read_mode(yaml_parser_t *parser) { |
| yaml_event_t event; |
| translationModes mode = 0; |
| int parse_error = 1; |
| |
| if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SEQUENCE_START_EVENT)) |
| yaml_error(YAML_SEQUENCE_START_EVENT, &event); |
| yaml_event_delete(&event); |
| |
| while ((parse_error = yaml_parser_parse(parser, &event)) && |
| (event.type == YAML_SCALAR_EVENT)) { |
| if (!strcmp((const char *)event.data.scalar.value, "noContractions")) { |
| mode |= noContractions; |
| } else if (!strcmp((const char *)event.data.scalar.value, "compbrlAtCursor")) { |
| mode |= compbrlAtCursor; |
| } else if (!strcmp((const char *)event.data.scalar.value, "dotsIO")) { |
| mode |= dotsIO; |
| } else if (!strcmp((const char *)event.data.scalar.value, "compbrlLeftCursor")) { |
| mode |= compbrlLeftCursor; |
| } else if (!strcmp((const char *)event.data.scalar.value, "ucBrl")) { |
| mode |= ucBrl; |
| } else if (!strcmp((const char *)event.data.scalar.value, "noUndefined")) { |
| mode |= noUndefined; |
| } else if (!strcmp((const char *)event.data.scalar.value, "partialTrans")) { |
| mode |= partialTrans; |
| } else { |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "Mode '%s' not supported\n", event.data.scalar.value); |
| } |
| yaml_event_delete(&event); |
| } |
| if (!parse_error) yaml_parse_error(parser); |
| if (event.type != YAML_SEQUENCE_END_EVENT) |
| yaml_error(YAML_SEQUENCE_END_EVENT, &event); |
| yaml_event_delete(&event); |
| return mode; |
| } |
| |
| static int |
| parse_number(const char *number, const char *name, int file_line) { |
| char *tail; |
| errno = 0; |
| |
| int val = strtol(number, &tail, 0); |
| if (errno != 0) |
| error_at_line(EXIT_FAILURE, 0, file_name, file_line, |
| "Not a valid %s '%s'. Must be a number\n", name, number); |
| if (number == tail) |
| error_at_line(EXIT_FAILURE, 0, file_name, file_line, |
| "No digits found in %s '%s'. Must be a number\n", name, number); |
| return val; |
| } |
| |
| static int * |
| read_inPos(yaml_parser_t *parser, int translen) { |
| int *pos = malloc(sizeof(int) * translen); |
| int i = 0; |
| yaml_event_t event; |
| int parse_error = 1; |
| |
| if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SEQUENCE_START_EVENT)) |
| yaml_error(YAML_SEQUENCE_START_EVENT, &event); |
| yaml_event_delete(&event); |
| |
| while ((parse_error = yaml_parser_parse(parser, &event)) && |
| (event.type == YAML_SCALAR_EVENT)) { |
| if (i >= translen) |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "Too many input positions for translation of length %d.", translen); |
| |
| pos[i++] = parse_number((const char *)event.data.scalar.value, "input position", |
| event.start_mark.line + 1); |
| yaml_event_delete(&event); |
| } |
| if (i < translen) |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "Too few input positions (%i) for translation of length %i\n", i, |
| translen); |
| if (!parse_error) yaml_parse_error(parser); |
| if (event.type != YAML_SEQUENCE_END_EVENT) |
| yaml_error(YAML_SEQUENCE_END_EVENT, &event); |
| yaml_event_delete(&event); |
| return pos; |
| } |
| |
| static int * |
| read_outPos(yaml_parser_t *parser, int wrdlen, int translen) { |
| int *pos = malloc(sizeof(int) * wrdlen); |
| int i = 0; |
| yaml_event_t event; |
| int parse_error = 1; |
| |
| if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SEQUENCE_START_EVENT)) |
| yaml_error(YAML_SEQUENCE_START_EVENT, &event); |
| yaml_event_delete(&event); |
| |
| while ((parse_error = yaml_parser_parse(parser, &event)) && |
| (event.type == YAML_SCALAR_EVENT)) { |
| if (i >= wrdlen) |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "Too many output positions for input string of length %d.", translen); |
| |
| pos[i++] = parse_number((const char *)event.data.scalar.value, "output position", |
| event.start_mark.line + 1); |
| yaml_event_delete(&event); |
| } |
| if (i < wrdlen) |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "Too few output positions (%i) for input string of length %i\n", i, |
| wrdlen); |
| if (!parse_error) yaml_parse_error(parser); |
| if (event.type != YAML_SEQUENCE_END_EVENT) |
| yaml_error(YAML_SEQUENCE_END_EVENT, &event); |
| yaml_event_delete(&event); |
| return pos; |
| } |
| |
| static void |
| read_cursorPos(yaml_parser_t *parser, int *cursorPos, int *expected_cursorPos, int wrdlen, |
| int translen) { |
| yaml_event_t event; |
| |
| if (!yaml_parser_parse(parser, &event) || |
| !(event.type == YAML_SEQUENCE_START_EVENT || event.type == YAML_SCALAR_EVENT)) |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "Expected %s or %s (actual %s)", event_names[YAML_SEQUENCE_START_EVENT], |
| event_names[YAML_SCALAR_EVENT], event_names[event.type]); |
| |
| if (event.type == YAML_SEQUENCE_START_EVENT) { |
| /* it's a sequence: read the two cursor positions (before and after) */ |
| yaml_event_delete(&event); |
| if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT)) |
| yaml_error(YAML_SCALAR_EVENT, &event); |
| *cursorPos = parse_number((const char *)event.data.scalar.value, |
| "cursor position", event.start_mark.line + 1); |
| if ((0 > *cursorPos) || (*cursorPos >= wrdlen)) |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "Cursor position (%i) outside of input string of length %i\n", |
| *cursorPos, wrdlen); |
| yaml_event_delete(&event); |
| if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT)) |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "Too few cursor positions, 2 are expected (before and after)\n"); |
| *expected_cursorPos = parse_number((const char *)event.data.scalar.value, |
| "expected cursor position", event.start_mark.line + 1); |
| if ((0 > *expected_cursorPos) || (*expected_cursorPos >= wrdlen)) |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "Expected cursor position (%i) outside of output string of length " |
| "%i\n", |
| *expected_cursorPos, translen); |
| yaml_event_delete(&event); |
| if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SEQUENCE_END_EVENT)) |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "Too many cursor positions, only 2 are expected (before and " |
| "after)\n"); |
| yaml_event_delete(&event); |
| } else { // YAML_SCALAR_EVENT |
| /* it's just a single value: just read the initial cursor position */ |
| *cursorPos = parse_number((const char *)event.data.scalar.value, |
| "cursor position before", event.start_mark.line + 1); |
| if ((0 > *cursorPos) || (*cursorPos >= wrdlen)) |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "Cursor position (%i) outside of input string of length %i\n", |
| *cursorPos, wrdlen); |
| *expected_cursorPos = -1; |
| yaml_event_delete(&event); |
| } |
| } |
| |
| static void |
| read_typeform_string(yaml_parser_t *parser, formtype *typeform, typeforms kind, int len) { |
| yaml_event_t event; |
| int typeform_len; |
| |
| if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT)) |
| yaml_error(YAML_SCALAR_EVENT, &event); |
| typeform_len = strlen((const char *)event.data.scalar.value); |
| if (typeform_len != len) |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "Too many or too typeforms (%i) for word of length %i\n", typeform_len, |
| len); |
| update_typeform((const char *)event.data.scalar.value, typeform, kind); |
| yaml_event_delete(&event); |
| } |
| |
| static formtype * |
| read_typeforms(yaml_parser_t *parser, int len) { |
| yaml_event_t event; |
| formtype *typeform = calloc(len, sizeof(formtype)); |
| int parse_error = 1; |
| |
| if (!yaml_parser_parse(parser, &event) || (event.type != YAML_MAPPING_START_EVENT)) |
| yaml_error(YAML_MAPPING_START_EVENT, &event); |
| yaml_event_delete(&event); |
| |
| while ((parse_error = yaml_parser_parse(parser, &event)) && |
| (event.type == YAML_SCALAR_EVENT)) { |
| if (strcmp((const char *)event.data.scalar.value, "computer_braille") == 0) { |
| yaml_event_delete(&event); |
| read_typeform_string(parser, typeform, computer_braille, len); |
| } else if (strcmp((const char *)event.data.scalar.value, "no_translate") == 0) { |
| yaml_event_delete(&event); |
| read_typeform_string(parser, typeform, no_translate, len); |
| } else if (strcmp((const char *)event.data.scalar.value, "no_contract") == 0) { |
| yaml_event_delete(&event); |
| read_typeform_string(parser, typeform, no_contract, len); |
| } else { |
| int i; |
| typeforms kind = plain_text; |
| for (i = 0; emph_classes[i]; i++) { |
| if (strcmp((const char *)event.data.scalar.value, emph_classes[i]) == 0) { |
| yaml_event_delete(&event); |
| kind = italic << i; |
| if (kind > emph_10) |
| error_at_line(EXIT_FAILURE, 0, file_name, |
| event.start_mark.line + 1, |
| "Typeform '%s' was not declared\n", |
| event.data.scalar.value); |
| read_typeform_string(parser, typeform, kind, len); |
| break; |
| } |
| } |
| } |
| } |
| if (!parse_error) yaml_parse_error(parser); |
| |
| if (event.type != YAML_MAPPING_END_EVENT) yaml_error(YAML_MAPPING_END_EVENT, &event); |
| yaml_event_delete(&event); |
| return typeform; |
| } |
| |
| static void |
| read_options(yaml_parser_t *parser, int direction, int wordLen, int translationLen, |
| int *xfail, translationModes *mode, formtype **typeform, int **inPos, |
| int **outPos, int *cursorPos, int *cursorOutPos, int *maxOutputLen, |
| int *realInputLen) { |
| yaml_event_t event; |
| char *option_name; |
| int parse_error = 1; |
| |
| *mode = 0; |
| *xfail = 0; |
| *typeform = NULL; |
| *inPos = NULL; |
| *outPos = NULL; |
| |
| while ((parse_error = yaml_parser_parse(parser, &event)) && |
| (event.type == YAML_SCALAR_EVENT)) { |
| option_name = |
| strndup((const char *)event.data.scalar.value, event.data.scalar.length); |
| |
| if (!strcmp(option_name, "xfail")) { |
| yaml_event_delete(&event); |
| *xfail = read_xfail(parser); |
| } else if (!strcmp(option_name, "mode")) { |
| yaml_event_delete(&event); |
| *mode = read_mode(parser); |
| } else if (!strcmp(option_name, "typeform")) { |
| if (direction != 0) { |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "typeforms only supported with testmode 'forward'\n"); |
| } |
| yaml_event_delete(&event); |
| *typeform = read_typeforms(parser, wordLen); |
| } else if (!strcmp(option_name, "inputPos")) { |
| yaml_event_delete(&event); |
| *inPos = read_inPos(parser, translationLen); |
| } else if (!strcmp(option_name, "outputPos")) { |
| yaml_event_delete(&event); |
| *outPos = read_outPos(parser, wordLen, translationLen); |
| } else if (!strcmp(option_name, "cursorPos")) { |
| if (direction == 2) { |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "cursorPos not supported with testmode 'bothDirections'\n"); |
| } |
| yaml_event_delete(&event); |
| read_cursorPos(parser, cursorPos, cursorOutPos, wordLen, translationLen); |
| } else if (!strcmp(option_name, "maxOutputLength")) { |
| if (direction == 2) { |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "maxOutputLength not supported with testmode 'bothDirections'\n"); |
| } |
| yaml_event_delete(&event); |
| if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT)) |
| yaml_error(YAML_SCALAR_EVENT, &event); |
| *maxOutputLen = parse_number((const char *)event.data.scalar.value, |
| "Maximum output length", event.start_mark.line + 1); |
| if (*maxOutputLen <= 0) |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "Maximum output length (%i) must be a positive number\n", |
| *maxOutputLen); |
| if (*maxOutputLen < translationLen) |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "Expected translation length (%i) must not exceed maximum output " |
| "length (%i)\n", |
| translationLen, *maxOutputLen); |
| yaml_event_delete(&event); |
| } else if (!strcmp(option_name, "realInputLength")) { |
| yaml_event_delete(&event); |
| if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT)) |
| yaml_error(YAML_SCALAR_EVENT, &event); |
| *realInputLen = parse_number((const char *)event.data.scalar.value, |
| "Real input length", event.start_mark.line + 1); |
| if (*realInputLen < 0) |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "Real input length (%i) must not be a negative number\n", |
| *realInputLen); |
| if (*realInputLen > wordLen) |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "Real input length (%i) must not exceed total input " |
| "length (%i)\n", |
| *realInputLen, wordLen); |
| yaml_event_delete(&event); |
| } else { |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "Unsupported option %s", option_name); |
| } |
| free(option_name); |
| } |
| if (!parse_error) yaml_parse_error(parser); |
| if (event.type != YAML_MAPPING_END_EVENT) yaml_error(YAML_MAPPING_END_EVENT, &event); |
| yaml_event_delete(&event); |
| } |
| |
| /* see http://stackoverflow.com/questions/5117393/utf-8-strings-length-in-linux-c */ |
| static int |
| my_strlen_utf8_c(char *s) { |
| int i = 0, j = 0; |
| while (s[i]) { |
| if ((s[i] & 0xc0) != 0x80) j++; |
| i++; |
| } |
| return j; |
| } |
| |
| /* |
| * String parsing is also done later in check_base. At this point we |
| * only need it to compute the actual string length in order to be |
| * able to provide error messages when parsing typeform and position arrays. |
| */ |
| static int |
| parsed_strlen(char *s) { |
| widechar *buf; |
| int len, maxlen; |
| maxlen = my_strlen_utf8_c(s); |
| buf = malloc(sizeof(widechar) * maxlen); |
| len = _lou_extParseChars(s, buf); |
| free(buf); |
| return len; |
| } |
| |
| static void |
| read_test(yaml_parser_t *parser, char **tables, const char *display_table, int direction, |
| int hyphenation) { |
| yaml_event_t event; |
| char *description = NULL; |
| char *word; |
| char *translation; |
| int xfail = 0; |
| translationModes mode = 0; |
| formtype *typeform = NULL; |
| int *inPos = NULL; |
| int *outPos = NULL; |
| int cursorPos = -1; |
| int cursorOutPos = -1; |
| int maxOutputLen = -1; |
| int realInputLen = -1; |
| |
| if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT)) |
| simple_error("Word expected", parser, &event); |
| |
| word = strndup((const char *)event.data.scalar.value, event.data.scalar.length); |
| yaml_event_delete(&event); |
| |
| if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SCALAR_EVENT)) |
| simple_error("Translation expected", parser, &event); |
| |
| translation = |
| strndup((const char *)event.data.scalar.value, event.data.scalar.length); |
| yaml_event_delete(&event); |
| |
| if (!yaml_parser_parse(parser, &event)) yaml_parse_error(parser); |
| |
| /* Handle an optional description */ |
| if (event.type == YAML_SCALAR_EVENT) { |
| description = word; |
| word = translation; |
| translation = |
| strndup((const char *)event.data.scalar.value, event.data.scalar.length); |
| yaml_event_delete(&event); |
| |
| if (!yaml_parser_parse(parser, &event)) yaml_parse_error(parser); |
| } |
| |
| if (event.type == YAML_MAPPING_START_EVENT) { |
| yaml_event_delete(&event); |
| read_options(parser, direction, parsed_strlen(word), parsed_strlen(translation), |
| &xfail, &mode, &typeform, &inPos, &outPos, &cursorPos, &cursorOutPos, |
| &maxOutputLen, &realInputLen); |
| |
| if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SEQUENCE_END_EVENT)) |
| yaml_error(YAML_SEQUENCE_END_EVENT, &event); |
| } else if (event.type != YAML_SEQUENCE_END_EVENT) { |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "Expected %s or %s (actual %s)", event_names[YAML_MAPPING_START_EVENT], |
| event_names[YAML_SEQUENCE_END_EVENT], event_names[event.type]); |
| } |
| |
| int result = 0; |
| char **table = tables; |
| while (*table) { |
| int r; |
| if (hyphenation == HYPHENATION_ON || hyphenation == HYPHENATION_BRAILLE) { |
| r = check_hyphenation( |
| *table, word, translation, hyphenation == HYPHENATION_BRAILLE); |
| } else { |
| // FIXME: Note that the typeform array was constructed using the |
| // emphasis classes mapping of the last compiled table. This |
| // means that if we are testing multiple tables at the same time |
| // they must have the same mapping (i.e. the emphasis classes |
| // must be defined in the same order). |
| r = check(*table, word, translation, .display_table = display_table, |
| .typeform = typeform, .mode = mode, .expected_inputPos = inPos, |
| .expected_outputPos = outPos, .cursorPos = cursorPos, |
| .expected_cursorPos = cursorOutPos, .max_outlen = maxOutputLen, |
| .real_inlen = realInputLen, .direction = direction, |
| .diagnostics = !xfail); |
| } |
| if (xfail != r) { |
| // FAIL or XPASS |
| if (description) fprintf(stderr, "%s\n", description); |
| error_at_line(0, 0, file_name, event.start_mark.line + 1, |
| (xfail ? "Unexpected Pass" : "Failure")); |
| errors++; |
| // on error print the table name, as it isn't always clear |
| // which table we are testing. You can can define a test |
| // for multiple tables. |
| fprintf(stderr, "Table: %s\n", *table); |
| if (display_table) fprintf(stderr, "Display table: %s\n", display_table); |
| // add an empty line after each error |
| fprintf(stderr, "\n"); |
| } else if (xfail && r && verbose) { |
| // XFAIL |
| // in verbose mode print expected failures |
| if (description) fprintf(stderr, "%s\n", description); |
| error_at_line(0, 0, file_name, event.start_mark.line + 1, "Expected Failure"); |
| fprintf(stderr, "Table: %s\n", *table); |
| if (display_table) fprintf(stderr, "Display table: %s\n", display_table); |
| fprintf(stderr, "\n"); |
| } |
| result |= r; |
| table++; |
| count++; |
| } |
| yaml_event_delete(&event); |
| |
| free(description); |
| free(word); |
| free(translation); |
| free(typeform); |
| free(inPos); |
| free(outPos); |
| } |
| |
| static void |
| read_tests(yaml_parser_t *parser, char **tables, const char *display_table, int direction, |
| int hyphenation) { |
| yaml_event_t event; |
| if (!yaml_parser_parse(parser, &event) || (event.type != YAML_SEQUENCE_START_EVENT)) |
| yaml_error(YAML_SEQUENCE_START_EVENT, &event); |
| |
| yaml_event_delete(&event); |
| |
| int done = 0; |
| while (!done) { |
| if (!yaml_parser_parse(parser, &event)) { |
| yaml_parse_error(parser); |
| } |
| if (event.type == YAML_SEQUENCE_END_EVENT) { |
| done = 1; |
| yaml_event_delete(&event); |
| } else if (event.type == YAML_SEQUENCE_START_EVENT) { |
| yaml_event_delete(&event); |
| read_test(parser, tables, display_table, direction, hyphenation); |
| } else { |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "Expected %s or %s (actual %s)", event_names[YAML_SEQUENCE_END_EVENT], |
| event_names[YAML_SEQUENCE_START_EVENT], event_names[event.type]); |
| } |
| } |
| } |
| |
| /* |
| * This custom table resolver handles magic table names that represent |
| * inline tables. |
| */ |
| static char ** |
| customTableResolver(const char *tableList, const char *base) { |
| static char *dummy_table[1]; |
| char *p = (char *)tableList; |
| while (*p != '\0') { |
| if (strncmp(p, inline_table_prefix, strlen(inline_table_prefix)) == 0) |
| return dummy_table; |
| while (*p != '\0' && *p != ',') p++; |
| if (*p == ',') p++; |
| } |
| return _lou_defaultTableResolver(tableList, base); |
| } |
| |
| #endif // HAVE_LIBYAML |
| |
| int |
| main(int argc, char *argv[]) { |
| int optc; |
| |
| set_program_name(argv[0]); |
| |
| while ((optc = getopt_long(argc, argv, "hv", longopts, NULL)) != -1) switch (optc) { |
| /* --help and --version exit immediately, per GNU coding standards. */ |
| case 'v': |
| version_etc( |
| stdout, program_name, PACKAGE_NAME, VERSION, AUTHORS, (char *)NULL); |
| exit(EXIT_SUCCESS); |
| break; |
| case 'h': |
| print_help(); |
| exit(EXIT_SUCCESS); |
| break; |
| default: |
| fprintf(stderr, "Try `%s --help' for more information.\n", program_name); |
| exit(EXIT_FAILURE); |
| break; |
| } |
| |
| if (optind != argc - 1) { |
| /* Print error message and exit. */ |
| if (optind < argc - 1) |
| fprintf(stderr, "%s: extra operand: %s\n", program_name, argv[optind + 1]); |
| else |
| fprintf(stderr, "%s: no YAML test file specified\n", program_name); |
| |
| fprintf(stderr, "Try `%s --help' for more information.\n", program_name); |
| exit(EXIT_FAILURE); |
| } |
| |
| #ifdef WITHOUT_YAML |
| fprintf(stderr, |
| "Skipping tests for %s as yaml was disabled in configure with " |
| "--without-yaml\n", |
| argv[1]); |
| return EXIT_SKIPPED; |
| #else |
| #ifndef HAVE_LIBYAML |
| fprintf(stderr, "Skipping tests for %s as libyaml was not found\n", argv[1]); |
| return EXIT_SKIPPED; |
| #endif // not HAVE_LIBYAML |
| #endif // WITHOUT_YAML |
| |
| #ifndef WITHOUT_YAML |
| #ifdef HAVE_LIBYAML |
| |
| FILE *file; |
| yaml_parser_t parser; |
| yaml_event_t event; |
| |
| file_name = argv[1]; |
| file = fopen(file_name, "rb"); |
| if (!file) { |
| fprintf(stderr, "%s: file not found: %s\n", program_name, file_name); |
| exit(3); |
| } |
| |
| char *dir_name = strdup(file_name); |
| int i = strlen(dir_name); |
| while (i > 0) { |
| if (dir_name[i - 1] == '/' || dir_name[i - 1] == '\\') { |
| i--; |
| break; |
| } |
| i--; |
| } |
| dir_name[i] = '\0'; |
| // FIXME: problem with this is that |
| // LOUIS_TABLEPATH=$(top_srcdir)/tables,... does not work anymore because |
| // $(top_srcdir) == .. (not an absolute path) |
| if (i > 0) |
| if (chdir(dir_name)) |
| error(EXIT_FAILURE, EIO, "Cannot change directory to %s", dir_name); |
| |
| // register custom table resolver |
| lou_registerTableResolver(&customTableResolver); |
| |
| assert(yaml_parser_initialize(&parser)); |
| |
| yaml_parser_set_input_file(&parser, file); |
| |
| if (!yaml_parser_parse(&parser, &event) || (event.type != YAML_STREAM_START_EVENT)) { |
| yaml_error(YAML_STREAM_START_EVENT, &event); |
| } |
| |
| if (event.data.stream_start.encoding != YAML_UTF8_ENCODING) |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "UTF-8 encoding expected (actual %s)", |
| encoding_names[event.data.stream_start.encoding]); |
| yaml_event_delete(&event); |
| |
| if (!yaml_parser_parse(&parser, &event) || |
| (event.type != YAML_DOCUMENT_START_EVENT)) { |
| yaml_error(YAML_DOCUMENT_START_EVENT, &event); |
| } |
| yaml_event_delete(&event); |
| |
| if (!yaml_parser_parse(&parser, &event) || (event.type != YAML_MAPPING_START_EVENT)) { |
| yaml_error(YAML_MAPPING_START_EVENT, &event); |
| } |
| yaml_event_delete(&event); |
| |
| if (!yaml_parser_parse(&parser, &event)) |
| simple_error("table expected", &parser, &event); |
| |
| int MAXTABLES = 150; |
| char *tables[MAXTABLES + 1]; |
| char *display_table = NULL; |
| while (1) { |
| if (event.type == YAML_SCALAR_EVENT && |
| !strcmp((const char *)event.data.scalar.value, "display")) { |
| table_value *v; |
| free(display_table); |
| v = read_table_value(&parser, event.start_mark.line + 1, 1); |
| display_table = strdup((char *)v->name); |
| if (!_lou_getDisplayTable(display_table)) |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "Display table %s not valid", display_table); |
| if (v->content) compile_inline_table(v); |
| free_table_value(v); |
| yaml_event_delete(&event); |
| if (!yaml_parser_parse(&parser, &event)) |
| simple_error("table expected", &parser, &event); |
| } |
| if (!(tables[0] = read_table(&event, &parser, display_table))) break; |
| yaml_event_delete(&event); |
| int k = 1; |
| while (1) { |
| if (!yaml_parser_parse(&parser, &event)) |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "Expected table or %s (actual %s)", |
| event_names[YAML_SCALAR_EVENT], event_names[event.type]); |
| if ((tables[k++] = read_table(&event, &parser, display_table))) { |
| if (k == MAXTABLES) |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "Only %d tables in one YAML test supported", MAXTABLES); |
| yaml_event_delete(&event); |
| } else |
| break; |
| } |
| |
| if (event.type != YAML_SCALAR_EVENT) yaml_error(YAML_SCALAR_EVENT, &event); |
| |
| int haveRunTests = 0; |
| while (1) { |
| int direction = DIRECTION_DEFAULT; |
| int hyphenation = HYPHENATION_DEFAULT; |
| if (!strcmp((const char *)event.data.scalar.value, "flags")) { |
| yaml_event_delete(&event); |
| read_flags(&parser, &direction, &hyphenation); |
| |
| if (!yaml_parser_parse(&parser, &event) || |
| (event.type != YAML_SCALAR_EVENT) || |
| strcmp((const char *)event.data.scalar.value, "tests")) { |
| simple_error("tests expected", &parser, &event); |
| } |
| yaml_event_delete(&event); |
| read_tests(&parser, tables, display_table, direction, hyphenation); |
| haveRunTests = 1; |
| |
| } else if (!strcmp((const char *)event.data.scalar.value, "tests")) { |
| yaml_event_delete(&event); |
| read_tests(&parser, tables, display_table, direction, hyphenation); |
| haveRunTests = 1; |
| } else { |
| if (haveRunTests) { |
| break; |
| } else { |
| simple_error("flags or tests expected", &parser, &event); |
| } |
| } |
| if (!yaml_parser_parse(&parser, &event)) |
| error_at_line(EXIT_FAILURE, 0, file_name, event.start_mark.line + 1, |
| "Expected table, flags, tests or %s (actual %s)", |
| event_names[YAML_MAPPING_END_EVENT], event_names[event.type]); |
| if (event.type != YAML_SCALAR_EVENT) break; |
| } |
| |
| char **p = tables; |
| while (*p) free(*(p++)); |
| } |
| if (event.type != YAML_MAPPING_END_EVENT) yaml_error(YAML_MAPPING_END_EVENT, &event); |
| yaml_event_delete(&event); |
| |
| if (!yaml_parser_parse(&parser, &event) || (event.type != YAML_DOCUMENT_END_EVENT)) { |
| yaml_error(YAML_DOCUMENT_END_EVENT, &event); |
| } |
| yaml_event_delete(&event); |
| |
| if (!yaml_parser_parse(&parser, &event) || (event.type != YAML_STREAM_END_EVENT)) { |
| yaml_error(YAML_STREAM_END_EVENT, &event); |
| } |
| yaml_event_delete(&event); |
| |
| yaml_parser_delete(&parser); |
| |
| free(emph_classes); |
| free(display_table); |
| lou_free(); |
| |
| assert(!fclose(file)); |
| |
| printf("%s (%d tests, %d failure%s)\n", (errors ? "FAILURE" : "SUCCESS"), count, |
| errors, ((errors != 1) ? "s" : "")); |
| |
| return errors ? 1 : 0; |
| |
| #endif // HAVE_LIBYAML |
| #endif // not WITHOUT_YAML |
| } |