Пример #1
0
void Node_add_sib(Node *sib1, Node *sib2)
{
  assert_not(sib1, NULL);
  assert_not(sib2, NULL);

  LIST_ADD(Node, sib1, sib2, sibling);
  sib2->parent = sib1->parent;
}
Пример #2
0
void Node_add_child(Node *parent, Node *child) 
{
  assert_not(parent, NULL);
  assert_not(child, NULL);

  LIST_ADD(Node, parent->child, child, sibling);
  child->parent = parent;
}
Пример #3
0
void Node_name(Node *node, bstring name) 
{
  assert_not(node, NULL);
  assert_not(name, NULL);

  if(node->name) {
    bdestroy(node->name);
  }

  node->name = name;
}
Пример #4
0
void Hub_broadcast(Hub *hub, Message *msg)
{
  Member *te = NULL;
  struct sglib_Member_iterator it;
  assert_not(msg, NULL);
  assert_not(hub, NULL);

  dbg("BROADCASTING MESSAGE:");
  Message_dump(msg);

  for(te=sglib_Member_it_init(&it,hub->members); te!=NULL; te=sglib_Member_it_next(&it)) {
    Member_send_msg(te, msg);
  } 
}
Пример #5
0
inline int io_ensure_sbuf(sbuf *buf, int fd, size_t length)
{
  int rc = 1;
  size_t ntrans = 0;

  assert_not(buf, NULL);
  assert(isvalidsock(fd) && "not a valid socket");

  // make sure there's enough for the payload
  while((size_t)sbuf_data_avail(buf) < length) {
    // need to read more
    rc = io_read_sbuf(fd, buf, &ntrans);
    if(!rc || errno) {
      if(errno && errno != EBADF) {
        fail("unexpected error reading from socket");
      }
      errno = 0;
      break;
    }
    check(ntrans != 0, "attempted read returned 0 length");

    if(ntrans < length) {
      dbg("attempted read returned %zu rather than %zu with %jd avail", ntrans, length, (intmax_t)sbuf_data_avail(buf));
    }
  }

  ensure(return rc);
}
Пример #6
0
Node *Node_parse(bstring buf)
{
  size_t from = 0;

  assert_not(buf, NULL);

  return Node_parse_seq(buf, &from);
}
Пример #7
0
void Node_catbstr(bstring str, Node *d, char sep, int follow_sibs) 
{
  int rc = 0;

  assert_not(str, NULL);

  if(d == NULL) return;

  if(d->sibling != NULL && follow_sibs) {
    Node_catbstr(str, d->sibling, sep, follow_sibs);
  }

  if(d->type == TYPE_GROUP) {
    rc = bformata(str, "[%c", sep);
    assert(rc == BSTR_OK);
  }

  if(d->child != NULL) {
    // we always follow siblings on the children
    Node_catbstr(str, d->child, sep, 1);
  }

  switch(d->type) {
    case TYPE_BLOB:
      bformata(str, "'%zu:" , blength(d->value.string));
      bconcat(str, d->value.string);
      bformata(str, "\'%c", sep);
      break;
    case TYPE_STRING:
      bformata(str, "\"%s\"%c" , bdata(d->value.string), sep);
      break;
    case TYPE_NUMBER:
      bformata(str, "%llu%c", d->value.number, sep);
      break;
    case TYPE_FLOAT:
      bformata(str, "%f%c", d->value.floating, sep);
      break;
    case TYPE_GROUP: 
      if(!d->name || bchar(d->name, 0) == '@') {
        rc = bformata(str, "]%c", sep);
        assert(rc == BSTR_OK);
      }
      break;
    case TYPE_INVALID: // fallthrough
    default:
      assert(!"invalid type for node");
      break;
  }

  if(d->name != NULL) {
    rc = bformata(str, "%s%c", bdata(d->name), sep); 
    assert(rc == BSTR_OK);
  }
}
Пример #8
0
int Hub_init(char *argv_0)
{
  assert_not(argv_0, NULL);

  check(CryptState_init(), "failed to init crypto");

  server_init(argv_0);

  return 1;
  on_fail(return 0);
}
Пример #9
0
void Hub_connection_handler(void *data)
{
  assert_not(data, NULL);

  MyriadClient *client = (MyriadClient *)data;
  Hub *hub = (Hub *)client->server->data;

  Hub_exec(hub, UEv_OPEN);
  Hub_queue_conn(hub, client);
  Hub_exec(hub, UEv_CLOSE);
}
Пример #10
0
int FrameSource_send(FrameSource frame, bstring header, Node *msg, int flush)
{
  int rc = 0;
  bstring data = Node_bstr(msg, 1);
  uint16_t msg_len = 0;
  size_t nout;

  assert_not(header, NULL);
  assert_not(msg, NULL);

  // convert the msg to a bstring to find out the full size
  check(data, "failed to build bstring from msg Node");

  // make sure the sbuf has enough space
  msg_len = blength(header) + blength(data);

  if(sbuf_space_avail(frame.out) < msg_len) {
    rc = io_flush_sbuf(frame.fd, frame.out, &nout);
    check(rc, "failed to flush sbuf to make room");
    check_then(sbuf_space_avail(frame.out) >= msg_len, "insufficient space available in sbuf", rc = 0);
  }

  // write the length, then header, then msg
  rc = sbuf_put_htons(frame.out, msg_len);
  check(rc, "failed to write length");

  rc = sbuf_write_bstr(frame.out, header);
  check(rc, "failed to write header");

  rc = sbuf_write_bstr(frame.out, data);
  check(rc, "failed to write body");
 
  if(flush) {
    rc = io_flush_sbuf(frame.fd, frame.out, &nout);
    check(rc, "failed to write to frame.fd");
  }

  ensure(bdestroy(data); return rc);
}
Пример #11
0
Node *Node_parse_seq(bstring buf, size_t *from)
{
  size_t nread = *from;
  stackish_parser parser;
  stackish_parser_init(&parser);
  char last = bchar(buf, blength(buf) - 1);

  assert_not(buf, NULL);
  assert_not(from, NULL);

  // make sure that the string ends in at least one space of some kind for the parser
  check(last == ' ' || last == '\n' || last == '\t', "buffer doesn't end in either ' \\n\\t'");

  nread = stackish_parser_execute(&parser, (const char *)bdata(buf), blength(buf), nread);
  check(!stackish_parser_has_error(&parser), "parsing error on stackish string");

  while(stackish_more(&parser) && !stackish_parser_is_finished(&parser) && nread < (size_t)blength(buf)) {
    assert(nread+stackish_more(&parser)+1 < (size_t)blength(buf) && "buffer overflow");
    // there is a blob that we have to pull out in order to continue
    nread = stackish_parser_execute(&parser, (const char *)bdata(buf), blength(buf), 
        nread+stackish_more(&parser)+1);
    check(!stackish_parser_has_error(&parser), "parsing error after BLOB");
  }

  *from = nread + 1;

  // skip over a last trailing newline
  while(bchar(buf, *from) == '\n') (*from)++;

  return parser.root;

  on_fail(dbg("failed parsing after %zu bytes", nread);
      stackish_node_clear(&parser); 
      *from = nread + 1;
      return NULL);
}
Пример #12
0
bstring Node_bstr(Node *d, int follow_sibs)
{
  int rc = 0;
  bstring temp = bfromcstr("");
  assert_mem(temp);

  assert_not(d, NULL);

  Node_catbstr(temp, d, ' ', follow_sibs);
  
  rc = bconchar(temp, '\n');
  assert(rc == BSTR_OK && "failed to append separator char");

  return temp;
}
Пример #13
0
Node *FrameSource_recv(FrameSource frame, bstring *hbuf, Node **header)
{
  Node *msg = NULL;
  unsigned short length = 0;
  int rc = 0;
  size_t from = 0;
  bstring data = NULL;

  assert_not(header, NULL);

  *hbuf = NULL; *header = NULL;

  rc = io_ensure_sbuf(frame.in, frame.fd, sizeof(uint16_t));
  if(!rc) return NULL;  // got closed, nothing to do

  rc = sbuf_get_ntohs(frame.in, &length);
  check(rc, "length read failure");
  check(length > 0, "zero length message");

  rc = io_ensure_sbuf(frame.in, frame.fd, length);
  check(rc, "io read failure");

  data = sbuf_read_bstr(frame.in, length);
  check(data, "failed to read a single bstr");
  check(blength(data) == length, "didn't get what we asked for from the sbuf");

  *header = Node_parse_seq(data, &from);
  check(*header, "failed to parse a header");
  // carve out the hbuf bstring for comparison
  *hbuf = bmidstr(data, 0, from);

  if(from < length) {
    // more to read
    msg = Node_parse_seq(data, &from);
    check(msg, "failed to read body");
  }
  check(from == length, "trailing data violates");

  ensure(bdestroy(data); return msg);
}
Пример #14
0
void Node_dump(Node *d, char sep, int follow_sibs) 
{
  int i = 0;
  bstring str = Node_bstr(d, follow_sibs);

  assert_not(d, NULL);

  // go through and convert non-printables to printable, but not the last \n
  for(i = 0; i < blength(str)-1; i++) {
    if(!isprint(str->data[i])) {
      str->data[i] = str->data[i] % (126-32) + 32;
    }
  }

  fwrite(str->data, blength(str), 1, stderr);

  if(bchar(str,blength(str)-1) != '\n') {
    fprintf(stderr, "\n");
  }

  bdestroy(str);
}
Пример #15
0
int coro_test_4( co_t *co_p )
{
	
	if ( *co_p ) goto **co_p;
	/* begin */
	do {
		assert_not( inside_test_3 );
		do {
			/* yield */
			*co_p = &&L__7;
	
			return CO_YIELD;

			L__7:;
		} while ( 0 );
	} while ( inside_test_3 != -1 );
	/* end */
	*co_p = &&L__END_test_4;

	L__END_test_4:
	
	return CO_END;
}
Пример #16
0
int Node_decons(Node *root, int copy, const char *format, ...) {
  const char *cur = format;
  va_list args;
  va_start (args, format);
  Node *cur_node = root;

  assert_not(root, NULL);
  assert_not(format, NULL);

#define UP() { cur_node = cur_node->parent; if(cur_node == NULL) break; }
#define NEXT() { if(cur_node->sibling) cur_node = cur_node->sibling; if(cur_node == NULL) break; }

  for(; *cur != '\0'; cur++) {
    if(cur_node == NULL) {
      log(ERROR, "deconstruct ran out at char #%jd (%c) in the spec: %s", (intmax_t)(cur-format), *cur, format);
      break;
    }

    switch(*cur) {
      case '[':
        cur_node = cur_node->child;
        break;
      case 'n': {
                  uint64_t *number = va_arg(args, uint64_t *);
                  check_type(number, NUMBER, cur_node);
                  *number = cur_node->value.number;
                  NEXT();
                  break;
                }
      case 'f': {
                  double *floating = va_arg(args, double *);
                  *floating = cur_node->value.floating;
                  check_type(floating, FLOAT, cur_node);
                  NEXT();
                  break;
                }
      case 's': {
                  bstring *str = va_arg(args, bstring *);
                  check_type(str, STRING, cur_node);
                  if(copy)
                    *str = bstrcpy(cur_node->value.string);
                  else
                    *str = cur_node->value.string;
                  NEXT();
                  break;
                }
      case 'b': {
                  bstring *blob = va_arg(args, bstring *);
                  check_type(blob, BLOB, cur_node);
                  if(copy)
                    *blob = bstrcpy(cur_node->value.string);
                  else
                    *blob = cur_node->value.string;
                  NEXT();
                  break;
                }
      case ']': {
                  UP();
                  NEXT();
                  break;
                }
      case '@': {
                  const char *attr = va_arg(args, const char *);
                  check(attr != NULL, "NULL for given for ATTR name");
                  check_then(cur_node->name && biseqcstr(cur_node->name, attr), 
                      "wrong attribute name at:", Node_dump(cur_node, ' ', 1));
                  // just confirm but don't go to the next one
                  break;
                }
      case 'w': {
                  UP();
                  const char *word = va_arg(args, const char *);
                  check(word != NULL, "NULL given for word");
                  check_then(cur_node->name && biseqcstr(cur_node->name, word), 
                      "wrong word name at:", Node_dump(cur_node, ' ', 1));
                  NEXT();
                  break;
                }
      case 'G': {
                  Node **group = va_arg(args, Node **);
                  check(group != NULL, "NULL given for group");
                  check(*group == NULL, "Output var wasn't NULL, must be NULL.");
                  *group = cur_node;
                  NEXT();
                  break;
                }
      case '.': NEXT(); break;

      default: {
                 fail("invalid char in spec");
                 break;
               }
    }
  }
  
  va_end(args);

  check(*cur == '\0', "didn't process whole specification");

  return 1;
  on_fail(return 0);
}
Пример #17
0
int coro__ut_data_2( co_t *co_p )
{
	static dword i;
	if ( *co_p ) goto **co_p;
	/* begin */
	data_set_word( var__ut_word, 1 );
	data_set_dword( var__ut_array_3, 1 );
	data_reset_all( peek__ut_data_1 );
	data_set_dword( var__ut_array_0, 0x55555555 );

	for ( i = 0; i < CYCLE_COUNT; i++ ) {

		do {
			/* wait */
			*co_p = &&L__5;

			L__5:
			if (!( data_get_changed( peek__ut_data_2, var__ut_byte ))) { /* cond */
		
				return CO_WAIT;
			}
		} while ( 0 );
		/* 2 */
		data_reset( peek__ut_data_2, var__ut_byte );
		assert_u32_eq(( byte ) i, data_get_byte( var__ut_byte ));

		data_set_word( var__ut_word, ( word ) i );

		do {
			/* wait */
			*co_p = &&L__6;

			L__6:
			if (!( data_get_changed( peek__ut_data_2, var__ut_array_1 ))) { /* cond */
		
				return CO_WAIT;
			}
		} while ( 0 );
		/* 4 */
		data_reset( peek__ut_data_2, var__ut_array_1 );

		data_set_dword( var__ut_array_3, i << 8 );
		assert_u32_eq( data_get_dword( var__ut_array_3 ), data_get_dword( var__ut_array_1 ));

		data_set_dword( var__ut_array_2, data_get_dword( var__ut_array_0 ));
		data_set_dword( var__ut_array_0, 0 );

		do {
			/* wait */
			*co_p = &&L__7;

			L__7:
			if (!( data_get_changed( peek__ut_data_2, var__ut_float ))) { /* cond */
		
				return CO_WAIT;
			}
		} while ( 0 );
		/* 6 */
		data_set_float( var__ut_float, data_get_float( var__ut_float ) + 0.25 );
		data_reset( peek__ut_data_2, var__ut_float );
	}
	assert_float_eq(( float )( CYCLE_COUNT >> 1 ), data_get_float( var__ut_float ), 0.0 );

	/* Voting operations */
	/* I: -  II: -  III: -  M: -  */
	assert_not( data_get_changed( peek__ut_data_2, var__ut_vote ));
	assert_not( voted_valid( var__ut_vote ));
	/* I: -  II: 0  III: -  M: -  */
	voted_set_byte( var__ut_vote, 2, 0, &vote_byte );
	assert_not( data_get_changed( peek__ut_data_2, var__ut_vote ));
	assert_not( voted_valid( var__ut_vote ));
	/* I: -  II: 0  III: 0  M: 0  */
	voted_set_byte( var__ut_vote, 3, 0, &vote_byte );
	assert( data_get_changed( peek__ut_data_2, var__ut_vote ));
	data_reset( peek__ut_data_2, var__ut_vote );
	assert( voted_valid( var__ut_vote ));
	assert_u32_eq( 0, data_get_byte( var__ut_vote ));
	/* I: -  II: 0  III: 1  M: -  */
	voted_set_byte( var__ut_vote, 3, 1, &vote_byte );
	assert( data_get_changed( peek__ut_data_2, var__ut_vote ));
	data_reset( peek__ut_data_2, var__ut_vote );
	assert_not( voted_valid( var__ut_vote ));

	do {
		/* yield */
		*co_p = &&L__8;
	
		return CO_YIELD;

		L__8:;
	} while ( 0 );

	/* I: 1  II: 0  III: 1  M: 1  */
	voted_set_byte( var__ut_vote, 1, 1, &vote_byte );
	assert( data_get_changed( peek__ut_data_2, var__ut_vote ));
	data_reset( peek__ut_data_2, var__ut_vote );
	assert( voted_valid( var__ut_vote ));
	assert_u32_eq( 1, data_get_byte( var__ut_vote ));
	/* I: 1  II: 0  III: 0  M: 0  */
	voted_set_byte( var__ut_vote, 3, 0, &vote_byte );
	assert( data_get_changed( peek__ut_data_2, var__ut_vote ));
	data_reset( peek__ut_data_2, var__ut_vote );
	assert( voted_valid( var__ut_vote ));
	assert_u32_eq( 0, data_get_byte( var__ut_vote ));
	/* I: 1  II: 2  III: 0  M: -  */
	voted_set_byte( var__ut_vote, 2, 2, &vote_byte );
	assert( data_get_changed( peek__ut_data_2, var__ut_vote ));
	data_reset( peek__ut_data_2, var__ut_vote );
	assert_not( voted_valid( var__ut_vote ));
	/* I: 3  II: 2  III: 3  M: 3  */
	voted_set_byte( var__ut_vote, 1, 3, &vote_byte );
	voted_set_byte( var__ut_vote, 3, 3, &vote_byte );
	assert( data_get_changed( peek__ut_data_2, var__ut_vote ));
	data_reset( peek__ut_data_2, var__ut_vote );
	assert( voted_valid( var__ut_vote ));
	assert_u32_eq( 3, data_get_byte( var__ut_vote ));

	do {
		/* yield */
		*co_p = &&L__9;
	
		return CO_YIELD;

		L__9:;
	} while ( 0 );

	/* I: 3  II: 3  III: 3  M: 3  */
	voted_set_byte( var__ut_vote, 2, 3, &vote_byte );
	assert_not( data_get_changed( peek__ut_data_2, var__ut_vote ));
	assert( voted_valid( var__ut_vote ));
	assert_u32_eq( 3, data_get_byte( var__ut_vote ));
	/* I: 4  II: 3  III: 3  M: 3  */
	voted_set_byte( var__ut_vote, 1, 4, &vote_byte );
	assert_not( data_get_changed( peek__ut_data_2, var__ut_vote ));
	assert( voted_valid( var__ut_vote ));
	assert_u32_eq( 3, data_get_byte( var__ut_vote ));
	/* RESET */
	voted_set_byte( var__ut_vote, 3, 2, &vote_byte );
	data_reset( peek__ut_data_2, var__ut_vote );
	/* end */
	*co_p = &&L__END__ut_data_2;

	L__END__ut_data_2:
	
	return CO_END;
}
Пример #18
0
int coro__ut_data_1( co_t *co_p )
{
	static dword i;
	if ( *co_p ) goto **co_p;
	/* begin */
	data_clear_watch( peek__ut_data_1 );
	data_watch( peek__ut_data_1, _ut_data_1_watch );

	data_set_byte( var__ut_byte, 1 );
	data_set_dword( var__ut_array_1, 1 );
	data_set_float( var__ut_float, 0.0 );
	data_reset_all( peek__ut_data_2 );

	/* Main operations */
	for ( i = 0; i < CYCLE_COUNT; i++ ) {
		/* 1 */
		data_set_byte( var__ut_byte, ( byte ) i );

		do {
			/* wait */
			*co_p = &&L__0;

			L__0:
			if (!( data_get_changed( peek__ut_data_1, var__ut_word ))) { /* cond */
		
				return CO_WAIT;
			}
		} while ( 0 );
		/* 3 */
		data_reset( peek__ut_data_1, var__ut_word );
		assert_u32_eq(( word ) i, data_get_word( var__ut_word ));

		data_set_dword( var__ut_array_1, i << 8 );

		do {
			/* wait */
			*co_p = &&L__1;

			L__1:
			if (!( data_get_changed( peek__ut_data_1, var__ut_array_3 ))) { /* cond */
		
				return CO_WAIT;
			}
		} while ( 0 );
		/* 5 */
		data_reset( peek__ut_data_1, var__ut_array_3 );
		assert_u32_eq( data_get_dword( var__ut_array_1 ), data_get_dword( var__ut_array_3 ));

		data_set_dword( var__ut_array_0, data_get_dword( var__ut_array_2 ));
		data_set_dword( var__ut_array_2, 0 );

		data_set_float( var__ut_float, data_get_float( var__ut_float ) + 0.25 );
		data_reset( peek__ut_data_1, var__ut_float );
		do {
			/* wait */
			*co_p = &&L__2;

			L__2:
			if (!( data_get_changed( peek__ut_data_1, var__ut_float ))) { /* cond */
		
				return CO_WAIT;
			}
		} while ( 0 );
	}
	assert_u32_eq( 0x55555555, data_get_dword( var__ut_array_0 ));

	/* Auxiliary operations */
	data_set_all_changed( peek__ut_data_1 );
	assert(( data_get_changed( peek__ut_data_1, var__ut_word )
	&& data_get_changed( peek__ut_data_1, var__ut_array_3 )
	&& data_get_changed( peek__ut_data_1, var__ut_float )
	&& !data_get_changed( peek__ut_data_1, var__ut_array_1 )
	&& !data_get_changed( peek__ut_data_1, var__ut_array_2 ))
	);
	data_reset_all( peek__ut_data_1 );
	assert_not( data_get_changed_any( peek__ut_data_1 ));
	data_set_changed( var__ut_byte );
	assert_not( data_get_changed_any( peek__ut_data_1 ));
	data_set_changed( var__ut_float );
	assert( data_get_changed_any( peek__ut_data_1 ));

	do {
		/* yield */
		*co_p = &&L__3;
	
		return CO_YIELD;

		L__3:;
	} while ( 0 );

	data_clear_watch( peek__ut_data_1 );
	assert_not( data_get_changed_any( peek__ut_data_1 ));
	data_watch_array( peek__ut_data_1, _ut_data_array_watch );
	data_set_changed( var__ut_float );
	data_set_changed( var__ut_word );
	data_set_changed( var__ut_array_0 );
	assert_not( data_get_changed_any( peek__ut_data_1 ));
	data_set_changed( var__ut_array_1 );
	assert( data_get_changed( peek__ut_data_1, var__ut_array_1 ));
	data_set_changed( var__ut_array_2 );
	assert( data_get_changed( peek__ut_data_1, var__ut_array_2 ));
	data_set_changed( var__ut_array_3 );
	assert( data_get_changed( peek__ut_data_1, var__ut_array_3 ));
	data_set_changed( var__ut_byte );
	assert( data_get_changed( peek__ut_data_1, var__ut_byte ));

	do {
		/* yield */
		*co_p = &&L__4;
	
		return CO_YIELD;

		L__4:;
	} while ( 0 );

	assert_u32_eq( var__ut_array, data_atovar( data_vartoa( var__ut_array_0 )));
	assert_str_eq( "_ut_byte", data_vartoa( data_atovar( "_ut_byte" )));

	i = 0x00ABCDEF;
	data_set( var__ut_array_3, &i );
	assert_str_eq( "0x00ABCDEF", data_get_string( __str, var__ut_array_3 ));
	data_set_string( var__ut_float, "8080.02" );
	assert_str_eq( "8080.02", data_get_string( __str, var__ut_float ));

	assert_str_eq( "word", data_get_type( var__ut_word ));
	/* end */
	*co_p = &&L__END__ut_data_1;

	L__END__ut_data_1:
	
	return CO_END;
}
Пример #19
0
Node *Node_cons(const char *format, ...) {
  const char *cur = format;
  va_list args;
  va_start (args, format);
  Node *root = NULL;
  Node *cur_node = NULL;

  assert_not(format, NULL);

#define UP() { cur_node = cur_node->parent; if(cur_node == NULL) break; }

  for(; *cur != '\0'; cur++) {
    switch(*cur) {
      case '[':
        cur_node = Node_new_group(cur_node);
        if(root == NULL) {
          root = cur_node;
        }
        break;
      case 'n': {
                  uint64_t number = va_arg(args, uint64_t);
                  Node *n = Node_new_number(cur_node, number);
                  assert_not(n,NULL);
                  break;
                }
      case 'f': {
                  double floating = va_arg(args, double);
                  Node *n = Node_new_float(cur_node, floating);
                  assert_not(n,NULL);
                  break;
                }
      case 'b': {
                  bstring blob = va_arg(args, bstring);
                  check(blob != NULL, "NULL given for blob");
                  Node *n = Node_new_blob(cur_node, blob);
                  assert_not(n,NULL);
                  break;
                }
      case 's': {
                  bstring string = va_arg(args, bstring);
                  check(string != NULL, "NULL given for string");
                  Node *n = Node_new_string(cur_node, string);
                  check(n != NULL, "Failed to create new string node");
                  break;
                }
      case ']': {
                  UP();
                  break;
                }
      case '@': {
                  const char *attr = va_arg(args, const char *);
                  check(attr != NULL, "NULL given for attr");
                  Node_name(cur_node->child, bfromcstr(attr));
                  break;
                }
      case 'w': {
                  const char *word = va_arg(args, const char *);
                  check(word != NULL, "NULL given for word name");
                  Node_name(cur_node, bfromcstr(word));
                  UP();
                  break;
                }
      case 'G': {
                  Node *group = va_arg(args, Node *);
                  check(group != NULL, "NULL given for group to add");
                  LIST_ADD(Node, cur_node->child, group, sibling);
                  break;
                }
      case ' ':
      case '\t':
      case '\n': {
                   break;
                 }

      default: {
                 fail("invalid character in spec");
                 break;
               }
    }
  }

  assert(*cur == '\0');
  assert(cur_node == NULL);
  va_end(args);

  return root;
  on_fail(return NULL);
}