/** * 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 "globals.h" #include #include #include /** * This processes value for `mds_kbdc_tree_t.processed` */ #define PROCESS_LEVEL 2 /** * Tree type constant shortener */ #define C(TYPE) MDS_KBDC_TREE_TYPE_##TYPE /** * 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 * @scope error:mds_kbdc_parse_error_t* Variable where the new error will be stored */ #define NEW_ERROR(NODE, SEVERITY, ...) \ NEW_ERROR_(result, SEVERITY, 1, (NODE)->loc_line, \ (NODE)->loc_start, (NODE)->loc_end, 1, __VA_ARGS__) /** * Remove ‘.’:s * * @param START:identifier The member of `tree` that is cleaned from ‘.’:s * @scope tree:mds_kbdc_tree_t* The tree from where the ‘.’:s are being removed * @scope here:mds_kbdc_tree_t** Help variable that must be available for use * @scope argument:mds_kbdc_tree_t* Help variable that must be available for use */ #define REMOVE_NOTHING(START) \ do \ { \ long processed = tree->processed; \ tree->processed = PROCESS_LEVEL; \ for (here = &(tree->START); *here;) \ if ((*here)->type != C(NOTHING)) \ here = &((*here)->next); \ else \ while (*here && (*here)->type == C(NOTHING)) \ { \ argument = (*here)->next, (*here)->next = NULL; \ if ((processed != PROCESS_LEVEL) && ((*here)->processed != PROCESS_LEVEL)) \ NEW_ERROR(*here, WARNING, "‘.’ outside alternation has no effect"); \ mds_kbdc_tree_free(*here); \ *here = argument; \ } \ } \ while (0) /** * Flatten an alternation of orderered subsequence, that is, * insert its interior in place of it and move its next * sibling to the next of the interior * * @param argument:mds_kbdc_tree_t* The argument to flatten * @scope here:mds_kbdc_tree_t** Pointer to the space where the argument was found * @scope temp:mds_kbdc_tree_t* Help variable that must be available for use */ #define FLATTEN(argument) \ do \ { \ /* Remember the alternation/subsequence and the argument that follows it. */ \ mds_kbdc_tree_t* eliminated_argument = argument; \ temp = argument->next; \ /* Find the last alternative/element. */ \ for (argument->next = argument->ordered.inner; argument->next;) \ argument = argument->next; \ /* Attach the argument that was after the alternation/subsequence to the */ \ /* end of the alternation/subsequence, that is, flatten the right side. */ \ argument->next = temp; \ /* Flatten the left side. */ \ *here = eliminated_argument->next; \ /* Free the memory of the alternation/subsequence. */ \ eliminated_argument->ordered.inner = NULL; \ eliminated_argument->next = NULL; \ mds_kbdc_tree_free(eliminated_argument); \ } \ while (0) /** * Variable whether the latest created error is stored */ static mds_kbdc_parse_error_t* error; /** * The parameter of `simplify_tree` */ static mds_kbdc_parsed_t* restrict result; /** * Simplify a subtree * * @param tree The tree * @return Zero on success, -1 on error */ static int simplify(mds_kbdc_tree_t* restrict tree); /** * Simplify an unordered subsequence-subtree * * @param tree The unordered subsequence-subtree * @return Zero on success, -1 on error */ static int simplify_unordered(mds_kbdc_tree_unordered_t* restrict tree); /** * Eliminiate an alternation * * @param tree The statement where the alternation is found * @param argument The argument to eliminate * @param argument_index The index of the argument to eliminate * @return Zero on sucess, -1 on error */ static int eliminate_alternation(mds_kbdc_tree_t* tree, mds_kbdc_tree_t* argument, size_t argument_index) { mds_kbdc_tree_t** here; mds_kbdc_tree_t* first; mds_kbdc_tree_t* last; mds_kbdc_tree_t* new_tree; mds_kbdc_tree_t* alternative; mds_kbdc_tree_t* next_statement; mds_kbdc_tree_t* next_alternative; mds_kbdc_tree_t* new_argument; size_t i; /* Detach next statement, we do not want to duplicate all following statements. */ next_statement = tree->next, tree->next = NULL; /* Detach alternation, we replace it in all duplcates, no need to duplicate all alternatives. */ alternative = argument->alternation.inner, argument->alternation.inner = NULL; /* Eliminate. */ for (first = last = NULL; alternative; alternative = next_alternative) { /* Duplicate statement. */ if (new_tree = mds_kbdc_tree_dup((mds_kbdc_tree_t*)tree), new_tree == NULL) { int saved_errno = errno; argument->alternation.inner = alternative; tree->next = next_statement; return errno = saved_errno, -1; } /* Join trees. */ if (last) last->next = new_tree; last = new_tree; first = first ? first : new_tree; /* Jump to the alternation. */ here = &(new_tree->macro_call.arguments); for (new_argument = *here, i = 0; i < argument_index; i++, here = &((*here)->next)) new_argument = new_argument->next; /* Detach alternative. */ next_alternative = alternative->next; /* Right-join alternative. */ alternative->next = new_argument->next, new_argument->next = NULL; mds_kbdc_tree_free(new_argument); /* Left-join alternative. */ *here = alternative; } /* Replace the statement with the first generated statement without the alternation. */ mds_kbdc_tree_destroy((mds_kbdc_tree_t*)tree); memcpy(tree, first, sizeof(mds_kbdc_tree_t)); if (first == last) last = (mds_kbdc_tree_t*)tree; free(first); /* Reattach the statement that followed to the last generated statement. */ last->next = next_statement; return 0; } /** * 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; mds_kbdc_tree_t* dup_arguments = NULL; mds_kbdc_tree_t** here; size_t argument_index = 0; int saved_errno; /* Simplify arguments. */ for (argument = tree->arguments; argument; argument = argument->next) fail_if (simplify(argument)); /* Remove ‘.’:s. */ REMOVE_NOTHING(arguments); /* Copy arguments. */ if (tree->arguments == NULL) return 0; fail_if ((dup_arguments = mds_kbdc_tree_dup(tree->arguments), dup_arguments == NULL)); /* Eliminate alterations. */ for (argument = dup_arguments; argument; argument = argument->next, argument_index++) if (argument->type == C(ALTERNATION)) fail_if (eliminate_alternation((mds_kbdc_tree_t*)tree, argument, argument_index)); mds_kbdc_tree_free(dup_arguments), dup_arguments = NULL; /* Example of what will happend: * * my_macro([1 2] [1 2] [1 2]) ## call 1 * * simplify_macro_call on call 1 * after processing argument 1 * my_macro(1 [1 2] [1 2]) ## call 1 * my_macro(2 [1 2] [1 2]) ## call 5 * after processing argument 2 * my_macro(1 1 [1 2]) ## call 1 * my_macro(1 2 [1 2]) ## call 3 * my_macro(2 [1 2] [1 2]) ## call 5 * after processing argument 3 * my_macro(1 1 1) ## call 1 * my_macro(1 1 2) ## call 2 * my_macro(1 2 [1 2]) ## call 3 * my_macro(2 [1 2] [1 2]) ## call 5 * * no difference after simplify_macro_call on call 2 * * simplify_macro_call on call 3 * no difference after processing argument 1 * no difference after processing argument 2 * after processing argument 3 * my_macro(1 1 1) ## (call 1) * my_macro(1 1 2) ## (call 2) * my_macro(1 2 1) ## call 3 * my_macro(1 2 1) ## call 4 * my_macro(2 [1 2] [1 2]) ## call 5 * * no difference after simplify_macro_call on call 4 * * simplify_macro_call on call 5 * no difference after processing argument 1 * after processing argument 2 * my_macro(1 1 1) ## (call 1) * my_macro(1 1 2) ## (call 2) * my_macro(1 2 1) ## (call 3) * my_macro(1 2 2) ## (call 4) * my_macro(2 1 [1 2]) ## call 5 * my_macro(2 2 [1 2]) ## call 7 * after processing argument 3 * my_macro(1 1 1) ## (call 1) * my_macro(1 1 2) ## (call 2) * my_macro(1 2 1) ## (call 3) * my_macro(1 2 2) ## (call 4) * my_macro(2 1 1) ## call 5 * my_macro(2 1 2) ## call 6 * my_macro(2 2 [1 2]) ## call 7 * * no difference after simplify_macro_call on call 6 * * simplify_macro_call on call 7 * no difference after processing argument 1 * no difference after processing argument 2 * after processing argument 3 * my_macro(1 1 1) ## (call 1) * my_macro(1 1 2) ## (call 2) * my_macro(1 2 1) ## (call 3) * my_macro(1 2 2) ## (call 4) * my_macro(2 1 1) ## (call 5) * my_macro(2 1 2) ## (call 6) * my_macro(2 2 1) ## call 7 * my_macro(2 2 2) ## call 8 * * no difference after simplify_macro_call on call 8 * * Nothings (‘.’) are removed before processing the alternations. */ return 0; pfail: saved_errno = errno; mds_kbdc_tree_free(dup_arguments); return errno = saved_errno, -1; } /** * Simplify a mapping-subtree * * @param tree The mapping-subtree * @return Zero on success, -1 on error */ static int simplify_map(mds_kbdc_tree_map_t* restrict tree) { mds_kbdc_tree_t* argument; mds_kbdc_tree_t** here; mds_kbdc_tree_t* dup_sequence = NULL; mds_kbdc_tree_t* temp; size_t argument_index = 0; int redo = 0, saved_errno; /* Check for bad things in the result. */ for (argument = tree->result; argument; argument = argument->next) if ((argument->type != C(KEYS)) && (argument->type != C(STRING))) NEW_ERROR(argument, ERROR, "not allowed in mapping output"); /* Simplify sequence. */ for (argument = tree->sequence; argument; argument = argument->next) fail_if (simplify(argument)); /* Remove ‘.’:s. */ REMOVE_NOTHING(sequence); /* Copy sequence. */ if (tree->sequence == NULL) return 0; fail_if ((dup_sequence = mds_kbdc_tree_dup(tree->sequence), dup_sequence == NULL)); /* Eliminate alterations, remember, unordered subsequences have been simplified to alternations of ordered subsequences. */ for (argument = dup_sequence; argument; argument = argument->next, argument_index++) if (argument->type == C(ALTERNATION)) fail_if (eliminate_alternation((mds_kbdc_tree_t*)tree, argument, argument_index)); mds_kbdc_tree_free(dup_sequence), dup_sequence = NULL; /* Eliminated ordered subsequences. */ for (here = &(tree->sequence); (argument = *here); redo ? (redo = 0) : (here = &(argument->next), 0)) if (argument->type == C(ORDERED)) { FLATTEN(argument); redo = 1; } /* Mapping statements are simplified in a manner similar * to how macro calls are simplified. However mapping * statements can also contain unordered subsequences, * there are translated into alternations of ordered * subsequences. Thus after the elimination of alternations, * ordered subsequences are eliminated too. * * Example of what will happen, ‘{ }’ presents an * ordered subsequence: * * (1 2) (3 4) : 0 ## mapping 1 * * simplify_map on mapping 1 * after simplification * [{1 2} {2 1}] [{3 4} {4 3}] ## mapping 1 * after alternation elimination on argument 1 * {1 2} [{3 4} {4 3}] ## mapping 1 * {2 1} [{3 4} {4 3}] ## mapping 3 * after alternation elimination on argument 2 * {1 2} {3 4} ## mapping 1 * {1 2} {4 3} ## mapping 2 * {2 1} [{3 4} {4 3}] ## mapping 3 * after ordered subsequence elimination * 1 2 3 4 ## mapping 1 * {1 2} {4 3} ## mapping 2 * {2 1} [{3 4} {4 3}] ## mapping 3 * * simplify_map on mapping 2 * no difference after simplification * no difference after alternation elimination on argument 1 * no difference after alternation elimination on argument 2 * after ordered subsequence elimination * 1 2 3 4 ## (mapping 1) * 1 2 4 3 ## mapping 2 * {2 1} [{3 4} {4 3}] ## mapping 3 * * simplify_map on mapping 3 * no difference after simplification * no difference after alternation elimination on argument 1 * after alternation elimination on argument 2 * 1 2 3 4 ## (mapping 1) * 1 2 4 3 ## (mapping 2) * {2 1} {3 4} ## mapping 3 * {2 1} {4 3} ## mapping 4 * after ordered subsequence elimination * 1 2 3 4 ## (mapping 1) * 1 2 4 3 ## (mapping 2) * 2 1 3 4 ## mapping 3 * {2 1} {4 3} ## mapping 4 * * simplify_map on mapping 4 * no difference after simplification * no difference after alternation elimination on argument 1 * no difference after alternation elimination on argument 2 * after ordered subsequence elimination * 1 2 3 4 ## (mapping 1) * 1 2 4 3 ## (mapping 2) * 2 1 3 4 ## (mapping 3) * 2 1 4 3 ## mapping 4 * * Nothings (‘.’) are removed before processing the alternations. */ return 0; pfail: saved_errno = errno; mds_kbdc_tree_free(dup_sequence); return errno = saved_errno, -1; } /** * Simplify an alternation-subtree * * @param tree The alternation-subtree * @return Zero on success, -1 on error */ static int simplify_alternation(mds_kbdc_tree_alternation_t* restrict tree) { mds_kbdc_tree_t* argument; mds_kbdc_tree_t* first_nothing = NULL; mds_kbdc_tree_t* temp; mds_kbdc_tree_t** here; int redo = 0; /* Test emptyness. */ if (tree->inner == NULL) { NEW_ERROR(tree, ERROR, "empty alternation"); tree->type = C(NOTHING); tree->processed = PROCESS_LEVEL; return 0; } /* Test singletonness. */ if (tree->inner->next == NULL) { temp = tree->inner; NEW_ERROR(tree, WARNING, "singleton alternation"); memcpy(tree, temp, sizeof(mds_kbdc_tree_t)); free(temp); return simplify((mds_kbdc_tree_t*)tree); } /* Simplify. */ for (here = &(tree->inner); (argument = *here); redo ? (redo = 0) : (here = &(argument->next), 0)) if ((argument->type == C(NOTHING)) && (argument->processed != PROCESS_LEVEL)) { /* Test multiple nothings. */ if (first_nothing == NULL) first_nothing = argument; else { NEW_ERROR(argument, WARNING, "multiple ‘.’ inside an alternation"); NEW_ERROR(first_nothing, NOTE, "first ‘.’ was here"); } } else if (argument->type == C(ALTERNATION)) { /* Alternation nesting. */ if (argument->processed != PROCESS_LEVEL) NEW_ERROR(argument, WARNING, "alternation inside alternation is unnessary"); fail_if (simplify_alternation(&(argument->alternation))); if (argument->type == C(ALTERNATION)) FLATTEN(argument); redo = 1; } else if (argument->type == C(UNORDERED)) { /* Nesting unordered subsequence, simplifies to alternation of ordered subsequence, or simpler. */ NEW_ERROR(argument, WARNING, "unordered subsequence inside alternation is discouraged"); fail_if (simplify_unordered(&(argument->unordered))); redo = 1; } return 0; pfail: return -1; } /** * Create a chain of ordered subsequence covering all * permutations of a set of subtrees * * @param elements The subtrees, chained * @return Chain of ordered subsequence, `NULL` on error */ static mds_kbdc_tree_t* create_permutations(mds_kbdc_tree_t* elements) { mds_kbdc_tree_t* first = NULL; mds_kbdc_tree_t** here = &first; mds_kbdc_tree_t** previous_next = &elements; mds_kbdc_tree_t* argument; mds_kbdc_tree_t* temp; mds_kbdc_tree_t* subperms = NULL; mds_kbdc_tree_t* perm; mds_kbdc_tree_t ordered; int saved_errno, no_perms; for (previous_next = &elements; (argument = *previous_next); previous_next = &((*previous_next)->next)) { /* Created ordered alternative for a permutation prototype. */ mds_kbdc_tree_initialise(&ordered, C(ORDERED)); /* Select the first element. */ temp = argument->next, argument->next = NULL; ordered.ordered.inner = mds_kbdc_tree_dup(argument); argument->next = temp; fail_if (ordered.ordered.inner == NULL); /* Create subpermutations. */ *previous_next = argument->next; argument->next = NULL; no_perms = (elements == NULL); subperms = create_permutations(elements); argument->next = *previous_next; *previous_next = argument; fail_if (no_perms ? 0 : subperms == NULL); /* Join first element with subpermutations. */ while (subperms) { /* Join. */ fail_if ((perm = mds_kbdc_tree_dup(&ordered), perm == NULL)); perm->ordered.inner = subperms->ordered.inner; /* Add the permutation to the chain. */ *here = perm; here = &(perm->next); /* Select next permutation. */ temp = subperms; subperms = subperms->next; temp->next = NULL; mds_kbdc_tree_free(temp); } /* Destroy prototype. */ mds_kbdc_tree_destroy(&ordered); } return first; pfail: saved_errno = errno; mds_kbdc_tree_free(first); mds_kbdc_tree_free(subperms); mds_kbdc_tree_destroy(&ordered); errno = saved_errno; return NULL; } /** * Simplify an unordered subsequence-subtree * * @param tree The unordered subsequence-subtree * @return Zero on success, -1 on error */ static int simplify_unordered(mds_kbdc_tree_unordered_t* restrict tree) { mds_kbdc_tree_t* arguments; mds_kbdc_tree_t* argument; mds_kbdc_tree_t* temp; mds_kbdc_tree_t** here; int allow_long = 0; size_t argument_count; int argv_force = 1; /* TODO globals.h */ /* Test for ‘(( ))’. */ if (tree->inner && (tree->inner->next == NULL) && (tree->inner->type == C(UNORDERED))) { temp = tree->inner; tree->inner = tree->inner->unordered.inner; temp->unordered.inner = NULL; mds_kbdc_tree_free(temp); allow_long = 1; } /* Test emptyness. */ if (tree->inner == NULL) { NEW_ERROR(tree, ERROR, "empty unordered subsequence"); tree->type = C(NOTHING); tree->processed = PROCESS_LEVEL; return 0; } /* Test singletonness. */ if (tree->inner->next == NULL) { temp = tree->inner; NEW_ERROR(tree, WARNING, "singleton unordered subsequence"); memcpy(tree, temp, sizeof(mds_kbdc_tree_t)); free(temp); return simplify((mds_kbdc_tree_t*)tree); } /* Remove ‘.’:s. */ REMOVE_NOTHING(inner); /* Simplify. */ for (argument = tree->inner, argument_count = 0; argument; argument = argument->next, argument_count++) if (argument->type == C(ALTERNATION)) { fail_if (simplify_alternation(&(argument->alternation))); argument->processed = PROCESS_LEVEL; } else if (argument->type == C(UNORDERED)) { NEW_ERROR(argument, WARNING, "unordered subsequence inside unordered subsequence is discouraged"); fail_if (simplify_unordered(&(argument->unordered))); argument->processed = PROCESS_LEVEL; } /* Check the size of the subsequence. */ if ((argument_count > 5) && (allow_long * argv_force == 0)) { if (allow_long == 0) NEW_ERROR(argument, ERROR, "unordered subsequence longer than 5 elements need double brackets"); else if (argv_force == 0) NEW_ERROR(argument, ERROR, "unordered subsequence of size %zu found, requires ‘--force’ to compile", argument_count); return 0; } /* Generate permutations. */ tree->type = C(ALTERNATION); tree->processed = PROCESS_LEVEL; arguments = tree->inner; if (tree->inner = create_permutations(arguments), tree->inner == NULL) return tree->inner = arguments, -1; mds_kbdc_tree_free(arguments); 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 #define S(type) if ((r = simplify_##type(&(tree->type)))) return r int r; again: if (tree == NULL) return 0; switch (tree->type) { case C(INFORMATION): s (information.inner); break; case C(FUNCTION): s (function.inner); break; case C(MACRO): s (macro.inner); break; case C(ASSUMPTION): s (assumption.inner); break; case C(FOR): s (for_.inner); break; case C(IF): s (if_.inner); s (if_.otherwise); break; case C(MAP): S (map); break; case C(ALTERNATION): S (alternation); break; case C(UNORDERED): S (unordered); break; case C(MACRO_CALL): S (macro_call); break; default: break; } tree = tree->next; goto again; #undef s #undef S } /** * Simplify a tree and generate related warnings and errors in the process * * @param result_ `result` from `parse_to_tree`, same sematics, will be updated * @return -1 if an error occursed that cannot be stored in `result`, zero otherwise */ int simplify_tree(mds_kbdc_parsed_t* restrict result_) { result = result_; return simplify(result_->tree); } #undef FLATTEN #undef REMOVE_NOTHING #undef NEW_ERROR #undef C #undef PROCESS_LEVEL