/** * mds — A micro-display server * Copyright © 2014 Mattias Andrée (maandree@member.fsf.org) * * 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 . */ #include "simplify-tree.h" #include #include #include /** * Wrapper around `asprintf` that makes sure that first * argument gets set to `NULL` on error and that zero is * returned on success rather than the number of printed * characters * * @param VAR:char** The output parameter for the string * @param ...:const char*, ... The format string and arguments * @return :int Zero on success, -1 on error */ #define xasprintf(VAR, ...) \ (asprintf(&(VAR), __VA_ARGS__) < 0 ? (VAR = NULL, -1) : 0) /** * Add an error the to error list * * @param NODE:const mds_kbdc_tree_t* The node the triggered the error * @param SEVERITY:identifier * in `MDS_KBDC_PARSE_ERROR_*` to indicate severity * @param ...:const char*, ... Error description format string and arguments */ #define NEW_ERROR(NODE, SEVERITY, ...) \ do \ { \ if (errors_ptr + 1 >= errors_size) \ { \ errors_size = errors_size ? (errors_size << 1) : 2; \ fail_if (xxrealloc(old_errors, *errors, errors_size, mds_kbdc_parse_error_t*)); \ old_errors = NULL; \ } \ fail_if (xcalloc(error, 1, mds_kbdc_parse_error_t)); \ (*errors)[errors_ptr + 0] = error; \ (*errors)[errors_ptr + 1] = NULL; \ errors_ptr++; \ error->line = (NODE)->loc_line; \ error->severity = MDS_KBDC_PARSE_ERROR_##SEVERITY; \ error->error_is_in_file = 1; \ error->start = (NODE)->loc_start; \ error->end = (NODE)->loc_end; \ fail_if ((error->pathname = strdup(pathname)) == NULL); \ fail_if ((error->code = strdup(source_code.real_lines[error->line])) == NULL); \ fail_if (xasprintf(error->description, __VA_ARGS__)); \ } \ while (0) /** * Temporary storage variable for the new error, * it is made available so that it is easy to * make adjustments to the error after calling * `NEW_ERROR` */ static mds_kbdc_parse_error_t* error; /** * The number of elements allocated for `*errors` */ static size_t errors_size; /** * The number of elements stored in `*errors` */ static size_t errors_ptr; /** * Pointer to the list of errors */ static mds_kbdc_parse_error_t*** errors; /** * The old `*errors` used temporary when reallocating `*errors` */ static mds_kbdc_parse_error_t** old_errors; /** * The pathname of the file that have been parsed */ static char* pathname; /** * Simplify a subtree * * @param tree The tree * @return Zero on success, -1 on error */ static int simplify(mds_kbdc_tree_t* restrict tree); /** * Simplify a macro call-subtree * * @param tree The macro call-subtree * @return Zero on success, -1 on error */ static int simplify_macro_call(mds_kbdc_tree_macro_call_t* restrict tree) { mds_kbdc_tree_t* argument = tree->arguments; mds_kbdc_tree_t** here; /* Simplify arguments. */ for (argument = tree->arguments; argument; argument = argument->next) simplify(argument); /* Remove ‘.’:s. */ for (here = &(tree->arguments); *here; here = &((*here)->next)) while (*here && (*here)->type == MDS_KBDC_TREE_TYPE_NOTHING) { mds_kbdc_tree_t* temp = (*here)->next; (*here)->next = NULL; NEW_ERROR(*here, WARNING, "‘.’ outside alternation has no effect"); mds_kbdc_tree_free(*here); *here = temp; } return 0; pfail: return -1; } /** * Simplify a subtree * * @param tree The tree * @return Zero on success, -1 on error */ static int simplify(mds_kbdc_tree_t* restrict tree) { #define s(expr) if ((r = simplify(tree->expr))) return r; int r; again: if (tree == NULL) return 0; switch (tree->type) { case MDS_KBDC_TREE_TYPE_INFORMATION: s (information.inner); break; case MDS_KBDC_TREE_TYPE_FUNCTION: s (function.inner); break; case MDS_KBDC_TREE_TYPE_MACRO: s (macro.inner); break; case MDS_KBDC_TREE_TYPE_ASSUMPTION: s (assumption.inner); break; case MDS_KBDC_TREE_TYPE_FOR: s (for_.inner); break; case MDS_KBDC_TREE_TYPE_IF: s (if_.inner); s (if_.otherwise); break; case MDS_KBDC_TREE_TYPE_MAP: /* TODO */ break; case MDS_KBDC_TREE_TYPE_ALTERNATION: /* TODO find alternations inside alternations */ break; case MDS_KBDC_TREE_TYPE_UNORDERED: /* TODO find unordered and nothing inside unordered */ break; case MDS_KBDC_TREE_TYPE_MACRO_CALL: if ((r = simplify_macro_call(&(tree->macro_call)))) return r; break; default: break; } tree = tree->next; goto again; #undef s } /** * Simplify a tree and generate related warnings in the process * * @param filename The filename of the tree that have been parsed * @param tree The tree, it may be modified * @param errors_ `NULL`-terminated list of found error, `NULL` if no errors were found or if -1 is returned * @return -1 if an error occursed that cannot be stored in `*errors`, zero otherwise */ int simplify_tree(const char* restrict filename, mds_kbdc_tree_t* restrict tree, mds_kbdc_parse_error_t*** restrict errors_) { int r, saved_errno; error = NULL; errors_size = errors_ptr = 0; errors = errors_; *errors = NULL; old_errors = NULL; /* Get a non-relative pathname for the file, relative filenames * can be misleading as the program can have changed working * directroy to be able to resolve filenames. */ fail_if ((pathname = realpath(filename, NULL), pathname == NULL)); if (r = simplify(tree), !r) return 0; pfail: saved_errno = errno; free(pathname); mds_kbdc_parse_error_free_all(old_errors); mds_kbdc_parse_error_free_all(*errors), *errors = NULL; errno = saved_errno; return r; } #undef NEW_ERROR #undef xasprintf