improved dot

This commit is contained in:
Gregory Burd 2024-03-21 23:09:16 -04:00
parent 38893379d8
commit 7c4789e71d
2 changed files with 198 additions and 183 deletions

View file

@ -169,12 +169,18 @@ shuffle(int *array, size_t n)
} }
} }
#define TEST_ARRAY_SIZE 50 #define TEST_ARRAY_SIZE 5
int int
main() main()
{ {
int rc = 0; int gen = 0, rc = 0;
FILE *of = fopen("/tmp/slm.dot", "w");
if (!of) {
perror("Failed to open file /tmp/slm.dot");
return 1;
}
/* Allocate and initialize a Skiplist. */ /* Allocate and initialize a Skiplist. */
slex_t *list = (slex_t *)malloc(sizeof(slex_t)); slex_t *list = (slex_t *)malloc(sizeof(slex_t));
@ -184,6 +190,7 @@ main()
rc = api_skip_init_slex(list, 12, __slm_key_compare); rc = api_skip_init_slex(list, 12, __slm_key_compare);
if (rc) if (rc)
return rc; return rc;
api_skip_dot_slex(of, list, gen++, sprintf_slex_node);
/* Test creating a snapshot of an empty Skiplist */ /* Test creating a snapshot of an empty Skiplist */
slex_snap_t *snap = api_skip_snapshot_slex(list); slex_snap_t *snap = api_skip_snapshot_slex(list);
@ -200,13 +207,17 @@ main()
for (int i = 0; i < asz; i++) { for (int i = 0; i < asz; i++) {
rc = api_skip_put_slex(list, array[i], to_lower(int_to_roman_numeral(array[i]))); rc = api_skip_put_slex(list, array[i], to_lower(int_to_roman_numeral(array[i])));
api_skip_dot_slex(of, list, gen++, sprintf_slex_node);
char *v = api_skip_get_slex(list, array[i]); char *v = api_skip_get_slex(list, array[i]);
api_skip_set_slex(list, array[i], to_upper(v)); api_skip_set_slex(list, array[i], to_upper(v));
} }
api_skip_dup_slex(list, -1, int_to_roman_numeral(-1)); api_skip_dup_slex(list, -1, int_to_roman_numeral(-1));
api_skip_dot_slex(of, list, gen++, sprintf_slex_node);
api_skip_dup_slex(list, 1, int_to_roman_numeral(1)); api_skip_dup_slex(list, 1, int_to_roman_numeral(1));
api_skip_dot_slex(of, list, gen++, sprintf_slex_node);
api_skip_del_slex(list, 0); api_skip_del_slex(list, 0);
api_skip_dot_slex(of, list, gen++, sprintf_slex_node);
#if 0 #if 0
snap = api_skip_snapshot_slex(list); snap = api_skip_snapshot_slex(list);
@ -239,15 +250,8 @@ main()
assert(strcmp(api_skip_pos_slex(list, SKIP_LTE, 2)->value, int_to_roman_numeral(2)) == 0); assert(strcmp(api_skip_pos_slex(list, SKIP_LTE, 2)->value, int_to_roman_numeral(2)) == 0);
assert(strcmp(api_skip_pos_slex(list, SKIP_LTE, (TEST_ARRAY_SIZE + 1))->value, int_to_roman_numeral(TEST_ARRAY_SIZE)) == 0); assert(strcmp(api_skip_pos_slex(list, SKIP_LTE, (TEST_ARRAY_SIZE + 1))->value, int_to_roman_numeral(TEST_ARRAY_SIZE)) == 0);
FILE *of = fopen("/tmp/slm.dot", "w");
if (!of) {
perror("Failed to open file /tmp/slm.dot");
return EXIT_FAILURE;
}
api_skip_dot_slex(of, list, 0, sprintf_slex_node);
fclose(of);
api_skip_destroy_slex(list); api_skip_destroy_slex(list);
api_skip_dot_end_slex(of, gen);
fclose(of);
return rc; return rc;
} }

View file

@ -997,178 +997,189 @@
return prefix##skip_remove_##decl(slist, &node); \ return prefix##skip_remove_##decl(slist, &node); \
} }
#define SKIPLIST_DECL_DOT(decl, prefix, field) \ #define SKIPLIST_DECL_DOT(decl, prefix, field) \
\ \
/* A type for a function that writes into a char[2048] buffer \ /* A type for a function that writes into a char[2048] buffer \
* a description of the value within the node. */ \ * a description of the value within the node. */ \
typedef void (*skip_sprintf_node_##decl##_t)(decl##_node_t *, char *); \ typedef void (*skip_sprintf_node_##decl##_t)(decl##_node_t *, char *); \
\ \
/* -- __skip_dot_node_ \ /* -- __skip_dot_node_ \
* Writes out a fragment of a DOT file representing a node. \ * Writes out a fragment of a DOT file representing a node. \
*/ \ */ \
static void __skip_dot_node_##decl(FILE *os, decl##_t *slist, decl##_node_t *node, size_t nsg, skip_sprintf_node_##decl##_t fn) \ static size_t __skip_dot_width_##decl(decl##_t *slist, decl##_node_t *node, size_t level) \
{ \ { \
char buf[2048]; \ size_t w = 0; \
size_t level, height = node->field.sle.len; \ decl##_node_t *n; \
decl##_node_t *next; \ \
\ if (node == slist->slh_tail) \
fprintf(os, "\"node%zu %p\"", nsg, (void *)node); \ return 0; \
fprintf(os, " [label = \""); \ n = node->field.sle.next[level]; \
level = height; \ \
while (level--) { \ do { \
fprintf(os, " { <w%zu> | <f%zu> %p } |", level, level, (void *)node->field.sle.next[level]); \ w++; \
} \ n = prefix##skip_prev_node_##decl(slist, n); \
if (fn) { \ } while (n && n->field.sle.prev != node); \
fn(node, buf); \ \
fprintf(os, " <f0> %s\"\n", buf); \ return --w; \
} else { \ } \
fprintf(os, " <f0> ?\"\n"); \ \
} \ /* -- __skip_dot_node_ \
fprintf(os, "shape = \"record\"\n"); \ * Writes out a fragment of a DOT file representing a node. \
fprintf(os, "];\n"); \ */ \
\ static void __skip_dot_node_##decl(FILE *os, decl##_t *slist, decl##_node_t *node, size_t nsg, skip_sprintf_node_##decl##_t fn) \
/* Now edges */ \ { \
level = 0; \ char buf[2048]; \
for (level = 0; level < height; level++) { \ size_t level, width, height = node->field.sle.len; \
fprintf(os, "\"node%zu %p\"", nsg, (void *)node); \ decl##_node_t *next; \
fprintf(os, ":f%zu -> ", level); \ \
fprintf(os, "\"node%zu %p\"", nsg, (void *)node->field.sle.next[level]); \ fprintf(os, "\"node%zu %p\"", nsg, (void *)node); \
fprintf(os, ":w%zu [];\n", level); \ fprintf(os, " [label = \""); \
} \ level = height; \
next = prefix##skip_next_node_##decl(slist, node); \ while (level--) { \
if (next) \ width = __skip_dot_width_##decl(slist, node, level); \
__skip_dot_node_##decl(os, slist, next, nsg, fn); \ fprintf(os, " { <w%zu> %zu | <f%zu> %p } |", level, width, level, (void *)node->field.sle.next[level]); \
} \ } \
\ if (fn) { \
/* -- __skip_dot_finish_ \ fn(node, buf); \
* Finalize the DOT file of the internal representation. \ fprintf(os, " <f0> %s\"\n", buf); \
*/ \ } else { \
static void __skip_dot_finish_##decl(FILE *os, size_t nsg) \ fprintf(os, " <f0> ?\"\n"); \
{ \ } \
size_t i; \ fprintf(os, "shape = \"record\"\n"); \
if (nsg > 0) { \ fprintf(os, "];\n"); \
/* Link the nodes together with an invisible node. \ \
* node0 [shape=record, label = "<f0> | <f1> | <f2> | <f3> | \ /* Now edges */ \
* <f4> | <f5> | <f6> | <f7> | <f8> | ", style=invis, width=0.01]; \ level = 0; \
*/ \ for (level = 0; level < height; level++) { \
fprintf(os, "node0 [shape=record, label = \""); \ fprintf(os, "\"node%zu %p\"", nsg, (void *)node); \
for (i = 0; i < nsg; ++i) { \ fprintf(os, ":f%zu -> ", level); \
fprintf(os, "<f%zu> | ", i); \ fprintf(os, "\"node%zu %p\"", nsg, (void *)node->field.sle.next[level]); \
} \ fprintf(os, ":w%zu [];\n", level); \
fprintf(os, "\", style=invis, width=0.01];\n"); \ } \
\ next = prefix##skip_next_node_##decl(slist, node); \
/* Now connect nodes with invisible edges \ if (next) \
* \ __skip_dot_node_##decl(os, slist, next, nsg, fn); \
* node0:f0 -> HeadNode [style=invis]; \ } \
* node0:f1 -> HeadNode1 [style=invis]; \ \
*/ \ /* -- _skip_dot_finish_ \
for (i = 0; i < nsg; ++i) { \ * Finalize the DOT file of the internal representation. \
fprintf(os, "node0:f%zu -> HeadNode%zu [style=invis];\n", i, i); \ */ \
} \ void prefix##skip_dot_end_##decl(FILE *os, size_t nsg) \
nsg = 0; \ { \
} \ size_t i; \
fprintf(os, "}\n"); \ if (nsg > 0) { \
} \ /* Link the nodes together with an invisible node. \
\ * node0 [shape=record, label = "<f0> | <f1> | <f2> | <f3> | \
/* -- skip_dot_start_ */ \ * <f4> | <f5> | <f6> | <f7> | <f8> | ", style=invis, width=0.01]; \
static int __skip_dot_start_##decl(FILE *os, decl##_t *slist, size_t nsg, skip_sprintf_node_##decl##_t fn) \ */ \
{ \ fprintf(os, "node0 [shape=record, label = \""); \
size_t level; \ for (i = 0; i < nsg; ++i) { \
decl##_node_t *head, *tail; \ fprintf(os, "<f%zu> | ", i); \
if (nsg == 0) { \ } \
fprintf(os, "digraph Skiplist {\n"); \ fprintf(os, "\", style=invis, width=0.01];\n"); \
fprintf(os, "label = \"Skiplist.\"\n"); \ \
fprintf(os, "graph [rankdir = \"LR\"];\n"); \ /* Now connect nodes with invisible edges \
fprintf(os, "node [fontsize = \"12\" shape = \"ellipse\"];\n"); \ * \
fprintf(os, "edge [];\n\n"); \ * node0:f0 -> HeadNode [style=invis]; \
} \ * node0:f1 -> HeadNode1 [style=invis]; \
fprintf(os, "subgraph cluster%zu {\n", nsg); \ */ \
fprintf(os, "style=dashed\n"); \ for (i = 0; i < nsg; ++i) { \
fprintf(os, "label=\"Skip list iteration %zu\"\n\n", nsg); \ fprintf(os, "node0:f%zu -> HeadNode%zu [style=invis];\n", i, i); \
fprintf(os, "\"HeadNode%zu\" [\n", nsg); \ } \
fprintf(os, "label = \""); \ nsg = 0; \
\ } \
/* Write out the fields */ \ fprintf(os, "}\n"); \
head = slist->slh_head; \ } \
if (SKIP_EMPTY(slist)) \ \
fprintf(os, "Empty HeadNode"); \ /* -- skip_dot_ \
else { \ * Create a DOT file of the internal representation of the \
level = head->field.sle.len - 1; \ * Skiplist on the provided file descriptor (default: STDOUT). \
do { \ * \
decl##_node_t *node = head->field.sle.next[level]; \ * To view the output: \
fprintf(os, "{ <f%zu> %p }", level, (void *)node); \ * $ dot -Tps filename.dot -o outfile.ps \
if (level && head->field.sle.next[level] != slist->slh_tail) \ * You can change the output format by varying the value after -T and \
fprintf(os, " | "); \ * choosing an appropriate filename extension after -o. \
} while (level-- && head->field.sle.next[level] != slist->slh_tail); \ * See: https://graphviz.org/docs/outputs/ for the format options. \
} \ * \
fprintf(os, "\"\n"); \ * https://en.wikipedia.org/wiki/DOT_(graph_description_language) \
fprintf(os, "shape = \"record\"\n"); \ */ \
fprintf(os, "];\n"); \ int prefix##skip_dot_##decl(FILE *os, decl##_t *slist, size_t nsg, skip_sprintf_node_##decl##_t fn) \
\ { \
/* Edges for head node */ \ size_t width, level; \
decl##_node_t *node = slist->slh_head; \ decl##_node_t *node, *next; \
for (level = 0; level < slist->slh_head->field.sle.len; level++) { \ \
if (node->field.sle.next[level] == slist->slh_tail) \ if (slist == NULL || fn == NULL) \
break; \ return nsg; \
fprintf(os, "\"HeadNode%zu\":f%zu -> ", nsg, level); \ if (__skip_integrity_check_##decl(slist) != 0) { \
fprintf(os, "\"node%zu %p\"", nsg, (void *)node->field.sle.next[level]); \ perror("Skiplist failed integrity checks, impossible to diagram."); \
fprintf(os, ":w%zu [];\n", level); \ return -1; \
} \ } \
fprintf(os, "\n"); \ if (nsg == 0) { \
\ fprintf(os, "digraph Skiplist {\n"); \
/* Now all nodes via level 0, if non-empty */ \ fprintf(os, "label = \"Skiplist.\"\n"); \
node = prefix##skip_head_##decl(slist); \ fprintf(os, "graph [rankdir = \"LR\"];\n"); \
if (node) \ fprintf(os, "node [fontsize = \"12\" shape = \"ellipse\"];\n"); \
__skip_dot_node_##decl(os, slist, node, nsg, fn); \ fprintf(os, "edge [];\n\n"); \
fprintf(os, "\n"); \ } \
\ fprintf(os, "subgraph cluster%zu {\n", nsg); \
/* The tail, sentinal node */ \ fprintf(os, "style=dashed\n"); \
tail = slist->slh_tail; \ fprintf(os, "label=\"Skip list iteration %zu\"\n\n", nsg); \
if (!SKIP_EMPTY(slist)) { \ fprintf(os, "\"HeadNode%zu\" [\n", nsg); \
fprintf(os, "\"node%zu %p\" [label = \"", nsg, (void *)slist->slh_tail); \ fprintf(os, "label = \""); \
level = tail->field.sle.len - 1; \ \
do { \ /* Write out the fields */ \
fprintf(os, "<w%zu> %p", level, (void *)node->field.sle.prev); \ if (prefix##skip_size_##decl(slist) == 0) \
if (level && node->field.sle.prev != slist->slh_head) \ fprintf(os, "Empty HeadNode"); \
fprintf(os, " | "); \ else { \
} while (level-- && node->field.sle.prev != slist->slh_head); \ node = slist->slh_head->field.sle.next[0]; \
fprintf(os, "\" shape = \"record\"];\n"); \ level = slist->slh_head->field.sle.len; \
} \ next = node->field.sle.next[level] == NULL ? slist->slh_tail : node->field.sle.next[level]; \
\ do { \
/* End: "subgraph cluster0 {" */ \ width = __skip_dot_width_##decl(slist, node, level); \
fprintf(os, "}\n\n"); \ fprintf(os, "{ %zu | <f%zu> %p }", width, level, (void *)next); \
nsg += 1; \ if (level) \
\ fprintf(os, " | "); \
return nsg; \ } while (level--); \
} \ } \
\ fprintf(os, "\"\n"); \
/* -- skip_dot_ \ fprintf(os, "shape = \"record\"\n"); \
* Create a DOT file of the internal representation of the \ fprintf(os, "];\n"); \
* Skiplist on the provided file descriptor (default: STDOUT). \ \
* \ /* Edges for head node */ \
* To view the output: \ node = slist->slh_head; \
* $ dot -Tps filename.dot -o outfile.ps \ for (level = 0; level < slist->slh_head->field.sle.len; level++) { \
* You can change the output format by varying the value after -T and \ if (node->field.sle.next[level] == slist->slh_tail) \
* choosing an appropriate filename extension after -o. \ break; \
* See: https://graphviz.org/docs/outputs/ for the format options. \ fprintf(os, "\"HeadNode%zu\":f%zu -> ", nsg, level); \
* \ fprintf(os, "\"node%zu %p\"", nsg, (void *)node->field.sle.next[level]); \
* https://en.wikipedia.org/wiki/DOT_(graph_description_language) \ fprintf(os, ":w%zu [];\n", level); \
*/ \ } \
int prefix##skip_dot_##decl(FILE *os, decl##_t *slist, size_t nsg, skip_sprintf_node_##decl##_t fn) \ fprintf(os, "\n"); \
{ \ \
if (__skip_integrity_check_##decl(slist) != 0) { \ /* Now all nodes via level 0, if non-empty */ \
perror("Skiplist failed integrity checks, impossible to diagram."); \ node = prefix##skip_head_##decl(slist); \
return -1; \ if (node) \
} \ __skip_dot_node_##decl(os, slist, node, nsg, fn); \
if (os == NULL) \ fprintf(os, "\n"); \
os = stdout; \ \
if (!os) { \ /* The tail, sentinal node */ \
perror("Failed to open output file, unable to write DOT file."); \ node = slist->slh_tail; \
return -1; \ if (prefix##skip_size_##decl(slist) > 0) { \
} \ fprintf(os, "\"node%zu %p\" [label = \"", nsg, (void *)slist->slh_tail); \
__skip_dot_start_##decl(os, slist, nsg, fn); \ level = node->field.sle.len; \
__skip_dot_finish_##decl(os, nsg); \ do { \
return 0; \ fprintf(os, "<w%zu> %p", level, (void *)node); \
if (level && node->field.sle.prev != slist->slh_head) \
fprintf(os, " | "); \
} while (level--); \
fprintf(os, "\" shape = \"record\"];\n"); \
} \
\
/* End: "subgraph cluster0 {" */ \
fprintf(os, "}\n\n"); \
nsg += 1; \
\
return nsg; \
} }
#endif /* _SKIPLIST_H_ */ #endif /* _SKIPLIST_H_ */