5 * (c) 2024 Straylight/Edgeware
8 /*----- Licensing notice --------------------------------------------------*
10 * This file is part of Xyla, a library of binary trees.
12 * Xyla is free software: you can redistribute it and/or modify it under
13 * the terms of the GNU Lesser General Public License as published by the
14 * Free Software Foundation; either version 3 of the License, or (at your
15 * option) any later version.
17 * Xyla is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
20 * License for more details.
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with Xyla. If not, see <https://www.gnu.org/licenses/>.
26 /*----- Header files ------------------------------------------------------*/
36 /*----- Primary switch ----------------------------------------------------*/
47 # define TREE__NAME(name) XYLA_AVL_##name
48 # define tree__name(name) xyla_avl_##name
49 # define HAVE_HEIGHT 1
50 # define FLAGMASK (~XYLA_AVLF_BALMASK)
54 # define TREE__NAME(name) XYLA_RB_##name
55 # define tree__name(name) xyla_rb_##name
56 # define HAVE_HEIGHT 1
57 # define FLAGMASK (~XYLA_RBF_RED)
61 # define TREE__NAME(name) XYLA_SPLAY_##name
62 # define tree__name(name) xyla_splay_##name
68 # define TREE__NAME(name) XYLA_TREAP_##name
69 # define tree__name(name) xyla_treap_##name
73 # error "unknown `TREE' value"
76 #define NODE tree__name(node)
77 #define ITER tree__name(iter)
78 #define RITER tree__name(riter)
79 #define PATH tree__name(path)
80 #define TREE_NODEPFX TREE__NAME(NODEPFX)
81 #define TREE_NODEUSFX TREE__NAME(NODEUSFX)
82 #define TREE_HEIGHT tree__name(height)
83 #define TREE_INITITER tree__name(inititer)
84 #define TREE_NEXT tree__name(next)
85 #define TREE_INITRITER tree__name(initriter)
86 #define TREE_PREV tree__name(prev)
87 #define TREE_CURRENT tree__name(current)
88 /* #define TREE_COPYPATH tree__name(copypath) */
89 #define TREE_FIRSTPATH tree__name(firstpath)
90 #define TREE_LASTPATH tree__name(lastpath)
91 #define TREE_NEXTPATH tree__name(nextpath)
92 #define TREE_PREVPATH tree__name(prevpath)
93 #define TREE_BEFOREPATH tree__name(beforepath)
94 #define TREE_AFTERPATH tree__name(afterpath)
95 #define TREE_ROOTPATH tree__name(rootpath)
96 #define TREE_UPPATH tree__name(uppath)
97 #define TREE_LEFTPATH tree__name(leftpath)
98 #define TREE_RIGHTPATH tree__name(rightpath)
99 /* #define TREE_REPLACE tree__name(replace) */
100 /* #define TREE_RIPPLE tree__name(ripple) */
101 #define TREE_ASCEND tree__name(ascend)
102 #define TREE_LOOKUP tree__name(lookup)
103 #define TREE_PROBE tree__name(probe)
104 #define TREE_INSERT tree__name(insert)
105 #define TREE_REMOVE tree__name(remove)
106 #define TREE_JOIN tree__name(join)
107 #define TREE_SPLIT tree__name(split)
108 #define TREE_SPLITAT tree__name(splitat)
109 /* #define TREE_SPLITROOT tree__name(splitroot) */
110 #define TREE_UNISECT tree__name(unisect)
111 #define TREE_DIFFSECT tree__name(diffsect)
112 #define TREE_CHECK tree__name(check)
116 # define HELSE(x, y) x
120 # define HELSE(x, y) y
124 /*----- Preliminaries -----------------------------------------------------*/
126 static FILE *in, *out;
132 static NORETURN PRINTF_LIKE(2, 3)
133 void bail(unsigned cat, const char *msg, ...)
135 /* Write a`printf'-formatted message MSG to standard error and quit the
136 * program. CAT is a constant which indicates the reason: `BCAT_FAIL'
137 * indicates an environmental problem (e.g., insufficient memory);
138 * `BCAT_ERR' indicates a user error; `BCAT_BUG' indicates a bug detected
139 * in the tree implementation. The category determines the token printed
140 * before the message. Also, if this is a fuzzing build, `BCAT_BUG'
141 * results in a call to `abort' rather than `exit' to let the fuzzer know
142 * that it should claim a win.
149 case BCAT_FAIL: fputs("failure: ", stderr); break;
150 case BCAT_ERR: fputs("error: ", stderr); break;
151 case BCAT_BUG: fputs("BUG: ", stderr); break;
153 fprintf(stderr, "(unknown bail code %u): ", cat);
157 va_start(ap, msg); vfprintf(stderr, msg, ap); va_end(ap);
160 if (pos >= 0) fprintf(stderr, " (input pos = %ld)", pos);
163 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
164 if (cat == BCAT_BUG) abort();
169 enum { DIR_IN, DIR_OUT };
170 /* Direction codes for `check_file', `open_file', and `close_file'. */
172 static void check_file(unsigned dir)
174 /* Check that the file indicated by DIR is free of errors. If DIR is
175 * `DIR_OUT' then buffered output is flushed.
184 case DIR_IN: fp = in; act = "read"; break;
185 case DIR_OUT: fp = out; act = "write"; f |= f_flush; break;
189 if (((f&f_flush) && fflush(fp)) || ferror(fp))
190 bail(BCAT_ERR, "%s error: %s", act, strerror(errno));
196 static void close_file(unsigned dir)
198 /* Close the file indicated by DIR. Check that it was in a good state
202 FILE **fp_inout, *fp, *stdfp;
205 case DIR_IN: fp_inout = ∈ stdfp = stdin; break;
206 case DIR_OUT: fp_inout = &out; stdfp = stdout; break;
212 if (fp != stdfp) fclose(fp);
217 static void open_file(const char *file, unsigned dir)
219 /* Open FILE as the current input or output file, as indicated by DIR. */
221 FILE **fp_inout, *fp, *stdfp;
222 const char *mode, *what;
228 fp_inout = ∈ stdfp = stdin; mode = "r"; what = "input";
232 fp_inout = &out; stdfp = stdout; mode = "w"; what = "output";
237 if (file[0] == '-' && !file[1]) {
238 if (((f&f_chkeof) && feof(stdfp)) || ferror(stdfp))
239 bail(BCAT_ERR, "already consumed standard %s", what);
242 fp = fopen(file, mode);
244 bail(BCAT_ERR, "failed to open %s file `%s': %s",
245 what, file, strerror(errno));
252 static unsigned long read_int(void)
254 /* Read and return an integer from the intput file.
256 * These are used as a search keys, indices, or weights.
262 /* For some stupid reason, there isn't a `scanf' format which just reads
263 * an unsigned integer with an arbitrary radix prefix. The `%d' format
264 * does the right thing for a signed value, but not unsigned. So we have
267 do ch = getc(in); while (isspace(ch));
268 if (!isdigit(ch)) bail(BCAT_ERR, "integer expected, found `%c'", ch);
271 if (fscanf(in, "%lu", &k) != 1)
272 bail(BCAT_ERR, "integer expected, scanf failed");
275 if (ch == 'x' || ch == 'X') {
276 if (fscanf(in, "%lx", &k) != 1)
277 bail(BCAT_ERR, "integer expected, scanf failed");
278 } else if ('0' <= ch && ch <= '7') {
280 if (fscanf(in, "%lo", &k) != 1)
281 bail(BCAT_ERR, "integer expected, scanf failed");
283 { ungetc(ch, in); k = 0; }
288 static unsigned long seed = 0xacd1051a;
289 /* Seed for random number generator. Don't set this to zero. */
291 #if TREE == TREAP || defined(FLAGMASK)
292 static unsigned long randwt(void)
294 /* Return a pseudo-random integer.
296 * We have our own -- currently a rather simple LFSR -- so that the test
297 * results are reproducible on all targets.
300 unsigned long x = seed, m;
302 #define RANDPOLY 0x915717d7
304 for (i = 0; i < 32; i++) {
305 m = ~(((seed >> 31)&1) - 1);
306 seed = (seed << 1) ^ (m&RANDPOLY);
308 seed &= 0xffffffff; return (x);
314 static const char *rctag(int rc)
316 /* Return the name of return code RC as a string. */
318 static const char *const tagtab[] = {
319 #define TAG(tag, msg) #tag,
325 if (XYLA_RC_LOSELIMIT <= rc && rc < XYLA_RC_WINLIMIT)
326 return (tagtab[rc - XYLA_RC_LOSELIMIT]);
328 return ("#<unknown libxyla error code>");
331 /*----- Node definition ---------------------------------------------------*/
334 /* Our test node structure. */
336 TREE_NODEPFX; /* tree node control data */
338 unsigned fref; /* reference value for user flags */
340 struct my_node *next, *prev; /* links in active nodes list */
341 size_t n; /* number of nodes in this subtree */
342 unsigned long seq, k; /* sequence number and key */
344 union my_nodeu { struct my_node my; TREE_NODEUSFX; };
347 #define MY_NODE(node) \
348 CONVERT_CAREFULLY(struct my_node *, struct xyla_bt_node *, node)
349 #define MY_NODEU(node) \
350 CONVERT_CAREFULLY(union my_nodeu *, struct xyla_bt_node *, node)
351 #define CONST_MY_NODE(node) \
352 CONVERT_CAREFULLY(const struct my_node *, \
353 const struct xyla_bt_node *, \
355 #define CONST_MY_NODEU(node) \
356 CONVERT_CAREFULLY(const union my_nodeu *, \
357 const struct xyla_bt_node *, \
360 #define MY_SPLAY_NODE(node) \
361 CONVERT_CAREFULLY(struct my_node *, struct xyla_splay_node *, node)
363 #define TREE_NODE(node) \
364 CONVERT_CAREFULLY(struct NODE *, struct xyla_bt_node *, node)
365 #define CONST_TREE_NODE(node) \
366 CONVERT_CAREFULLY(const struct NODE *, \
367 const struct xyla_bt_node *, \
370 static unsigned long nodeseq = 0; /* next node sequence number */
371 static struct my_node *nodes = 0; /* list of active nodes */
373 static int my_ordnav(struct xyla_bt_nodecls *cls,
374 const struct xyla_bt_node *node, void *arg)
376 /* Navigation function to search by key. */
378 const struct my_node *mynode = CONST_MY_NODE(node);
379 unsigned long *key = arg;
381 if (*key < mynode->k) return (-1);
382 else if (*key > mynode->k) return (+1);
386 static int my_idxnav(struct xyla_bt_nodecls *cls,
387 const struct xyla_bt_node *node, void *arg)
389 /* Navigation function to search by index. */
391 const struct my_node *myleft = CONST_MY_NODE(node->left);
392 unsigned long *i_inout = arg, i = *i_inout, n;
396 if (i < n) return (-1);
400 *i_inout = i - 1; return (+1);
403 static void my_upd(struct xyla_bt_nodecls *cls, struct xyla_bt_node *node)
405 /* Update function to maintain node count. */
408 *mynode = MY_NODE(node),
409 *left = MY_NODE(mynode->bt.left), *right = MY_NODE(mynode->bt.right);
411 mynode->n = (left ? left->n : 0) + (right ? right->n : 0) + 1;
414 static const void *my_key(struct xyla_bt_nodecls *cls,
415 const struct xyla_bt_node *node)
417 /* Return search key for node. */
419 return (&CONST_MY_NODE(node)->k);
422 #define CHKF_OUTMASK 0x3000u /* check flags: output mode mask */
423 #define CHKOUT_NONE 0x0000u /* no output */
424 #define CHKOUT_LIST 0x1000u /* structure list */
425 #define CHKOUT_KEYS 0x2000u /* plain list of keys */
426 #define CHKOUT_DUMP 0x3000u /* full multiline dump */
427 #define CHKF_SANITY 0x0800u /* checking user input */
429 struct my_checkstate {
430 /* Checking state. */
432 unsigned f; /* flags */
433 #define CF_SPC 1u /* put space before next output */
437 /* Per-node tracking information */
439 struct xyla_bt_ordinfo _ord; /* order checking state */
440 int lv; /* indentation level */
443 static int my_check(unsigned op, const struct xyla_bt_check *chk)
445 /* Main checking and walking function. */
447 const struct my_node *node = CONST_MY_NODE(chk->node), *left, *right;
448 struct my_checkstate *st = chk->state;
450 *node_info = chk->node_info,
451 *parent_info = chk->parent_info;
453 int rc = XYLA_RC_OK, subrc;
457 /* Reached a null link. Some dump types print something distinctive
458 * if the entire tree is empty, which we can determine easily enough.
459 * The `CHKOUT_LIST' format needs to indicate all null links
463 switch (chk->f&CHKF_OUTMASK) {
470 if (chk->pos == XYLA_BTPOS_ROOT) fputs("(empty tree)", out);
473 if (chk->pos == XYLA_BTPOS_ROOT)
474 fputs("\t(tree empty)\n", out);
479 case XYLA_CHKOP_BEFORE:
480 /* Called before the children. Set up our indentation level, which
481 * is a little tricky for red-black trees, because we align black
489 if (!(node->rb.f&XYLA_RBF_RED))
490 node_info->lv = (parent_info->lv + 2)&~1;
493 node_info->lv = parent_info->lv + 1;
494 if ((chk->f&CHKF_OUTMASK) == CHKOUT_LIST) putc('(', out);
498 /* Called after the left subtree. This is where the main printing
502 switch (chk->f&CHKF_OUTMASK) {
508 if (!(node->rb.f&XYLA_RBF_RED)) putc('*', out);
510 fprintf(out, "0x%08lx$ ", (unsigned long)node->trp.wt);
512 fprintf(out, "%lu", node->k);
516 if (!(st->f&CF_SPC)) st->f |= CF_SPC;
518 fprintf(out, "%lu", node->k);
521 fprintf(out, "\t#0x%08lx (n = %3lu)\t%*s",
522 node->seq, (unsigned long)node->n, 4*node_info->lv, "");
524 fprintf(out, "(%c)", AVL_BALCH(node));
526 fprintf(out, "(%c)", node->rb.f&XYLA_RBF_RED ? ' ' : '*');
531 fprintf(out, " 0x%08lx$", (unsigned long)node->trp.wt);
533 fprintf(out, " %lu\n", node->k);
538 case XYLA_CHKOP_AFTER:
539 /* Called after the right subtree. This is where we check that the
540 * node count is correct and that the user flags are faithfully
544 left = CONST_MY_NODE(chk->left);
545 right = CONST_MY_NODE(chk->right);
546 want = (left ? left->n : 0) + (right ? right->n : 0) + 1;
547 if (node->n != want) {
549 xyla_bt_bughdr("MY_TREE", chk->root, chk->fp);
550 xyla_bt_printnode(chk->cls, &node->bt, chk->fp);
551 fprintf(chk->fp, " count %lu /= %lu\n",
552 (unsigned long)node->n, (unsigned long)want);
557 if ((node->T.f&FLAGMASK) != node->fref) {
559 xyla_bt_bughdr("MY_TREE", chk->root, chk->fp);
560 xyla_bt_printnode(chk->cls, &node->bt, chk->fp);
561 fprintf(chk->fp, " flags %x /= %x\n",
562 node->T.f&FLAGMASK, node->fref);
567 if ((chk->f&CHKF_OUTMASK) == CHKOUT_LIST) putc(')', out);
571 /* Check that the ordering is respected. */
572 subrc = xyla_bt_chkorder(op, chk);
573 if (subrc) { rc = subrc; if (subrc != XYLA_RC_BAD) goto end; }
580 static void my_ident(struct xyla_bt_nodecls *cls,
581 const struct xyla_bt_node *node, FILE *fp)
583 /* Node identification function. */
585 const struct my_node *my_node = CONST_MY_NODE(node);
587 fprintf(fp, "#<node #0x%08lx ", my_node->seq);
589 fprintf(fp, "0x%08lx$ ", (unsigned long)my_node->trp.wt);
591 fprintf(fp, "%lu>", my_node->k);
594 static const struct xyla_bt_chkops my_ops = {
595 /* Node class operations table. */
597 { sizeof(my_ops), my_upd },
598 { my_ordnav, my_key },
599 { my_check, sizeof(struct my_nodeinfo), my_ident },
602 /*----- Node utilities ----------------------------------------------------*/
604 static union my_nodeu *make_node(unsigned long k)
606 /* Return a fresh node with key K. */
608 union my_nodeu *node;
610 node = malloc(sizeof(*node));
611 if (!node) bail(BCAT_FAIL, "out of memory");
613 node->my.fref = node->my.T.f = randwt()&FLAGMASK;
615 node->my.n = 99; node->my.seq = nodeseq++; node->my.k = k;
616 if (nodes) nodes->prev = &node->my;
617 node->my.next = nodes; node->my.prev = 0; nodes = &node->my;
621 static void free_node(struct my_node *node)
623 /* Free a node which we don't want any more. */
625 struct my_node *next = node->next, *prev = node->prev;
627 if (next) next->prev = prev;
628 if (prev) prev->next = next;
633 static void free_tree(struct xyla_bt_node **root)
635 /* Free the tree rooted at ROOT. */
637 while (*root) free_node(xyla_bt_severfirst(root));
640 static void check_dump(struct xyla_bt_nodecls *cls,
641 struct xyla_bt_node *const *root HARG(int expht),
644 /* Run a check or dump of the tree at ROOT.
646 * If output is requested, then suppress checking output. If there were
647 * problems them report them afterwards. If we didn't have a full dump
648 * and problems were found, then print a dump afterwards.
651 struct my_checkstate st;
656 if ((f&CHKF_OUTMASK) == CHKOUT_DUMP) {
657 fputs("tree dump", out);
658 H( if (expht != -1) fprintf(out, ", ht = %d", expht); )
661 rc = TREE_CHECK(cls, root,
662 (f&CHKF_OUTMASK) == CHKOUT_NONE ? stderr : 0, f
665 if ((f&CHKF_OUTMASK) == CHKOUT_NONE) {
666 ht = TREE_HEIGHT(CONST_TREE_NODE(*root));
668 xyla_bt_bughdr("MY_TREE", root, stderr);
669 fprintf(stderr, "expected height %d /= %d\n", expht, ht);
670 if (!rc) rc = XYLA_RC_BAD;
674 if ((f&CHKF_OUTMASK) == CHKOUT_LIST || (f&CHKF_OUTMASK) == CHKOUT_KEYS)
677 if ((f&CHKF_OUTMASK) != CHKOUT_NONE)
678 TREE_CHECK(cls, root, stderr, CHKOUT_NONE HARG(expht), &st);
679 if ((f&CHKF_OUTMASK) != CHKOUT_DUMP) {
680 fputs("offending tree...\n", out);
681 TREE_CHECK(cls, root, 0, CHKOUT_DUMP HARG(expht), &st);
683 if (f&CHKF_SANITY) bail(BCAT_ERR, "invalid tree: %s", xyla_strerror(rc));
684 else bail(BCAT_BUG, "check failed: %s", xyla_strerror(rc));
688 static union my_nodeu *copy_tree(const union my_nodeu *node)
690 /* Make a copy of a tree.
692 * The nodes have new sequence numbers, but should otherwise be identical.
695 union my_nodeu *copy, *left, *right;
697 if (!node) return (0);
698 left = copy_tree(CONST_MY_NODEU(node->bt.left));
699 right = copy_tree(CONST_MY_NODEU(node->bt.right));
700 copy = make_node(node->my.k);
702 copy->my.T.f = node->my.T.f;
703 copy->my.fref = node->my.fref;
705 copy->my.spl.parent = 0;
706 if (left) left->my.spl.parent = ©->spl;
707 if (right) right->my.spl.parent = ©->spl;
709 copy->my.trp.wt = node->my.trp.wt;
711 copy->my.bt.left = left ? &left->bt : 0;
712 copy->my.bt.right = right ? &right->bt : 0;
713 copy->my.n = node->my.n;
717 static int ascend_index(struct xyla_bt_node *node,
718 struct xyla_bt_node *parent,
719 struct xyla_bt_node *sibling,
720 unsigned pos, void *arg)
722 /* Ascension function to determine a node's index. */
724 struct my_node *mysib = MY_NODE(sibling);
725 size_t *i_inout = arg;
727 if (pos == XYLA_BTPOS_RIGHT)
728 *i_inout += 1 + (mysib ? mysib->n : 0);
732 static size_t path_index(struct PATH *path)
734 /* Return the index of a node given a full path to it. */
736 struct my_node *mynode, *myleft;
739 mynode = TREE_CURRENT(path);
741 myleft = MY_NODE(mynode->bt.left);
742 if (myleft) i += myleft->n;
745 TREE_ASCEND(ascend_index, path, &i);
749 static int full_path_p(struct PATH *path)
751 /* Return wehther PATH is full. */
753 return (!!TREE_CURRENT(path));
756 static struct xyla_bt_node *read_tree(HELSE(int *ht_out, void))
758 /* Read a tree structure description from the input file and return
759 * a pointer to the root.
761 * If the tree has a notion of height, then return the height in *HT_OUT.
764 union my_nodeu *node;
765 struct my_node *left, *right;
768 H( int ht; int lht; int rht; )
772 unsigned f = XYLA_RBF_RED;
774 size_t wt = 0, lwt, rwt;
779 /* Check for a `_' indicating a nil subtree, or the open paren. */
780 do ch = getc(in); while (isspace(ch));
781 if (ch == '_') { H( *ht_out = 0; ) return (0); }
782 else if (ch != '(') bail(BCAT_ERR, "expected `(', found `%c'", ch);
784 /* Read the left subtree. */
785 left = MY_NODE(read_tree(H(&lht)));
787 /* Read flags before the node proper. */
789 do ch = getc(in); while (isspace(ch));
791 if (f == 3 && ch == '-') f = XYLA_AVLBAL_LBIAS;
792 else if (f == 3 && ch == '=') f = XYLA_AVLBAL_EVEN;
793 else if (f == 3 && ch == '+') f = XYLA_AVLBAL_RBIAS;
796 if (ch == '*') f &= ~XYLA_RBF_RED;
802 /* Read the node key. */
803 ungetc(ch, in); k = read_int();
804 do ch = getc(in); while (isspace(ch));
806 /* If this is a treap, then maybe that was actually the node weight.
807 * If so, stash it and read the real key.
810 if (ch == '$') { wt = k; f |= f_weight; k = read_int(); }
815 /* Read the right subtree. */
816 right = MY_NODE(read_tree(H(&rht)));
818 /* Expect the closing paren. */
819 do ch = getc(in); while (isspace(ch));
820 if (ch != ')') bail(BCAT_ERR, "expected `)', found `%c'", ch);
824 node->my.bt.left = left ? &left->bt : 0;
825 node->my.bt.right = right ? &right->bt : 0;
826 node->my.n = (left ? left->n : 0) + (right ? right->n : 0) + 1;
828 /* Fill in the tree-specific bits. */
833 if (lht == -1 || rht == -1)
834 bail(BCAT_ERR, "missing balance annotation");
836 case -1: f = XYLA_AVLBAL_LBIAS; ht = lht + 1; break;
837 case 0: f = XYLA_AVLBAL_EVEN; ht = lht + 1; break;
838 case +1: f = XYLA_AVLBAL_RBIAS; ht = rht + 1; break;
839 default: bail(BCAT_ERR, "tree is not AVL"); ht = 0; break;
842 node->my.avl.f = (node->my.avl.f&FLAGMASK) | f;
844 ht = f&XYLA_RBF_RED ? lht : lht + 1;
845 node->my.rb.f = (node->my.rb.f&FLAGMASK) | f;
847 if (left) left->spl.parent = &node->spl;
848 if (right) right->spl.parent = &node->spl;
849 node->my.spl.parent = 0;
851 lwt = left ? left->trp.wt : 0xffffffff;
852 rwt = right ? right->trp.wt : 0xffffffff;
854 node->my.trp.wt = wt;
856 wt = (((rwt < lwt ? rwt : lwt)&0xfff00000) - (1ul << 20)) | node->my.seq;
857 if (wt > lwt || wt > rwt) bail(BCAT_ERR, "tree is not a treap");
858 node->my.trp.wt = wt;
862 /* Save the subtree height. */
873 /*----- Main program ------------------------------------------------------*/
875 int main(int argc, char *argv[])
879 struct xyla_bt_nodecls mycls;
880 struct { struct xyla_bt_node *root; H( int ht; ) } cur, stack[NSTACK], u;
881 struct my_node *node = 0, *t;
882 union my_nodeu *nodeu;
883 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
884 struct my_node *x, *y;
887 struct xyla_bt_node **link;
889 struct xyla_bt_node *btnode;
895 int opt, ch, rc, sp = 0;
896 unsigned long i, j, k;
907 /* Parse command-line options. */
910 if (opt >= argc) break;
912 if (*p != '-') break;
914 if (*p == '-' && !p[1]) { opt++; break; }
916 #define GETARG do { \
918 if (opt >= argc) bail(BCAT_ERR, "missing argument"); \
923 case 'o': GETARG; open_file(p, DIR_OUT); goto next_arg;
924 default: bail(BCAT_ERR, "unknown option `-%c'", p[-1]);
932 /* Initialize my node class. */
933 mycls.ops = &my_ops.node;
935 /* Initialize the current tree. */
936 cur.root = 0; H( cur.ht = 0; )
938 /* Set up the input and output streams. */
939 if (!out) out = stdout;
940 if (opt >= argc) in = stdin;
941 else open_file(argv[opt++], DIR_IN);
942 V( xyla__verbout = out; )
945 do ch = getc(in); while (isspace(ch));
949 if (opt < argc) open_file(argv[opt++], DIR_IN);
953 case ';': /* comment */
954 do ch = getc(in); while (ch != EOF && ch != '\n');
957 case ':': /* print message */
959 ch = getc(in); if (ch == '\n' || ch == EOF) break;
969 case '+': /* insert */
970 if (!(f&f_key)) bail(BCAT_ERR, "no key");
972 node = TREE_PROBE(&mycls, &cur.root, my_ordnav, &k, &path);
974 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
975 bail(BCAT_ERR, "placed insertion is out-of-bounds for fuzzers");
977 if (full_path_p(&path)) bail(BCAT_ERR, "path full");
980 if (node) bail(BCAT_ERR, "key %lu already present", k);
981 nodeu = make_node(k);
983 if (f&f_weight) nodeu->my.trp.wt = wt;
984 else nodeu->my.trp.wt = randwt();
986 rc = TREE_INSERT(&mycls, &path, &nodeu->T);
987 H( if (rc == XYLA_RC_HTCHG) cur.ht++; else )
989 bail(BCAT_BUG, "insert returned #%s: %s",
990 rctag(rc), xyla_strerror(rc));
992 f &= ~(f_key | f_path | f_weight);
995 case '-': /* remove */
997 if (!full_path_p(&path)) bail(BCAT_ERR, "path empty");
999 if (!(f&f_key)) bail(BCAT_ERR, "no key or path");
1000 node = TREE_PROBE(&mycls, &cur.root, my_ordnav, &k, &path);
1001 if (!node) bail(BCAT_ERR, "key %lu not present", k);
1003 rc = TREE_REMOVE(&mycls, &path);
1004 H( if (rc == XYLA_RC_HTCHG) cur.ht--; else )
1006 bail(BCAT_BUG, "remove returned #%s: %s",
1007 rctag(rc), xyla_strerror(rc));
1008 free_node(node); node = 0; f &= ~(f_key | f_path);
1011 case '#': /* populate */
1013 do ch = getc(in); while (isspace(ch));
1014 if (ch == '-') j = read_int();
1015 else { ungetc(ch, in); j = i; i = 1; }
1017 if (!TREE_PROBE(&mycls, &cur.root, my_ordnav, &i, &path)) {
1018 nodeu = make_node(i);
1020 nodeu->my.trp.wt = randwt();
1022 rc = TREE_INSERT(&mycls, &path, &nodeu->T);
1023 H( if (rc == XYLA_RC_HTCHG) cur.ht++; else )
1025 bail(BCAT_BUG, "insert returned #%s: %s",
1026 rctag(rc), xyla_strerror(rc));
1029 else if (i < j) i++;
1032 node = 0; f &= ~(f_key | f_path | f_weight);
1035 case '?': /* lookup */
1036 if (!(f&f_key)) bail(BCAT_ERR, "no key");
1037 node = TREE_LOOKUP(&mycls, &cur.root, my_ordnav, &k);
1038 f &= ~(f_key | f_path);
1041 case '@': /* probe */
1042 if (!(f&f_key)) bail(BCAT_ERR, "no key");
1043 node = TREE_PROBE(&mycls, &cur.root, my_ordnav, &k, &path);
1044 f = (f&~f_key) | f_path;
1047 case ',': /* lookup/probe by index */
1048 if (!(f&f_key)) bail(BCAT_ERR, "no key");
1052 node = TREE_LOOKUP(&mycls, &cur.root, my_idxnav, &k);
1053 f &= ~(f_key | f_path);
1056 node = TREE_PROBE(&mycls, &cur.root, my_idxnav, &k, &path);
1057 f = (f&~f_key) | f_path;
1060 bail(BCAT_ERR, "expected `?' or `@', found `%c'", ch);
1065 case '[': /* first */
1066 node = TREE_FIRSTPATH(&cur.root, &path);
1067 if (!node) f &= ~f_key;
1068 else { k = node->k; f |= f_key; }
1072 case ']': /* last */
1073 node = TREE_LASTPATH(&cur.root, &path);
1074 if (!node) f &= ~f_key;
1075 else { k = node->k; f |= f_key; }
1079 case 'c': /* current */
1080 if (!(f&f_path)) bail(BCAT_ERR, "no path");
1081 node = TREE_CURRENT(&path);
1082 if (!node) f &= ~f_key;
1083 else { k = node->k; f |= f_key; }
1086 case '<': /* previous */
1087 if (!(f&f_path)) bail(BCAT_ERR, "no path");
1088 node = TREE_PREVPATH(&path);
1089 if (!node) f &= ~f_key;
1090 else { k = node->k; f |= f_key; }
1093 case '>': /* next */
1094 if (!(f&f_path)) bail(BCAT_ERR, "no path");
1095 node = TREE_NEXTPATH(&path);
1096 if (!node) f &= ~f_key;
1097 else { k = node->k; f |= f_key; }
1100 case '{': /* before */
1101 if (!(f&f_path)) bail(BCAT_ERR, "no path");
1102 else if (!full_path_p(&path)) bail(BCAT_ERR, "path empty");
1103 TREE_BEFOREPATH(&path); f &= ~f_key;
1106 case '}': /* after */
1107 if (!(f&f_path)) bail(BCAT_ERR, "no path");
1108 else if (!full_path_p(&path)) bail(BCAT_ERR, "path empty");
1109 TREE_AFTERPATH(&path); f &= ~f_key;
1112 case 't': /* root (`top') */
1113 node = TREE_ROOTPATH(&cur.root, &path);
1114 if (!node) f &= ~f_key;
1115 else { k = node->k; f |= f_key; }
1120 if (!(f&f_path)) bail(BCAT_ERR, "no path");
1121 node = TREE_UPPATH(&pos, &path);
1123 case XYLA_BTPOS_ROOT: fputs("root\n", out); break;
1124 case XYLA_BTPOS_LEFT: fputs("left\n", out); break;
1125 case XYLA_BTPOS_RIGHT: fputs("right\n", out); break;
1126 default: bail(BCAT_BUG, "unexpected position code %u\n", pos);
1130 case 'l': /* left */
1131 if (!(f&f_path)) bail(BCAT_ERR, "no path");
1132 else if (!full_path_p(&path)) bail(BCAT_ERR, "path empty");
1133 node = TREE_LEFTPATH(&path);
1134 if (!node) f &= ~f_key;
1135 else { k = node->k; f |= f_key; }
1138 case 'r': /* right */
1139 if (!(f&f_path)) bail(BCAT_ERR, "no path");
1140 else if (!full_path_p(&path)) bail(BCAT_ERR, "path empty");
1141 node = TREE_RIGHTPATH(&path);
1142 if (!node) f &= ~f_key;
1143 else { k = node->k; f |= f_key; }
1146 case '*': /* clear */
1147 node = 0; f &= ~(f_key | f_path);
1150 case '(': /* push */
1151 if (sp >= NSTACK) bail(BCAT_ERR, "stack full");
1152 stack[sp++] = cur; cur.root = 0; H( cur.ht = 0; )
1153 node = 0; f &= ~(f_key | f_path | f_weight);
1156 case '"': /* duplicate */
1157 if (sp >= NSTACK) bail(BCAT_ERR, "stack full");
1159 cur.root = (struct xyla_bt_node *)copy_tree(MY_NODEU(cur.root));
1160 node = 0; f &= ~(f_key | f_path | f_weight);
1164 if (!sp) bail(BCAT_ERR, "stack empty");
1165 free_tree(&cur.root); cur = stack[--sp];
1166 node = 0; f &= ~(f_key | f_path | f_weight);
1169 case '%': /* swap */
1170 if (!sp) bail(BCAT_ERR, "stack empty");
1171 u = stack[sp - 1]; stack[sp - 1] = cur; cur = u;
1172 node = 0; f &= ~(f_key | f_path | f_weight);
1175 case '~': /* join */
1176 if (!sp) bail(BCAT_ERR, "stack empty");
1177 if (!(f&f_key)) t = 0;
1179 t = &make_node(k)->my;
1181 if (f&f_weight) t->trp.wt = wt;
1182 else t->trp.wt = randwt();
1185 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1186 x = TREE_LASTPATH(&stack[sp - 1].root, &path);
1187 y = TREE_FIRSTPATH(&cur.root, &path);
1188 if (!t ? x && y && x->k >= y->k
1189 : (x && x->k >= t->k) || (y && t->k >= y->k))
1190 bail(BCAT_ERR, "invalid ordering");
1192 rc = TREE_JOIN(&mycls, &cur.root HARG(&cur.ht),
1193 &stack[sp - 1].root HARG(stack[sp - 1].ht),
1195 &cur.root HARG(cur.ht));
1197 bail(BCAT_BUG, "join returned #%s: %s",
1198 rctag(rc), xyla_strerror(rc));
1199 sp--; node = 0; f &= ~(f_key | f_path | f_weight);
1202 case '/': /* split */
1203 if (sp > NSTACK - 2) bail(BCAT_ERR, "stack full");
1204 if (!(f&f_path)) bail(BCAT_ERR, "no path");
1205 rc = TREE_SPLIT(&mycls, &stack[sp].root HARG(&stack[sp].ht),
1207 &cur.root HARG(&cur.ht),
1210 bail(BCAT_BUG, "split returned #%s: %s",
1211 rctag(rc), xyla_strerror(rc));
1212 node = MY_NODE(btnode);
1213 if (node) node->n = 1;
1214 stack[sp + 1].root = node ? &node->bt : 0;
1215 H( stack[sp + 1].ht = node ? 1 : 0; )
1216 sp += 2; f &= ~(f_key | f_path | f_weight);
1219 case '|': /* union/intersection */
1220 if (!sp) bail(BCAT_ERR, "stack empty");
1221 rc = TREE_UNISECT(&mycls, &cur.root HARG(&cur.ht),
1222 &stack[sp - 1].root HARG(&stack[sp - 1].ht),
1223 &stack[sp - 1].root HARG(stack[sp - 1].ht),
1224 &cur.root HARG(cur.ht));
1226 bail(BCAT_BUG, "unisect returned #%s: %s",
1227 rctag(rc), xyla_strerror(rc));
1228 node = 0; f &= ~(f_key | f_path | f_weight);
1231 case '\\': /* difference/intersection */
1232 if (!sp) bail(BCAT_ERR, "stack empty");
1233 if (sp >= NSTACK) bail(BCAT_ERR, "stack full");
1234 rc = TREE_DIFFSECT(&mycls, &cur.root HARG(&cur.ht),
1235 &stack[sp].root HARG(&stack[sp].ht),
1236 &cur.root HARG(cur.ht),
1237 &stack[sp - 1].root);
1239 bail(BCAT_BUG, "diffsect returned #%s: %s",
1240 rctag(rc), xyla_strerror(rc));
1241 sp++; node = 0; f &= ~(f_key | f_path | f_weight);
1244 case '^': /* ascend */
1245 if (!(f&f_path)) bail(BCAT_ERR, "no path");
1246 fprintf(out, "%lu\n", (unsigned long)path_index(&path));
1249 case 'k': /* print key */
1252 if (f&f_weight) fprintf(out, "0x%08lx$ ", (unsigned long)wt);
1254 fprintf(out, "%lu\n", k);
1256 else fputs("(no key)\n", out);
1259 case 'n': /* print node */
1260 if (!node) fputs("(nil)\n", out);
1261 else fprintf(out, "#<node #0x%08lx %lu>\n", node->seq, node->k);
1264 case 'p': /* print path */
1266 fprintf(out, "(nil)\n");
1270 if (path.root == &cur.root) fputs("#<link root -> ", out);
1271 else fprintf(out, "#<link address %p -> ... ", (void *)path.root);
1272 t = MY_SPLAY_NODE(path.node);
1274 fputs("(nil)>", out);
1276 fprintf(out, "node #0x%08lx %lu>", t->seq, t->k);
1278 fprintf(out, " #<node #0x%08lx %lu %s> -> (nil)",
1280 path.f == XYLA_SPF_LEFT ? "left" : "right");
1284 for (i = 0, t = 0; i <= path.k; i++) {
1285 if (i) putc(' ', out);
1287 if (!t && link == &cur.root)
1288 fputs("#<link root -> ", out);
1289 else if (t && link == &t->bt.left)
1290 fprintf(out, "#<link node #0x%08lx %lu left -> ",
1292 else if (t && link == &t->bt.right)
1293 fprintf(out, "#<link node #0x%08lx %lu right -> ",
1296 fprintf(out, "#<link address %p -> ", (void *)link);
1298 if (!t) fputs("(nil)", out);
1299 else fprintf(out, "node #0x%08lx %lu", t->seq, t->k);
1303 putc(')', out); putc('\n', out);
1307 case 'i': /* iterate */
1308 TREE_INITITER(cur.root, &it); f |= f_first;
1310 t = TREE_NEXT(&it); if (!t) break;
1311 if (f&f_first) f &= ~f_first;
1312 else putc(' ', out);
1313 fprintf(out, "%lu", t->k);
1315 if (f&f_first) fputs("(empty tree)", out);
1319 case 'j': /* reverse iterate */
1320 TREE_INITRITER(cur.root, &rit); f |= f_first;
1322 t = TREE_PREV(&rit); if (!t) break;
1323 if (f&f_first) f &= ~f_first;
1324 else putc(' ', out);
1325 fprintf(out, "%lu", t->k);
1327 if (f&f_first) fputs("(empty tree)", out);
1331 case 'K': /* list keys */
1332 check_dump(&mycls, &cur.root HARG(cur.ht), CHKOUT_KEYS);
1335 case 'L': /* list */
1336 check_dump(&mycls, &cur.root HARG(cur.ht), CHKOUT_LIST);
1339 case 'D': /* dump */
1340 check_dump(&mycls, &cur.root HARG(cur.ht), CHKOUT_DUMP);
1343 case '!': /* check */
1344 check_dump(&mycls, &cur.root HARG(cur.ht), CHKOUT_NONE);
1347 case '=': /* assign */
1348 free_tree(&cur.root);
1349 cur.root = read_tree(H(&cur.ht));
1351 if (cur.ht == -1) cur.ht = xyla_avl_height(AVL_NODE(cur.root));
1353 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1354 check_dump(&mycls, &cur.root HARG(cur.ht), CHKOUT_NONE | CF_SANITY);
1356 node = 0; f &= ~(f_key | f_path | f_weight);
1359 case 'S': /* seed */
1363 case 'V': /* verbose */
1367 if (ch == EOF || isspace(ch)) break;
1371 ch = getc(in); if (ch == '\n' || ch == EOF) break;
1372 NV( putc(ch, out); )
1374 NV( putc('\n', out); )
1376 case 'i': V( v |= XYLA__VF_ITER; ) break;
1377 case 'p': V( v |= XYLA__VF_PATH; ) break;
1378 case '@': V( v |= XYLA__VF_PROBE; ) break;
1379 case '+': V( v |= XYLA__VF_INSERT; ) break;
1380 case '-': V( v |= XYLA__VF_REMOVE; ) break;
1381 case '/': V( v |= XYLA__VF_SPLIT; ) break;
1382 case '~': V( v |= XYLA__VF_JOIN; ) break;
1383 case '|': V( v |= XYLA__VF_UNISECT; ) break;
1384 case '\\': V( v |= XYLA__VF_DIFFSECT; ) break;
1386 case 'Z': V( v |= XYLA__VF_SPLAY; ) break;
1388 default: bail(BCAT_ERR, "unexpected category `%c'", ch);
1391 V( xyla__verbose = v; )
1394 case 'B': /* rebalance */
1396 xyla_splay_rebalance(&mycls, &cur.root);
1400 case 'Z': /* splay */
1401 if (!(f&f_path)) bail(BCAT_ERR, "no path");
1403 xyla_splay_splay(&mycls, &path);
1408 case '$': /* weight */
1409 if (!(f&f_key)) bail(BCAT_ERR, "no key");
1410 wt = k; f = (f&~f_key) | f_weight;
1415 if (isdigit(ch)) /* set key */
1416 { ungetc(ch, in); k = read_int(); f |= f_key; }
1418 bail(BCAT_ERR, "unexpected character `%c'", ch);
1423 check_file(DIR_OUT);
1427 /* Free the current tree and all of the stacked trees. */
1428 free_tree(&cur.root);
1429 for (i = 0; i < sp; i++) free_tree(&stack[i].root);
1431 /* Work through the list. Anything here has mysteriously gone missing
1432 * from all of the trees we've been tracking.
1435 fputs("leaked nodes...", out);
1436 for (node = nodes; node; node = node->next)
1437 fprintf(out, "\t#0x%08lx %lu\n", node->seq, node->k);
1438 bail(BCAT_BUG, "leaked nodes");
1441 /* The user is expected to clear the stack. */
1442 if (sp) bail(BCAT_ERR, "stack not empty");
1444 /* Close the files and make sure all the output actually got out. */
1445 close_file(DIR_IN); close_file(DIR_OUT);
1447 /* It's all over. */
1458 /*----- That's all, folks -------------------------------------------------*/