3 /* for debugging, runes like
4 * ./ours.redactgraph consistency movfeatsplitedges consistency movfeatrmstubs consistency movfeatsplitnodes consistency trivpairnodes consistency trivnullnodes consistency printforneato | neato -Tps >u.ps
16 #include "graph-data.h"
18 #define MMALLOC(sz) mmalloc(sz,__FILE__,__LINE__)
19 #define MREALLOC(p,sz) mrealloc(p,sz,__FILE__,__LINE__)
21 static void *mmalloccheck(void *p, const char *op, size_t sz,
22 const char *file, int line) {
24 fprintf(stderr,"%s:%d: %s of %lu failed: %s\n",
25 file, line, op, (unsigned long)sz, strerror(errno));
29 static void *mmalloc(size_t sz, const char *file, int line) {
33 return mmalloccheck(p,"malloc",sz,file,line);
36 static void *mrealloc(void *p, size_t sz, const char *file, int line) {
38 return mmalloccheck(p,"realloc",sz,file,line);
41 static char *mvasprintf(const char *fmt, va_list al)
42 __attribute__((format(printf,1,0)));
43 static char *mvasprintf(const char *fmt, va_list al) {
47 r= vasprintf(&p,fmt,al);
49 fprintf(stderr,"vasprintf(\"%s\",...) failed: %s\n",
50 fmt, strerror(errno));
56 static char *masprintf(const char *fmt, ...)
57 __attribute__((format(printf,1,2)));
58 static char *masprintf(const char *fmt, ...) {
62 p= mvasprintf(fmt,al);
67 /*---------- output processing ----------*/
69 static void voutputstuff(int trace, const char *fmt, va_list al)
70 __attribute__((format(printf,2,0)));
71 static void voutputstuff(int trace, const char *fmt, va_list al) {
72 static struct OutputBuf {
78 char *s, *p, *newline;
82 s= mvasprintf(fmt,al);
86 newline= strchr(p,'\n');
87 if (newline) { *newline= 0; }
89 if (l+b->used > b->allocd) {
91 b->buf= MREALLOC(b->buf, b->allocd);
93 memcpy(b->buf+b->used, p, l);
98 if (trace) { fputs("# ",stdout); }
99 fwrite(b->buf, 1,b->used, stdout);
101 if (ferror(stdout) || fflush(stdout)) {
102 perror("stdout voutputstuff"); exit(12);
112 static void v##fn(const char *fmt, va_list al) \
113 __attribute__((format(printf,1,0))); \
114 static void v##fn(const char *fmt, va_list al) { \
115 voutputstuff(tr,fmt,al); \
117 static void fn(const char *fmt, ...) __attribute__((format(printf,1,2))); \
118 static void fn(const char *fmt, ...) { \
127 /*---------- traceing of various things ----------*/
129 static void trace_node(Node *node) {
130 trace("n%s",node->pname);
133 static void trace_nodeside(NodeSide *node) {
134 trace("n%s %c",node->node->pname,"OI"[node->side]);
137 static void trace_edge(Edge *edge) {
138 trace("e%s",edge->pname);
141 static void trace_edgeend(EdgeEnd *edge) {
142 trace("e%s %c",edge->edge->pname,"OI"[edge->end]);
145 static void trace_combpos(int combpos, Segment *seg) {
151 FOR_SEGMENT_MOVFEATS(i,f,seg) {
152 if (!f->movfeat) continue;
153 trace("%s%d", f->movfeat, (combpos / weight) % f->n_positions);
154 weight *= f->n_positions;
158 /*---------- general graph-manipulation tools ----------*/
160 static EdgeEnd *edgeend_otherend(EdgeEnd *thisend) {
161 return &thisend->edge->ends[!thisend->end];
164 static int count_edges(const NodeSide *node) {
168 for (count=0, edge=node->head;
170 count++, edge=edge->next);
174 static Edge *edge_create(const char *pname, double distance,
175 MovFeat *subseg, int movpos) {
179 e= MMALLOC(sizeof(*e));
181 e->distance= distance;
185 e->ends[end].edge= e;
186 e->ends[end].end= end;
188 trace("[new:"); trace_edge(e); trace("]");
192 static Node *node_create(const char *pname) {
196 n= MMALLOC(sizeof(*n));
199 n->sides[side].node= n;
200 n->sides[side].side= side;
201 LIST_INIT(n->sides[side]);
203 LIST_LINK_TAIL(all_nodes,n);
207 /* Perhaps surprisingly, it's OK to remove nodes and edges in loops
208 * made with the macros in graph-data.h, provided that if the
209 * _current_ node or edge is removed, that happens last. This is
210 * because removing a node or edge it doesn't actually free it or
211 * change its contents, so the thing->next is still valid afterwards.
212 * This depends on the fact that we never free anything we're done
216 static void edgeend_detach(EdgeEnd *toremove) {
217 trace("[detach:"); trace_edgeend(toremove);
218 trace("-"); trace_nodeside(toremove->node); trace("]");
219 LIST_UNLINK(*toremove->node, toremove);
221 static void edgeend_attach(EdgeEnd *toattach, NodeSide *newtarget) {
222 trace("[attach:"); trace_edgeend(toattach);
223 trace("-"); trace_nodeside(newtarget); trace("]");
224 LIST_LINK_TAIL(*newtarget, toattach);
225 toattach->node= newtarget;
227 static void edgeend_replumb(EdgeEnd *tomove, NodeSide *newtarget) {
228 edgeend_detach(tomove);
229 edgeend_attach(tomove,newtarget);
232 static void edge_delete(EdgeEnd *detachlast) {
234 edge= detachlast->edge;
235 edgeend_detach(&edge->ends[!detachlast->end]);
236 edgeend_detach(detachlast);
237 trace("[delete:"); trace_edge(edge); trace("]");
240 static void node_surely_orphaned(Node *node) {
241 trace("[nodeorphan:"); trace_node(node); trace("]");
242 assert(!node->sides[0].head);
243 assert(!node->sides[1].head);
244 LIST_UNLINK(all_nodes, node);
247 /*---------- operations ----------*/
249 /* We combine the individual moveable features in each segment into
250 * a single `feature' with the cross-product of all of the
251 * positions. The new feature does not appear in
252 * Segment->[n_]movfeats, but Edge->subseg and Edge->movpos are
253 * updated (ie Edge->subseg->segment->movfeats is the original array
254 * containing only the actual features, and so does not contain
255 * Edge->subseg). It is often printed as if its name was `*' (which
256 * is not legal in input files etc.). The positions of the
257 * *-feature are numbered according to the obvious arithmetic coding
258 * of the individual actual feature positions. See also
261 * The conversion has two stages:
263 * FIRST STAGE (movfeatsplitedges):
265 * We multiply up each edge of any segment with moveable features to
266 * be an edge which is a position of the *-feature.
268 * For example, if segment A1 has feature P with states 0 and 1
269 * and feature J with states 0 and 1, then the edges are translated
272 * A1 A1/P0J0 A1/P0J1 A1/P1J0 A1/P1J1
273 * A1/P0 A1/P0J0 A1/P0J1
274 * A1/P1 A1/P1J0 A1/P1J1
275 * A1/J0 A1/P0J0 A1/P1J0
276 * A1/J1 A1/P0J1 A1/P1J1
278 * *----A1/P0----* becomes *----A1/P0J0----*
281 * SECOND STAGE (movfeatrmstubs):
283 * We remove stub moveable edges: whenever, at a node which has only
284 * edges which are part of the same segment, we find an edge on one
285 * side in which is not matched exactly (moveable feature settings
286 * and direction) by an edge on the other side, we delete that extra
289 * is split into one node
290 * for each position and direction (directions are
291 * end0-to-side0+end1-to-side1 and end0-to-side1+end1-to-side0), to
292 * which the corresponding edges are connected. Any resulting node
293 * which has only one edge is removed together with the edge.
295 * For example (neglecting direction for the purposes of the
296 * example), when we start with:
299 * ...*----A1/P0J1----*---A1/P0J1---*...
300 * `---A1/P1J0---' `--A1/P1J1--'
303 * the edges A1/P0J0 and A1/P1J1 are not matched on the RHS of the node,
304 * so they are removed:
306 * ...*----A1/P0J1----*---A1/P0J1---*...
307 * `---A1/P1J0---' `--A1/P1J1--'
312 * We split nodes: every node which has only edges which are part of
313 * the same segment is split into one node for each kind of edge
314 * which goes through it. The 2nd stage ensures that this is always
317 * Our example above turns into:
319 * ...*----A1/P0J1----*---A1/P0J1---*...
320 * `---A1/P1J0----*---A1/P1J1--'
323 static void movfeatsplitedges(void) {
325 int side, l, i, weight, combpos, end;
327 Edge *edge, *newedge;
329 MovFeat *subseg, *starfeature, *loopfeat;
332 trace("movfeatsplitedges ...\n");
333 FOR_ALL_NODES(node) {
334 FOR_EDGES(edge, node,side,edgeend) {
335 subseg= edge->subseg;
336 segment= subseg->segment;
338 trace("movfeatsplitedges "); trace_edge(edge);
339 trace(" segment %s ", segment->segname);
340 if (segment->n_movfeats<=1) { trace("no moveables\n"); continue; }
341 if (subseg == segment->starfeature) { trace("*-feature\n"); continue; }
343 starfeature= segment->starfeature;
348 starfeature= MMALLOC(sizeof(*starfeature));
349 starfeature->segment= segment;
351 l=2; starfeature->n_positions=1;
352 FOR_SEGMENT_MOVFEATS(i,loopfeat, segment) {
353 if (!loopfeat->movfeat) continue;
354 l += strlen(loopfeat->movfeat);
355 starfeature->n_positions *= loopfeat->n_positions;
358 starfeature->movfeat= featstr= MMALLOC(l);
359 strcpy(featstr, "*");
361 FOR_SEGMENT_MOVFEATS(i,loopfeat, segment) {
362 if (!loopfeat->movfeat) continue;
363 strcat(featstr, loopfeat->movfeat);
366 segment->starfeature= starfeature;
368 trace(" %s(%d) ", starfeature->movfeat, starfeature->n_positions);
370 if (subseg->movfeat) {
372 FOR_SEGMENT_MOVFEATS(i,loopfeat, segment) {
373 if (loopfeat == subseg) goto found;
374 weight *= loopfeat->n_positions;
378 trace("%s(x%d)",subseg->movfeat,weight);
386 for (combpos=0; combpos<starfeature->n_positions; combpos++) {
387 trace("movfeatsplitedges "); trace_combpos(combpos,segment);
388 if ((combpos / weight) % subseg->n_positions != edge->movpos) {
389 trace(" excluded\n");
392 newedge= edge_create(masprintf("%s/*%d",edge->pname,combpos),
397 edgeend_attach(&newedge->ends[end], edge->ends[end].node);
399 edge_delete(edgeend);
402 trace("movfeatsplitedges complete.\n");
405 static int movfeat_isinnernode(Node *node) {
408 Segment *all_segment, *segment;
413 if (!node->sides[side].head) {
414 trace(" %c terminus\n", "OI"[side]);
418 FOR_NODE_EDGEENDS(side,edgeend, node) {
419 segment= edgeend->edge->subseg->segment;
421 all_segment= segment;
422 } else if (all_segment != segment) {
423 trace(" not inner: %s and %s\n",
424 all_segment->segname, segment->segname);
429 trace(" not inner: no edges?!\n");
432 trace(" inner %s ",segment->segname);
436 static EdgeEnd *movfeat_findmate(EdgeEnd *key) {
441 node= key->node->node;
442 mateside= !key->node->side;
444 FOR_SIDE_EDGEENDS(mate, node,mateside) {
445 if (key->edge->subseg == mate->edge->subseg &&
446 key->edge->movpos == mate->edge->movpos &&
447 key->end != mate->end)
450 return 0; /* no mate - must be a stub */
453 static void movfeatrmstubs(void) {
454 int pass, anychange, side;
457 EdgeEnd *edgeend, *mate;
463 trace("movfeatrmstubs pass %d ...\n", pass);
464 FOR_ALL_NODES(node) {
465 trace("movfeatrmstubs ");
466 if (!movfeat_isinnernode(node)) continue;
467 FOR_NODE_EDGEENDS(side,edgeend, node) {
469 mate= movfeat_findmate(edgeend);
471 trace("("); trace_edgeend(edgeend); trace(")");
474 edge_delete(edgeend);
479 trace("movfeatrmstubs pass %d %s\n", pass, anychange?"changed":"alldone");
483 static void movfeatsplitnodes(void) {
484 Node *node, *newnode;
485 EdgeEnd *edgeend, *mate;
489 FOR_ALL_NODES(node) {
490 trace("movfeatsplitnodes ");
491 if (!movfeat_isinnernode(node)) continue;
492 if (!node->sides[0].head->next) {
493 trace("pair-trivial\n");
496 FOR_NODE_EDGEENDS(side,edgeend, node) {
498 mate= movfeat_findmate(edgeend);
500 newnode= node_create(masprintf("%sx%d",node->pname,edge->movpos));
501 edgeend_replumb(mate, &newnode->sides[mate->node->side]);
502 edgeend_replumb(edgeend, &newnode->sides[edgeend->node->side]);
507 static void trivpairnodes(void) {
508 /* Eliminate near-trivial nodes - in this case, ones which have only
509 * two edges, which come in on opposite sides, have the same
510 * subsegspec, and with the two ends identically aligned.
512 EdgeEnd *leftedge, *rightedge;
515 FOR_ALL_NODES(node) {
516 trace("trivpairnodes node "); trace_node(node); trace(" ");
517 if (!(count_edges(&node->sides[0])==1 &&
518 count_edges(&node->sides[1])==1)) {
519 trace("no, a non-unitary side\n");
522 leftedge= node->sides[0].head;
523 rightedge= node->sides[1].head;
524 if (leftedge->edge->subseg != rightedge->edge->subseg) {
525 trace("no, (sub)segments differ\n");
528 if (leftedge->edge->movpos != rightedge->edge->movpos) {
529 trace("no, movpos's differ\n");
532 if (leftedge->end == rightedge->end) {
533 trace("no, opposite alignment\n");
537 if (!strchr(rightedge->edge->pname, '+')) {
538 rightedge->edge->pname=
539 masprintf("%s+", rightedge->edge->pname);
541 rightedge->edge->distance += leftedge->edge->distance;
542 edgeend_replumb(rightedge, edgeend_otherend(leftedge)->node);
543 edge_delete(leftedge);
544 trace(" trivpairnodes operation complete\n");
548 static void trivnullnodes(void) {
549 /* Eliminate trivial nodes - ones which have no edges. */
553 trace("trivnullnodes ");
554 FOR_ALL_NODES(node) {
556 if (node->sides[side].head)
559 node_surely_orphaned(node);
562 trace(" trivnullnodes done.\n");
565 static void check(const char *why) {
567 int alloc_edone, used_edone;
568 typedef struct { Edge *edge; int endsdone; } EdgeDone;
569 EdgeDone *edone, *search_edone;
575 alloc_edone= used_edone= 0;
577 trace("consistency check (%s) ...\n",why);
578 FOR_ALL_NODES(node) {
579 trace(" consistency "); trace_node(node); trace("\n");
580 LIST_CHECKNODE(all_nodes,node);
582 trace(" consistency "); trace_nodeside(&node->sides[side]); trace("\n");
583 assert(node->sides[side].node == node);
584 assert(node->sides[side].side == side);
585 FOR_SIDE_EDGEENDS(edgeend, node,side) {
586 trace(" consistency "); trace_nodeside(&node->sides[side]);
587 trace(" "); trace_edgeend(edgeend); trace("\n");
590 LIST_CHECKNODE(node->sides[side], edgeend);
591 assert(edgeend == &edge->ends[edgeend->end]);
592 assert(edgeend->node == &node->sides[side]);
593 if (used_edone==alloc_edone) {
594 alloc_edone += 2; alloc_edone *= 2;
595 edone= MREALLOC(edone, alloc_edone * sizeof(*edone));
597 for (search_edone= edone;
598 search_edone < edone + used_edone && search_edone->edge != edge;
600 if (search_edone >= edone + used_edone) {
601 search_edone->edge= edge;
602 search_edone->endsdone= 0;
605 search_edone->endsdone++;
606 assert(search_edone->endsdone <= 2);
610 for (search_edone= edone;
611 search_edone < edone + used_edone;
613 trace("consistency "); trace_edge(search_edone->edge);
614 trace(" count=%d\n", search_edone->endsdone);
615 assert(search_edone->endsdone == 2);
617 trace("consistency check (%s) ok\n",why);
620 static void consistency(void) {
621 check("command-line request");
624 static void printforneato(void) {
630 output("digraph L {\n");
632 FOR_ALL_NODES(node) {
633 output(" n%sO [label=\"%s\"];\n"
634 " n%sI [label=\"+\"];\n",
635 node->pname, node->pname, node->pname);
636 output(" n%sO -> n%sI [len=0.25 arrowsize=0];\n",
637 node->pname, node->pname);
638 FOR_EDGES(edge, node,side,edgeend) {
639 output(" n%s%c -> n%s%c [label=\"%s:",
640 edge->ends[0].node->node->pname,
641 "OI"[edge->ends[0].node->side],
642 edge->ends[1].node->node->pname,
643 "OI"[edge->ends[1].node->side],
645 if (!edge->subseg->segment->segname) {
648 output(edge->subseg->segment->segname);
649 if (edge->subseg->movfeat) {
650 output("/%s%d",edge->subseg->movfeat,edge->movpos);
653 output(":%.2f\"];\n",edge->distance);
664 static const OpInfo opinfos[]= {
665 #define OI(x) { #x, x },
668 OI(movfeatsplitedges)
670 OI(movfeatsplitnodes)
676 static void usage(void) {
680 "usage: .../redactgraph <operation> [<operation>...]\n"
682 for (oi=opinfos; oi->name; oi++) {
683 fprintf(stderr," %s\n",oi->name);
689 int main(int argc, const char *const *argv) {
693 if (!argv[0] || !argv[1]) usage();
695 while ((arg= *++argv)) {
696 for (oi=opinfos; oi->name && strcmp(arg,oi->name); oi++);
697 if (!oi->name) usage();
702 if (ferror(stdout) || fflush(stdout)) { perror("stdout"); exit(12); }