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_CHECK tree__name(check)
83 #define TREE_HEIGHT tree__name(height)
84 #define TREE_LOOKUP tree__name(lookup)
85 #define TREE_PROBE tree__name(probe)
86 #define TREE_INITITER tree__name(inititer)
87 #define TREE_NEXT tree__name(next)
88 #define TREE_INITRITER tree__name(initriter)
89 #define TREE_PREV tree__name(prev)
90 #define TREE_CURRENT tree__name(current)
91 #define TREE_ROOTPATH tree__name(rootpath)
92 #define TREE_UPPATH tree__name(uppath)
93 #define TREE_LEFTPATH tree__name(leftpath)
94 #define TREE_RIGHTPATH tree__name(rightpath)
95 #define TREE_FIRSTPATH tree__name(firstpath)
96 #define TREE_LASTPATH tree__name(lastpath)
97 #define TREE_NEXTPATH tree__name(nextpath)
98 #define TREE_PREVPATH tree__name(prevpath)
99 #define TREE_BEFOREPATH tree__name(beforepath)
100 #define TREE_AFTERPATH tree__name(afterpath)
101 #define TREE_ASCEND tree__name(ascend)
102 #define TREE_INSERT tree__name(insert)
103 #define TREE_REMOVE tree__name(remove)
104 #define TREE_JOIN tree__name(join)
105 #define TREE_SPLIT tree__name(split)
106 #define TREE_SPLITAT tree__name(splitat)
107 #define TREE_UNISECT tree__name(unisect)
108 #define TREE_DIFFSECT tree__name(diffsect)
112 # define HELSE(x, y) x
116 # define HELSE(x, y) y
120 /*----- Preliminaries -----------------------------------------------------*/
122 static FILE *in, *out;
128 static NORETURN PRINTF_LIKE(2, 3)
129 void bail(unsigned cat, const char *msg, ...)
131 /* Write a`printf'-formatted message MSG to standard error and quit the
132 * program. CAT is a constant which indicates the reason: `BCAT_FAIL'
133 * indicates an environmental problem (e.g., insufficient memory);
134 * `BCAT_ERR' indicates a user error; `BCAT_BUG' indicates a bug detected
135 * in the tree implementation. The category determines the token printed
136 * before the message. Also, if this is a fuzzing build, `BCAT_BUG'
137 * results in a call to `abort' rather than `exit' to let the fuzzer know
138 * that it should claim a win.
145 case BCAT_FAIL: fputs("failure: ", stderr); break;
146 case BCAT_ERR: fputs("error: ", stderr); break;
147 case BCAT_BUG: fputs("BUG: ", stderr); break;
149 fprintf(stderr, "(unknown bail code %u): ", cat);
153 va_start(ap, msg); vfprintf(stderr, msg, ap); va_end(ap);
156 if (pos >= 0) fprintf(stderr, " (input pos = %ld)", pos);
159 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
160 if (cat == BCAT_BUG) abort();
165 enum { DIR_IN, DIR_OUT };
166 /* Direction codes for `check_file', `open_file', and `close_file'. */
168 static void check_file(unsigned dir)
170 /* Check that the file indicated by DIR is free of errors. If DIR is
171 * `DIR_OUT' then buffered output is flushed.
180 case DIR_IN: fp = in; act = "read"; break;
181 case DIR_OUT: fp = out; act = "write"; f |= f_flush; break;
185 if (((f&f_flush) && fflush(fp)) || ferror(fp))
186 bail(BCAT_ERR, "%s error: %s", act, strerror(errno));
192 static void close_file(unsigned dir)
194 /* Close the file indicated by DIR. Check that it was in a good state
198 FILE **fp_inout, *fp, *stdfp;
201 case DIR_IN: fp_inout = ∈ stdfp = stdin; break;
202 case DIR_OUT: fp_inout = &out; stdfp = stdout; break;
208 if (fp != stdfp) fclose(fp);
213 static void open_file(const char *file, unsigned dir)
215 /* Open FILE as the current input or output file, as indicated by DIR. */
217 FILE **fp_inout, *fp, *stdfp;
218 const char *mode, *what;
224 fp_inout = ∈ stdfp = stdin; mode = "r"; what = "input";
228 fp_inout = &out; stdfp = stdout; mode = "w"; what = "output";
233 if (file[0] == '-' && !file[1]) {
234 if (((f&f_chkeof) && feof(stdfp)) || ferror(stdfp))
235 bail(BCAT_ERR, "already consumed standard %s", what);
238 fp = fopen(file, mode);
240 bail(BCAT_ERR, "failed to open %s file `%s': %s",
241 what, file, strerror(errno));
248 static unsigned long read_int(void)
250 /* Read and return an integer from the intput file.
252 * These are used as a search keys, indices, or weights.
258 /* For some stupid reason, there isn't a `scanf' format which just reads
259 * an unsigned integer with an arbitrary radix prefix. The `%d' format
260 * does the right thing for a signed value, but not unsigned. So we have
263 do ch = getc(in); while (isspace(ch));
264 if (!isdigit(ch)) bail(BCAT_ERR, "integer expected, found `%c'", ch);
267 if (fscanf(in, "%lu", &k) != 1)
268 bail(BCAT_ERR, "integer expected, scanf failed");
271 if (ch == 'x' || ch == 'X') {
272 if (fscanf(in, "%lx", &k) != 1)
273 bail(BCAT_ERR, "integer expected, scanf failed");
274 } else if ('0' <= ch && ch <= '7') {
276 if (fscanf(in, "%lo", &k) != 1)
277 bail(BCAT_ERR, "integer expected, scanf failed");
279 { ungetc(ch, in); k = 0; }
284 static unsigned long seed = 0xacd1051a;
285 /* Seed for random number generator. Don't set this to zero. */
287 #if TREE == TREAP || defined(FLAGMASK)
288 static unsigned long randwt(void)
290 /* Return a pseudo-random integer.
292 * We have our own -- currently a rather simple LFSR -- so that the test
293 * results are reproducible on all targets.
296 unsigned long x = seed, m;
298 #define RANDPOLY 0x915717d7
300 for (i = 0; i < 32; i++) {
301 m = ((seed >> 31)&1) - 1;
302 seed = (seed << 1) ^ (m&RANDPOLY);
304 seed &= 0xffffffff; return (x);
310 static const char *rctag(int rc)
312 /* Return the name of return code RC as a string. */
314 static const char *const tagtab[] = {
315 #define TAG(tag, msg) #tag,
321 if (XYLA_RC_LOSELIMIT <= rc && rc < XYLA_RC_WINLIMIT)
322 return (tagtab[rc - XYLA_RC_LOSELIMIT]);
324 return ("#<unknown libxyla error code>");
327 /*----- Node definition ---------------------------------------------------*/
330 /* Our test node structure. */
332 TREE_NODEPFX; /* tree node control data */
334 unsigned fref; /* reference value for user flags */
336 struct my_node *next, *prev; /* links in active nodes list */
337 size_t n; /* number of nodes in this subtree */
338 unsigned long seq, k; /* sequence number and key */
340 union my_nodeu { struct my_node my; TREE_NODEUSFX; };
343 #define MY_NODE(node) \
344 CONVERT_CAREFULLY(struct my_node *, struct xyla_bt_node *, node)
345 #define MY_NODEU(node) \
346 CONVERT_CAREFULLY(union my_nodeu *, struct xyla_bt_node *, node)
347 #define CONST_MY_NODE(node) \
348 CONVERT_CAREFULLY(const struct my_node *, \
349 const struct xyla_bt_node *, \
351 #define CONST_MY_NODEU(node) \
352 CONVERT_CAREFULLY(const union my_nodeu *, \
353 const struct xyla_bt_node *, \
356 #define MY_SPLAY_NODE(node) \
357 CONVERT_CAREFULLY(struct my_node *, struct xyla_splay_node *, node)
359 #define TREE_NODE(node) \
360 CONVERT_CAREFULLY(struct NODE *, struct xyla_bt_node *, node)
361 #define CONST_TREE_NODE(node) \
362 CONVERT_CAREFULLY(const struct NODE *, \
363 const struct xyla_bt_node *, \
366 static unsigned long nodeseq = 0; /* next node sequence number */
367 static struct my_node *nodes = 0; /* list of active nodes */
369 static int my_ordnav(struct xyla_bt_nodecls *cls,
370 const struct xyla_bt_node *node, void *arg)
372 /* Navigation function to search by key. */
374 const struct my_node *mynode = CONST_MY_NODE(node);
375 unsigned long *key = arg;
377 if (*key < mynode->k) return (-1);
378 else if (*key > mynode->k) return (+1);
382 static int my_idxnav(struct xyla_bt_nodecls *cls,
383 const struct xyla_bt_node *node, void *arg)
385 /* Navigation function to search by index. */
387 const struct my_node *myleft = CONST_MY_NODE(node->left);
388 unsigned long *i_inout = arg, i = *i_inout, n;
392 if (i < n) return (-1);
396 *i_inout = i - 1; return (+1);
399 static void my_upd(struct xyla_bt_nodecls *cls, struct xyla_bt_node *node)
401 /* Update function to maintain node count. */
404 *mynode = MY_NODE(node),
405 *left = MY_NODE(mynode->bt.left), *right = MY_NODE(mynode->bt.right);
407 mynode->n = (left ? left->n : 0) + (right ? right->n : 0) + 1;
410 static const void *my_key(struct xyla_bt_nodecls *cls,
411 const struct xyla_bt_node *node)
413 /* Return search key for node. */
415 return (&CONST_MY_NODE(node)->k);
418 #define CHKF_OUTMASK 0x3000u /* check flags: output mode mask */
419 #define CHKOUT_NONE 0x0000u /* no output */
420 #define CHKOUT_LIST 0x1000u /* structure list */
421 #define CHKOUT_KEYS 0x2000u /* plain list of keys */
422 #define CHKOUT_DUMP 0x3000u /* full multiline dump */
423 #define CHKF_SANITY 0x0800u /* checking user input */
425 struct my_checkstate {
426 /* Checking state. */
428 unsigned f; /* flags */
429 #define CF_SPC 1u /* put space before next output */
433 /* Per-node tracking information */
435 struct xyla_bt_ordinfo _ord; /* order checking state */
436 int lv; /* indentation level */
439 static int my_check(unsigned op, const struct xyla_bt_check *chk)
441 /* Main checking and walking function. */
443 const struct my_node *node = CONST_MY_NODE(chk->node), *left, *right;
444 struct my_checkstate *st = chk->state;
446 *node_info = chk->node_info,
447 *parent_info = chk->parent_info;
449 int rc = XYLA_RC_OK, subrc;
453 /* Reached a null link. Some dump types print something distinctive
454 * if the entire tree is empty, which we can determine easily enough.
455 * The `CHKOUT_LIST' format needs to indicate all null links
459 switch (chk->f&CHKF_OUTMASK) {
466 if (chk->pos == XYLA_BTPOS_ROOT) fputs("(empty tree)", out);
469 if (chk->pos == XYLA_BTPOS_ROOT)
470 fputs("\t(tree empty)\n", out);
475 case XYLA_CHKOP_BEFORE:
476 /* Called before the children. Set up our indentation level, which
477 * is a little tricky for red-black trees, because we align black
485 if (!(node->rb.f&XYLA_RBF_RED))
486 node_info->lv = (parent_info->lv + 2)&~1;
489 node_info->lv = parent_info->lv + 1;
490 if ((chk->f&CHKF_OUTMASK) == CHKOUT_LIST) putc('(', out);
494 /* Called after the left subtree. This is where the main printing
498 switch (chk->f&CHKF_OUTMASK) {
504 if (!(node->rb.f&XYLA_RBF_RED)) putc('*', out);
506 fprintf(out, "0x%08lx$ ", (unsigned long)node->trp.wt);
508 fprintf(out, "%lu", node->k);
512 if (!(st->f&CF_SPC)) st->f |= CF_SPC;
514 fprintf(out, "%lu", node->k);
517 fprintf(out, "\t#0x%08lx (n = %3lu)\t%*s",
518 node->seq, (unsigned long)node->n, 4*node_info->lv, "");
520 fprintf(out, "(%c)", AVL_BALCH(node));
522 fprintf(out, "(%c)", node->rb.f&XYLA_RBF_RED ? ' ' : '*');
527 fprintf(out, " 0x%08lx$", (unsigned long)node->trp.wt);
529 fprintf(out, " %lu\n", node->k);
534 case XYLA_CHKOP_AFTER:
535 /* Called after the right subtree. This is where we check that the
536 * node count is correct and that the user flags are faithfully
540 left = CONST_MY_NODE(chk->left);
541 right = CONST_MY_NODE(chk->right);
542 want = (left ? left->n : 0) + (right ? right->n : 0) + 1;
543 if (node->n != want) {
545 xyla_bt_bughdr("MY_TREE", chk->root, chk->fp);
546 xyla_bt_printnode(chk->cls, &node->bt, chk->fp);
547 fprintf(chk->fp, " count %lu /= %lu\n",
548 (unsigned long)node->n, (unsigned long)want);
553 if ((node->T.f&FLAGMASK) != node->fref) {
555 xyla_bt_bughdr("MY_TREE", chk->root, chk->fp);
556 xyla_bt_printnode(chk->cls, &node->bt, chk->fp);
557 fprintf(chk->fp, " flags %x /= %x\n",
558 node->T.f&FLAGMASK, node->fref);
563 if ((chk->f&CHKF_OUTMASK) == CHKOUT_LIST) putc(')', out);
567 /* Check that the ordering is respected. */
568 subrc = xyla_bt_chkorder(op, chk);
569 if (subrc) { rc = subrc; if (subrc != XYLA_RC_BAD) goto end; }
576 static void my_ident(struct xyla_bt_nodecls *cls,
577 const struct xyla_bt_node *node, FILE *fp)
579 /* Node identification function. */
581 const struct my_node *my_node = CONST_MY_NODE(node);
583 fprintf(fp, "#<node #0x%08lx ", my_node->seq);
585 fprintf(fp, "0x%08lx$ ", (unsigned long)my_node->trp.wt);
587 fprintf(fp, "%lu>", my_node->k);
590 static const struct xyla_bt_chkops my_ops = {
591 /* Node class operations table. */
593 { sizeof(my_ops), my_upd },
594 { my_ordnav, my_key },
595 { my_check, sizeof(struct my_nodeinfo), my_ident },
598 /*----- Node utilities ----------------------------------------------------*/
600 static union my_nodeu *make_node(unsigned long k)
602 /* Return a fresh node with key K. */
604 union my_nodeu *node;
606 node = malloc(sizeof(*node));
607 if (!node) bail(BCAT_FAIL, "out of memory");
609 node->my.fref = node->my.T.f = randwt()&FLAGMASK;
611 node->my.n = 99; node->my.seq = nodeseq++; node->my.k = k;
612 if (nodes) nodes->prev = &node->my;
613 node->my.next = nodes; node->my.prev = 0; nodes = &node->my;
617 static void free_node(struct my_node *node)
619 /* Free a node which we don't want any more. */
621 struct my_node *next = node->next, *prev = node->prev;
623 if (next) next->prev = prev;
624 if (prev) prev->next = next;
629 static void free_tree(struct xyla_bt_node **root)
631 /* Free the tree rooted at ROOT. */
633 while (*root) free_node(xyla_bt_severfirst(root));
636 static void check_dump(struct xyla_bt_nodecls *cls,
637 struct xyla_bt_node *const *root HARG(int expht),
640 /* Run a check or dump of the tree at ROOT.
642 * If output is requested, then suppress checking output. If there were
643 * problems them report them afterwards. If we didn't have a full dump
644 * and problems were found, then print a dump afterwards.
647 struct my_checkstate st;
652 if ((f&CHKF_OUTMASK) == CHKOUT_DUMP) {
653 fputs("tree dump", out);
654 H( if (expht != -1) fprintf(out, ", ht = %d", expht); )
657 rc = TREE_CHECK(cls, root,
658 (f&CHKF_OUTMASK) == CHKOUT_NONE ? stderr : 0, f
661 if ((f&CHKF_OUTMASK) == CHKOUT_NONE) {
662 ht = TREE_HEIGHT(CONST_TREE_NODE(*root));
664 xyla_bt_bughdr("MY_TREE", root, stderr);
665 fprintf(stderr, "expected height %d /= %d\n", expht, ht);
666 if (!rc) rc = XYLA_RC_BAD;
670 if ((f&CHKF_OUTMASK) == CHKOUT_LIST || (f&CHKF_OUTMASK) == CHKOUT_KEYS)
673 if ((f&CHKF_OUTMASK) != CHKOUT_NONE)
674 TREE_CHECK(cls, root, stderr, CHKOUT_NONE HARG(expht), &st);
675 if ((f&CHKF_OUTMASK) != CHKOUT_DUMP) {
676 fputs("offending tree...\n", out);
677 TREE_CHECK(cls, root, 0, CHKOUT_DUMP HARG(expht), &st);
679 if (f&CHKF_SANITY) bail(BCAT_ERR, "invalid tree: %s", xyla_strerror(rc));
680 else bail(BCAT_BUG, "check failed: %s", xyla_strerror(rc));
684 static union my_nodeu *copy_tree(const union my_nodeu *node)
686 /* Make a copy of a tree.
688 * The nodes have new sequence numbers, but should otherwise be identical.
691 union my_nodeu *copy, *left, *right;
693 if (!node) return (0);
694 left = copy_tree(CONST_MY_NODEU(node->bt.left));
695 right = copy_tree(CONST_MY_NODEU(node->bt.right));
696 copy = make_node(node->my.k);
698 copy->my.T.f = node->my.T.f;
699 copy->my.fref = node->my.fref;
701 copy->my.spl.parent = 0;
702 if (left) left->my.spl.parent = ©->spl;
703 if (right) right->my.spl.parent = ©->spl;
705 copy->my.trp.wt = node->my.trp.wt;
707 copy->my.bt.left = left ? &left->bt : 0;
708 copy->my.bt.right = right ? &right->bt : 0;
709 copy->my.n = node->my.n;
713 static int ascend_index(struct xyla_bt_node *node,
714 struct xyla_bt_node *parent,
715 struct xyla_bt_node *sibling,
716 unsigned pos, void *arg)
718 /* Ascension function to determine a node's index. */
720 struct my_node *mysib = MY_NODE(sibling);
721 size_t *i_inout = arg;
723 if (pos == XYLA_BTPOS_RIGHT)
724 *i_inout += 1 + (mysib ? mysib->n : 0);
728 static size_t path_index(struct PATH *path)
730 /* Return the index of a node given a full path to it. */
732 struct my_node *mynode, *myleft;
735 mynode = TREE_CURRENT(path);
737 myleft = MY_NODE(mynode->bt.left);
738 if (myleft) i += myleft->n;
741 TREE_ASCEND(ascend_index, path, &i);
745 static int full_path_p(struct PATH *path)
747 /* Return wehther PATH is full. */
749 return (!!TREE_CURRENT(path));
752 static struct xyla_bt_node *read_tree(HELSE(int *ht_out, void))
754 /* Read a tree structure description from the input file and return
755 * a pointer to the root.
757 * If the tree has a notion of height, then return the height in *HT_OUT.
760 union my_nodeu *node;
761 struct my_node *left, *right;
764 H( int ht; int lht; int rht; )
768 unsigned f = XYLA_RBF_RED;
770 size_t wt = 0, lwt, rwt;
775 /* Check for a `_' indicating a nil subtree, or the open paren. */
776 do ch = getc(in); while (isspace(ch));
777 if (ch == '_') { H( *ht_out = 0; ) return (0); }
778 else if (ch != '(') bail(BCAT_ERR, "expected `(', found `%c'", ch);
780 /* Read the left subtree. */
781 left = MY_NODE(read_tree(H(&lht)));
783 /* Read flags before the node proper. */
785 do ch = getc(in); while (isspace(ch));
787 if (f == 3 && ch == '-') f = XYLA_AVLBAL_LBIAS;
788 else if (f == 3 && ch == '=') f = XYLA_AVLBAL_EVEN;
789 else if (f == 3 && ch == '+') f = XYLA_AVLBAL_RBIAS;
792 if (ch == '*') f &= ~XYLA_RBF_RED;
798 /* Read the node key. */
799 ungetc(ch, in); k = read_int();
800 do ch = getc(in); while (isspace(ch));
802 /* If this is a treap, then maybe that was actually the node weight.
803 * If so, stash it and read the real key.
806 if (ch == '$') { wt = k; f |= f_weight; k = read_int(); }
811 /* Read the right subtree. */
812 right = MY_NODE(read_tree(H(&rht)));
814 /* Expect the closing paren. */
815 do ch = getc(in); while (isspace(ch));
816 if (ch != ')') bail(BCAT_ERR, "expected `)', found `%c'", ch);
820 node->my.bt.left = left ? &left->bt : 0;
821 node->my.bt.right = right ? &right->bt : 0;
822 node->my.n = (left ? left->n : 0) + (right ? right->n : 0) + 1;
824 /* Fill in the tree-specific bits. */
829 if (lht == -1 || rht == -1)
830 bail(BCAT_ERR, "missing balance annotation");
832 case -1: f = XYLA_AVLBAL_LBIAS; ht = lht + 1; break;
833 case 0: f = XYLA_AVLBAL_EVEN; ht = lht + 1; break;
834 case +1: f = XYLA_AVLBAL_RBIAS; ht = rht + 1; break;
835 default: bail(BCAT_ERR, "tree is not AVL");
838 node->my.avl.f = (node->my.avl.f&FLAGMASK) | f;
840 ht = f&XYLA_RBF_RED ? lht : lht + 1;
841 node->my.rb.f = (node->my.rb.f&FLAGMASK) | f;
843 if (left) left->spl.parent = &node->spl;
844 if (right) right->spl.parent = &node->spl;
845 node->my.spl.parent = 0;
847 lwt = left ? left->trp.wt : 0xffffffff;
848 rwt = right ? right->trp.wt : 0xffffffff;
850 node->my.trp.wt = wt;
852 wt = (((rwt < lwt ? rwt : lwt)&0xfff00000) - (1ul << 20)) | node->my.seq;
853 if (wt > lwt || wt > rwt) bail(BCAT_ERR, "tree is not a treap");
854 node->my.trp.wt = wt;
858 /* Save the subtree height. */
869 /*----- Main program ------------------------------------------------------*/
871 int main(int argc, char *argv[])
875 struct xyla_bt_nodecls mycls;
876 struct { struct xyla_bt_node *root; H( int ht; ) } cur, stack[NSTACK], u;
877 struct my_node *node = 0, *t;
878 union my_nodeu *nodeu;
879 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
880 struct my_node *x, *y;
883 struct xyla_bt_node **link;
885 struct xyla_bt_node *btnode;
891 int opt, ch, rc, sp = 0;
892 unsigned long i, j, k;
903 /* Parse command-line options. */
906 if (opt >= argc) break;
908 if (*p != '-') break;
910 if (*p == '-' && !p[1]) { opt++; break; }
912 #define GETARG do { \
914 if (opt >= argc) bail(BCAT_ERR, "missing argument"); \
919 case 'o': GETARG; open_file(p, DIR_OUT); goto next_arg;
920 default: bail(BCAT_ERR, "unknown option `-%c'", p[-1]);
928 /* Initialize my node class. */
929 mycls.ops = &my_ops.node;
931 /* Initialize the current tree. */
932 cur.root = 0; H( cur.ht = 0; )
934 /* Set up the input and output streams. */
935 if (!out) out = stdout;
936 if (opt >= argc) in = stdin;
937 else open_file(argv[opt++], DIR_IN);
938 V( xyla__verbout = out; )
941 do ch = getc(in); while (isspace(ch));
945 if (opt < argc) open_file(argv[opt++], DIR_IN);
949 case ';': /* comment */
950 do ch = getc(in); while (ch != EOF && ch != '\n');
953 case ':': /* print message */
955 ch = getc(in); if (ch == '\n' || ch == EOF) break;
965 case '+': /* insert */
966 if (!(f&f_key)) bail(BCAT_ERR, "no key");
968 node = TREE_PROBE(&mycls, &cur.root, my_ordnav, &k, &path);
970 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
971 bail(BCAT_ERR, "placed insertion is out-of-bounds for fuzzers");
973 if (full_path_p(&path)) bail(BCAT_ERR, "path full");
976 if (node) bail(BCAT_ERR, "key %lu already present", k);
977 nodeu = make_node(k);
979 if (f&f_weight) nodeu->my.trp.wt = wt;
980 else nodeu->my.trp.wt = randwt();
982 rc = TREE_INSERT(&mycls, &path, &nodeu->T);
983 H( if (rc == XYLA_RC_HTCHG) cur.ht++; else )
985 bail(BCAT_BUG, "insert returned #%s: %s",
986 rctag(rc), xyla_strerror(rc));
988 f &= ~(f_key | f_path | f_weight);
991 case '-': /* remove */
993 if (!full_path_p(&path)) bail(BCAT_ERR, "path empty");
995 if (!(f&f_key)) bail(BCAT_ERR, "no key or path");
996 node = TREE_PROBE(&mycls, &cur.root, my_ordnav, &k, &path);
997 if (!node) bail(BCAT_ERR, "key %lu not present", k);
999 rc = TREE_REMOVE(&mycls, &path);
1000 H( if (rc == XYLA_RC_HTCHG) cur.ht--; else )
1002 bail(BCAT_BUG, "remove returned #%s: %s",
1003 rctag(rc), xyla_strerror(rc));
1004 free_node(node); node = 0; f &= ~(f_key | f_path);
1007 case '#': /* populate */
1009 do ch = getc(in); while (isspace(ch));
1010 if (ch == '-') j = read_int();
1011 else { ungetc(ch, in); j = i; i = 1; }
1013 if (!TREE_PROBE(&mycls, &cur.root, my_ordnav, &i, &path)) {
1014 nodeu = make_node(i);
1016 nodeu->my.trp.wt = randwt();
1018 rc = TREE_INSERT(&mycls, &path, &nodeu->T);
1019 H( if (rc == XYLA_RC_HTCHG) cur.ht++; else )
1021 bail(BCAT_BUG, "insert returned #%s: %s",
1022 rctag(rc), xyla_strerror(rc));
1025 else if (i < j) i++;
1028 node = 0; f &= ~(f_key | f_path | f_weight);
1031 case '?': /* lookup */
1032 if (!(f&f_key)) bail(BCAT_ERR, "no key");
1033 node = TREE_LOOKUP(&mycls, &cur.root, my_ordnav, &k);
1034 f &= ~(f_key | f_path);
1037 case '@': /* probe */
1038 if (!(f&f_key)) bail(BCAT_ERR, "no key");
1039 node = TREE_PROBE(&mycls, &cur.root, my_ordnav, &k, &path);
1040 f = (f&~f_key) | f_path;
1043 case ',': /* lookup/probe by index */
1044 if (!(f&f_key)) bail(BCAT_ERR, "no key");
1048 node = TREE_LOOKUP(&mycls, &cur.root, my_idxnav, &k);
1049 f &= ~(f_key | f_path);
1052 node = TREE_PROBE(&mycls, &cur.root, my_idxnav, &k, &path);
1053 f = (f&~f_key) | f_path;
1056 bail(BCAT_ERR, "expected `?' or `@', found `%c'", ch);
1061 case 't': /* root (`top') */
1062 node = TREE_ROOTPATH(&cur.root, &path);
1063 if (!node) f &= ~f_key;
1064 else { k = node->k; f |= f_key; }
1069 if (!(f&f_path)) bail(BCAT_ERR, "no path");
1070 node = TREE_UPPATH(&pos, &path);
1072 case XYLA_BTPOS_ROOT: fputs("root\n", out); break;
1073 case XYLA_BTPOS_LEFT: fputs("left\n", out); break;
1074 case XYLA_BTPOS_RIGHT: fputs("right\n", out); break;
1075 default: bail(BCAT_BUG, "unexpected position code %u\n", pos);
1079 case 'l': /* left */
1080 if (!(f&f_path)) bail(BCAT_ERR, "no path");
1081 else if (!full_path_p(&path)) bail(BCAT_ERR, "path empty");
1082 node = TREE_LEFTPATH(&path);
1083 if (!node) f &= ~f_key;
1084 else { k = node->k; f |= f_key; }
1087 case 'r': /* right */
1088 if (!(f&f_path)) bail(BCAT_ERR, "no path");
1089 else if (!full_path_p(&path)) bail(BCAT_ERR, "path empty");
1090 node = TREE_RIGHTPATH(&path);
1091 if (!node) f &= ~f_key;
1092 else { k = node->k; f |= f_key; }
1095 case '[': /* first */
1096 node = TREE_FIRSTPATH(&cur.root, &path);
1097 if (!node) f &= ~f_key;
1098 else { k = node->k; f |= f_key; }
1102 case ']': /* last */
1103 node = TREE_LASTPATH(&cur.root, &path);
1104 if (!node) f &= ~f_key;
1105 else { k = node->k; f |= f_key; }
1109 case 'c': /* current */
1110 if (!(f&f_path)) bail(BCAT_ERR, "no path");
1111 node = TREE_CURRENT(&path);
1112 if (!node) f &= ~f_key;
1113 else { k = node->k; f |= f_key; }
1116 case '<': /* previous */
1117 if (!(f&f_path)) bail(BCAT_ERR, "no path");
1118 node = TREE_PREVPATH(&path);
1119 if (!node) f &= ~f_key;
1120 else { k = node->k; f |= f_key; }
1123 case '>': /* next */
1124 if (!(f&f_path)) bail(BCAT_ERR, "no path");
1125 node = TREE_NEXTPATH(&path);
1126 if (!node) f &= ~f_key;
1127 else { k = node->k; f |= f_key; }
1130 case '{': /* before */
1131 if (!(f&f_path)) bail(BCAT_ERR, "no path");
1132 else if (!full_path_p(&path)) bail(BCAT_ERR, "path empty");
1133 TREE_BEFOREPATH(&path); f &= ~f_key;
1136 case '}': /* after */
1137 if (!(f&f_path)) bail(BCAT_ERR, "no path");
1138 else if (!full_path_p(&path)) bail(BCAT_ERR, "path empty");
1139 TREE_AFTERPATH(&path); f &= ~f_key;
1142 case '*': /* clear */
1143 node = 0; f &= ~(f_key | f_path);
1146 case '(': /* push */
1147 if (sp >= NSTACK) bail(BCAT_ERR, "stack full");
1148 stack[sp++] = cur; cur.root = 0; H( cur.ht = 0; )
1149 node = 0; f &= ~(f_key | f_path | f_weight);
1152 case '"': /* duplicate */
1153 if (sp >= NSTACK) bail(BCAT_ERR, "stack full");
1155 cur.root = (struct xyla_bt_node *)copy_tree(MY_NODEU(cur.root));
1156 node = 0; f &= ~(f_key | f_path | f_weight);
1160 if (!sp) bail(BCAT_ERR, "stack empty");
1161 free_tree(&cur.root); cur = stack[--sp];
1162 node = 0; f &= ~(f_key | f_path | f_weight);
1165 case '%': /* swap */
1166 if (!sp) bail(BCAT_ERR, "stack empty");
1167 u = stack[sp - 1]; stack[sp - 1] = cur; cur = u;
1168 node = 0; f &= ~(f_key | f_path | f_weight);
1171 case '~': /* join */
1172 if (!sp) bail(BCAT_ERR, "stack empty");
1173 if (!(f&f_key)) t = 0;
1175 t = &make_node(k)->my;
1177 if (f&f_weight) t->trp.wt = wt;
1178 else t->trp.wt = randwt();
1181 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1182 x = TREE_LASTPATH(&stack[sp - 1].root, &path);
1183 y = TREE_FIRSTPATH(&cur.root, &path);
1184 if (!t ? x && y && x->k >= y->k
1185 : (x && x->k >= t->k) || (y && t->k >= y->k))
1186 bail(BCAT_ERR, "invalid ordering");
1188 rc = TREE_JOIN(&mycls, &cur.root HARG(&cur.ht),
1189 &stack[sp - 1].root HARG(stack[sp - 1].ht),
1191 &cur.root HARG(cur.ht));
1193 bail(BCAT_BUG, "join returned #%s: %s",
1194 rctag(rc), xyla_strerror(rc));
1195 sp--; node = 0; f &= ~(f_key | f_path | f_weight);
1198 case '/': /* split */
1199 if (sp > NSTACK - 2) bail(BCAT_ERR, "stack full");
1200 if (!(f&f_path)) bail(BCAT_ERR, "no path");
1201 rc = TREE_SPLIT(&mycls, &stack[sp].root HARG(&stack[sp].ht),
1203 &cur.root HARG(&cur.ht),
1206 bail(BCAT_BUG, "split returned #%s: %s",
1207 rctag(rc), xyla_strerror(rc));
1208 node = MY_NODE(btnode);
1209 if (node) node->n = 1;
1210 stack[sp + 1].root = node ? &node->bt : 0;
1211 H( stack[sp + 1].ht = node ? 1 : 0; )
1212 sp += 2; f &= ~(f_key | f_path | f_weight);
1215 case '|': /* union/intersection */
1216 if (!sp) bail(BCAT_ERR, "stack empty");
1217 rc = TREE_UNISECT(&mycls, &cur.root HARG(&cur.ht),
1218 &stack[sp - 1].root HARG(&stack[sp - 1].ht),
1219 &stack[sp - 1].root HARG(stack[sp - 1].ht),
1220 &cur.root HARG(cur.ht));
1222 bail(BCAT_BUG, "unisect returned #%s: %s",
1223 rctag(rc), xyla_strerror(rc));
1224 node = 0; f &= ~(f_key | f_path | f_weight);
1227 case '\\': /* difference/intersection */
1228 if (!sp) bail(BCAT_ERR, "stack empty");
1229 if (sp >= NSTACK) bail(BCAT_ERR, "stack full");
1230 rc = TREE_DIFFSECT(&mycls, &cur.root HARG(&cur.ht),
1231 &stack[sp].root HARG(&stack[sp].ht),
1232 &cur.root HARG(cur.ht),
1233 &stack[sp - 1].root);
1235 bail(BCAT_BUG, "diffsect returned #%s: %s",
1236 rctag(rc), xyla_strerror(rc));
1237 sp++; node = 0; f &= ~(f_key | f_path | f_weight);
1240 case '^': /* ascend */
1241 if (!(f&f_path)) bail(BCAT_ERR, "no path");
1242 fprintf(out, "%lu\n", (unsigned long)path_index(&path));
1245 case 'k': /* print key */
1248 if (f&f_weight) fprintf(out, "0x%08lx$ ", (unsigned long)wt);
1250 fprintf(out, "%lu\n", k);
1252 else fputs("(no key)\n", out);
1255 case 'n': /* print node */
1256 if (!node) fputs("(nil)\n", out);
1257 else fprintf(out, "#<node #0x%08lx %lu>\n", node->seq, node->k);
1260 case 'p': /* print path */
1262 fprintf(out, "(nil)\n");
1266 if (path.root == &cur.root) fputs("#<link root -> ", out);
1267 else fprintf(out, "#<link address %p -> ... ", (void *)path.root);
1268 t = MY_SPLAY_NODE(path.node);
1270 fputs("(nil)>", out);
1272 fprintf(out, "node #0x%08lx %lu>", t->seq, t->k);
1274 fprintf(out, " #<node #0x%08lx %lu %s> -> (nil)",
1276 path.f == XYLA_SPF_LEFT ? "left" : "right");
1280 for (i = 0, t = 0; i <= path.k; i++) {
1281 if (i) putc(' ', out);
1283 if (!t && link == &cur.root)
1284 fputs("#<link root -> ", out);
1285 else if (t && link == &t->bt.left)
1286 fprintf(out, "#<link node #0x%08lx %lu left -> ",
1288 else if (t && link == &t->bt.right)
1289 fprintf(out, "#<link node #0x%08lx %lu right -> ",
1292 fprintf(out, "#<link address %p -> ", (void *)link);
1294 if (!t) fputs("(nil)", out);
1295 else fprintf(out, "node #0x%08lx %lu", t->seq, t->k);
1299 putc(')', out); putc('\n', out);
1303 case 'i': /* iterate */
1304 TREE_INITITER(cur.root, &it); f |= f_first;
1306 t = TREE_NEXT(&it); if (!t) break;
1307 if (f&f_first) f &= ~f_first;
1308 else putc(' ', out);
1309 fprintf(out, "%lu", t->k);
1311 if (f&f_first) fputs("(empty tree)", out);
1315 case 'j': /* reverse iterate */
1316 TREE_INITRITER(cur.root, &rit); f |= f_first;
1318 t = TREE_PREV(&rit); if (!t) break;
1319 if (f&f_first) f &= ~f_first;
1320 else putc(' ', out);
1321 fprintf(out, "%lu", t->k);
1323 if (f&f_first) fputs("(empty tree)", out);
1327 case 'K': /* list keys */
1328 check_dump(&mycls, &cur.root HARG(cur.ht), CHKOUT_KEYS);
1331 case 'L': /* list */
1332 check_dump(&mycls, &cur.root HARG(cur.ht), CHKOUT_LIST);
1335 case 'D': /* dump */
1336 check_dump(&mycls, &cur.root HARG(cur.ht), CHKOUT_DUMP);
1339 case '!': /* check */
1340 check_dump(&mycls, &cur.root HARG(cur.ht), CHKOUT_NONE);
1343 case '=': /* assign */
1344 free_tree(&cur.root);
1345 cur.root = read_tree(H(&cur.ht));
1347 if (cur.ht == -1) cur.ht = xyla_avl_height(AVL_NODE(cur.root));
1349 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
1350 check_dump(&mycls, &cur.root HARG(cur.ht), CHKOUT_NONE | CF_SANITY);
1352 node = 0; f &= ~(f_key | f_path | f_weight);
1355 case 'S': /* seed */
1359 case 'V': /* verbose */
1363 if (ch == EOF || isspace(ch)) break;
1367 ch = getc(in); if (ch == '\n' || ch == EOF) break;
1368 NV( putc(ch, out); )
1370 NV( putc('\n', out); )
1372 case 'i': V( v |= XYLA__VF_ITER; ) break;
1373 case 'p': V( v |= XYLA__VF_PATH; ) break;
1374 case '@': V( v |= XYLA__VF_PROBE; ) break;
1375 case '+': V( v |= XYLA__VF_INSERT; ) break;
1376 case '-': V( v |= XYLA__VF_REMOVE; ) break;
1377 case '/': V( v |= XYLA__VF_SPLIT; ) break;
1378 case '~': V( v |= XYLA__VF_JOIN; ) break;
1379 case '|': V( v |= XYLA__VF_UNISECT; ) break;
1380 case '\\': V( v |= XYLA__VF_DIFFSECT; ) break;
1382 case 'Z': V( v |= XYLA__VF_SPLAY; ) break;
1384 default: bail(BCAT_ERR, "unexpected category `%c'", ch);
1387 V( xyla__verbose = v; )
1390 case 'B': /* rebalance */
1392 xyla_splay_rebalance(&mycls, &cur.root);
1396 case 'Z': /* splay */
1397 if (!(f&f_path)) bail(BCAT_ERR, "no path");
1399 xyla_splay_splay(&mycls, &path);
1404 case '$': /* weight */
1405 if (!(f&f_key)) bail(BCAT_ERR, "no key");
1406 wt = k; f = (f&~f_key) | f_weight;
1411 if (isdigit(ch)) /* set key */
1412 { ungetc(ch, in); k = read_int(); f |= f_key; }
1414 bail(BCAT_ERR, "unexpected character `%c'", ch);
1419 check_file(DIR_OUT);
1423 /* Free the current tree and all of the stacked trees. */
1424 free_tree(&cur.root);
1425 for (i = 0; i < sp; i++) free_tree(&stack[i].root);
1427 /* Work through the list. Anything here has mysteriously gone missing
1428 * from all of the trees we've been tracking.
1431 fputs("leaked nodes...", out);
1432 for (node = nodes; node; node = node->next)
1433 fprintf(out, "\t#0x%08lx %lu\n", node->seq, node->k);
1434 bail(BCAT_BUG, "leaked nodes");
1437 /* The user is expected to clear the stack. */
1438 if (sp) bail(BCAT_ERR, "stack not empty");
1440 /* Close the files and make sure all the output actually got out. */
1441 close_file(DIR_IN); close_file(DIR_OUT);
1443 /* It's all over. */
1454 /*----- That's all, folks -------------------------------------------------*/