318 lines
6.9 KiB
C
318 lines
6.9 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
// SPDX-FileCopyrightText: 2007-2010 Jan Engelhardt
|
|
/*
|
|
* graph-lchain.c - Longest-chain tree nodes
|
|
*/
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <libHX/init.h>
|
|
#include <libHX/map.h>
|
|
#include <libHX/string.h>
|
|
|
|
struct node {
|
|
char *name;
|
|
struct node *parent;
|
|
bool mark;
|
|
};
|
|
|
|
static struct HXmap *nodename_map;
|
|
|
|
static inline bool lchain_contains(const struct node *start,
|
|
const struct node *needle)
|
|
{
|
|
const struct node *w;
|
|
|
|
for (w = start; w != NULL; w = w->parent)
|
|
if (w == needle)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static void lchain_relink(struct node *source, struct node *new_parent)
|
|
{
|
|
unsigned int old_path_length, new_path_length;
|
|
struct node *common_ancestor, *w;
|
|
|
|
/*
|
|
* Check whether adding <a1, a2> to the tree would generate a
|
|
* circle/loop a1-->a2-->...-->a1. If so, CONTINUE.
|
|
*
|
|
* This ensures the tree remains a tree. Once you have loops
|
|
* the graph would quickly get cramped.
|
|
*/
|
|
|
|
/* Make sure it will be loop-free */
|
|
if (lchain_contains(new_parent, source))
|
|
return;
|
|
|
|
/*
|
|
* [ Now we know that <a1, a2> is a potential candidate to replace
|
|
* <a1, a3> and that the tree is indeed loop-free. ]
|
|
*/
|
|
|
|
/*
|
|
* Find longest path towards common ancestor.
|
|
*/
|
|
|
|
/* Walk down old path and mark */
|
|
for (w = source->parent; w != NULL; w = w->parent) {
|
|
if (w->mark) {
|
|
fprintf(stderr, "Impossible loop detected at %s\n",
|
|
w->name);
|
|
abort();
|
|
}
|
|
w->mark = true;
|
|
}
|
|
|
|
/* Walk down new path and check for mark=1 */
|
|
new_path_length = 0;
|
|
for (w = new_parent; w != NULL; w = w->parent) {
|
|
++new_path_length;
|
|
if (w->mark)
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Let c be a common ancestor of an existing path, e.g.
|
|
* a1-->a3-->...-->c, and the "proposed" new path
|
|
* a1-->a2-->...-->c. If no such common ancestor exists, CONTINUE.
|
|
*/
|
|
|
|
common_ancestor = w;
|
|
if (common_ancestor == NULL) {
|
|
/* No common ancestor. Clear flags. Bail out. */
|
|
for (w = source->parent; w != NULL; w = w->parent)
|
|
w->mark = false;
|
|
return;
|
|
}
|
|
|
|
/* Walk down old path again and reset mark for further traversals. */
|
|
old_path_length = 0;
|
|
for (w = source; w != common_ancestor; w = w->parent) {
|
|
++old_path_length;
|
|
w->mark = false;
|
|
}
|
|
for (; w != NULL; w = w->parent)
|
|
w->mark = false;
|
|
|
|
/*
|
|
* If the path a1-->a2-->...->c is longer (has more edges) than
|
|
* a1-->a3->...-->c, delete <a1, a3> from the tree and add
|
|
* <a1, a2> instead.
|
|
*
|
|
* This I call longest-chaining.
|
|
*
|
|
* One can see that this results in a tree with only ever one
|
|
* tuple to match <a1, ANY>.
|
|
* This ensures that no two edges ever overlap which "helps"
|
|
* decramping the graph.
|
|
*/
|
|
|
|
source->parent = new_parent;
|
|
}
|
|
|
|
static void lchain_link(const char *source_name, const char *target_name)
|
|
{
|
|
struct node *source_ptr, *target_ptr;
|
|
int ret;
|
|
|
|
target_ptr = HXmap_get(nodename_map, target_name);
|
|
if (target_ptr == NULL) {
|
|
struct node local_node = {.name = HX_strdup(target_name)};
|
|
|
|
if (local_node.name == NULL) {
|
|
perror("strdup");
|
|
abort();
|
|
}
|
|
|
|
target_ptr = HX_memdup(&local_node, sizeof(local_node));
|
|
if (target_ptr == NULL) {
|
|
perror("HX_memdup");
|
|
abort();
|
|
}
|
|
|
|
ret = HXmap_add(nodename_map, local_node.name, target_ptr);
|
|
if (ret <= 0) {
|
|
fprintf(stderr, "HXmap_add: %s\n", strerror(-ret));
|
|
abort();
|
|
}
|
|
}
|
|
|
|
source_ptr = HXmap_get(nodename_map, source_name);
|
|
if (source_ptr == NULL) {
|
|
struct node local_node = {
|
|
.name = HX_strdup(source_name),
|
|
.parent = target_ptr,
|
|
};
|
|
|
|
if (local_node.name == NULL) {
|
|
perror("strdup");
|
|
abort();
|
|
}
|
|
|
|
source_ptr = HX_memdup(&local_node, sizeof(local_node));
|
|
if (source_ptr == NULL) {
|
|
perror("HX_memdup");
|
|
abort();
|
|
}
|
|
|
|
ret = HXmap_add(nodename_map, local_node.name, source_ptr);
|
|
if (ret <= 0) {
|
|
fprintf(stderr, "HXmap_add: %s\n", strerror(-ret));
|
|
abort();
|
|
}
|
|
|
|
/* New parent */
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* If there is no edge <a1, ANY> in the (directed) tree, add edge to
|
|
* tree, restart loop with next tuple ("CONTINUE"/RETURN).
|
|
*/
|
|
if (source_ptr->parent == NULL) {
|
|
if (lchain_contains(target_ptr, source_ptr))
|
|
return;
|
|
/* New parent */
|
|
source_ptr->parent = target_ptr;
|
|
return;
|
|
}
|
|
|
|
/* [ Now we know there must be a <a1, ANY> edge in the tree. ] */
|
|
|
|
/* If the edge <a1, a2> is already in the tree, CONTINUE. */
|
|
|
|
if (strcmp(source_ptr->parent->name, target_name) == 0)
|
|
/* Parent did not change */
|
|
return;
|
|
|
|
/* [ Now there is <a1, a3> in the tree, with a2 != a3 ] */
|
|
|
|
lchain_relink(source_ptr, target_ptr);
|
|
}
|
|
|
|
static void lchain_process(FILE *fp)
|
|
{
|
|
hxmc_t *source_name = NULL, *target_name = NULL;
|
|
|
|
while (HX_getl(&source_name, fp) != NULL) {
|
|
if (HX_getl(&target_name, fp) == NULL)
|
|
break;
|
|
|
|
HX_chomp(source_name);
|
|
HX_chomp(target_name);
|
|
lchain_link(source_name, target_name);
|
|
}
|
|
|
|
HXmc_free(source_name);
|
|
HXmc_free(target_name);
|
|
}
|
|
|
|
static void lchain_dump_tree(const struct HXmap *tree)
|
|
{
|
|
const struct HXmap_node *tree_node;
|
|
const struct node *source;
|
|
struct HXmap_trav *trav;
|
|
|
|
trav = HXmap_travinit(tree, 0);
|
|
while ((tree_node = HXmap_traverse(trav)) != NULL) {
|
|
source = tree_node->data;
|
|
if (source->parent != NULL)
|
|
printf("%s\n%s\n",
|
|
source->name, source->parent->name);
|
|
}
|
|
|
|
HXmap_travfree(trav);
|
|
}
|
|
|
|
static void lchain_free_tree(struct HXmap *tree)
|
|
{
|
|
const struct HXmap_node *tree_node;
|
|
struct node *source;
|
|
struct HXmap_trav *trav;
|
|
|
|
trav = HXmap_travinit(tree, 0);
|
|
while ((tree_node = HXmap_traverse(trav)) != NULL) {
|
|
source = tree_node->data;
|
|
free(source->name);
|
|
free(source);
|
|
}
|
|
|
|
HXmap_travfree(trav);
|
|
HXmap_free(tree);
|
|
}
|
|
|
|
static int main2(int argc, const char **argv)
|
|
{
|
|
const char **file;
|
|
FILE *fp;
|
|
|
|
setvbuf(stdout, NULL, _IOLBF, 0);
|
|
|
|
nodename_map = HXmap_init(HXMAPT_DEFAULT, HXMAP_SKEY);
|
|
if (nodename_map == NULL) {
|
|
perror("HXmap_init");
|
|
abort();
|
|
}
|
|
|
|
if (argc == 1) {
|
|
lchain_process(stdin);
|
|
} else {
|
|
for (file = &argv[1]; *file != NULL; ++file) {
|
|
fp = fopen(*file, "r");
|
|
if (fp == NULL) {
|
|
fprintf(stderr, "%s: Could not open %s: %s\n",
|
|
*argv, *file, strerror(errno));
|
|
continue;
|
|
}
|
|
lchain_process(fp);
|
|
fclose(fp);
|
|
}
|
|
}
|
|
|
|
lchain_dump_tree(nodename_map);
|
|
lchain_free_tree(nodename_map);
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
int main(int argc, const char **argv)
|
|
{
|
|
int ret;
|
|
|
|
if ((ret = HX_init()) <= 0) {
|
|
fprintf(stderr, "HX_init: %s\n", strerror(-ret));
|
|
abort();
|
|
}
|
|
ret = main2(argc, argv);
|
|
HX_exit();
|
|
return ret;
|
|
}
|
|
|
|
#if 0
|
|
static void lchain_loopcheck(const struct HXmap *tree)
|
|
{
|
|
const struct HXmap_node *tree_node;
|
|
const struct node *source;
|
|
struct node *w;
|
|
struct HXmap_trav *trav;
|
|
|
|
trav = HXmap_travinit(tree, 0);
|
|
while ((tree_node = HXmap_traverse(trav)) != NULL) {
|
|
source = tree_node->data;
|
|
for (w = source->parent; w != NULL; w = w->parent) {
|
|
if (w->mark)
|
|
fprintf(stderr, "Loop detected at %s\n",
|
|
w->name);
|
|
w->mark = true;
|
|
}
|
|
for (w = source->parent; w != NULL; w = w->parent)
|
|
w->mark = false;
|
|
}
|
|
|
|
HXmap_travfree(trav);
|
|
}
|
|
#endif
|