blob: 953d8ed45c49d688202d6587c302364bfcf2e133 [file] [log] [blame]
/* 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
}