chiark / gitweb /
working on data2safety
[trains.git] / layout / redactgraph.c
1 /*
2  * output format from printforforsafety is lines like this:
3  *   segment <node>.<side> <node>.<side> <segment>[/<movinfo>] <distance>
4  *   #...         (comment)
5  *                (blank)
6  *   end          (at end of file, mandatory)
7  * syntactic restrictions:
8  *   <node> is alphanumeric and starts with a letter
9  *   <side> is precisely the digit 0 or the digit 1
10  *   <segment> is alphanumeric (and comes from the layout)
11  *   <movinfo> is  <movfeat><movpos>[<movfeat><movpos>...]*<combpos>
12  *            where <movfeat> is alphabetic and <movpos> is and
13  *            <combpos> are in decimal and have no unnecessary leading 0's
14  *   <distance> contains only digits and perhaps decimal point
15  * semantic restrictions (not enforced by redactgraph but ought to
16  * be impossible for layout designer to violate):
17  *   Each <segment>[/<movinfo>] appears exactly once.
18  *   <segment> appears either once without /<movinfo> or always with.
19  *   Each side of each node appears either
20  *    - once as the 0th or 1th end of a <segment>
21  *    - one or more times as the same end of <segment>/<movinfo>'s with
22  *      the same <segment>
23  *    - not at all (indicates a terminus)
24  * Refer to safety.h for info regarding <combpos>.
25  */
26 /* for debugging, runes like
27  *  ./ours.redactgraph consistency movfeatsplitedges consistency movfeatrmstubs consistency movfeatsplitnodes consistency trivpairnodes consistency trivnullnodes consistency printforneato | neato -Tps >u.ps
28  * are often useful.
29  */
30
31 #include <stdarg.h>
32 #include <string.h>
33 #include <stdlib.h>
34 #include <stdio.h>
35 #include <assert.h>
36 #include <errno.h>
37
38 #include "dlist.h"
39 #include "graph-data.h"
40
41 #define MMALLOC(sz) mmalloc(sz,__FILE__,__LINE__)
42 #define MREALLOC(p,sz) mrealloc(p,sz,__FILE__,__LINE__)
43
44 static void *mmalloccheck(void *p, const char *op, size_t sz,
45                           const char *file, int line) {
46   if (p) return p;
47   fprintf(stderr,"%s:%d: %s of %lu failed: %s\n",
48           file, line, op, (unsigned long)sz, strerror(errno));
49   exit(16);
50 }
51
52 static void *mmalloc(size_t sz, const char *file, int line) {
53   void *p;
54   if (!sz) return 0;
55   p= malloc(sz);
56   return mmalloccheck(p,"malloc",sz,file,line);
57 }
58
59 static void *mrealloc(void *p, size_t sz, const char *file, int line) {
60   p= realloc(p,sz);
61   return mmalloccheck(p,"realloc",sz,file,line);
62 }  
63
64 static char *mvasprintf(const char *fmt, va_list al)
65      __attribute__((format(printf,1,0)));
66 static char *mvasprintf(const char *fmt, va_list al) {
67   char *p;
68   int r;
69   
70   r= vasprintf(&p,fmt,al);
71   if (r<0) {
72     fprintf(stderr,"vasprintf(\"%s\",...) failed: %s\n",
73             fmt, strerror(errno));
74     exit(16);
75   }
76   return p;
77 }
78
79 static char *masprintf(const char *fmt, ...)
80      __attribute__((format(printf,1,2)));
81 static char *masprintf(const char *fmt, ...) {
82   va_list al;
83   char *p;
84   va_start(al,fmt);
85   p= mvasprintf(fmt,al);
86   va_end(al);
87   return p;
88 }
89
90 /*---------- output processing ----------*/
91
92 static void voutputstuff(int trace, const char *fmt, va_list al)
93      __attribute__((format(printf,2,0)));
94 static void voutputstuff(int trace, const char *fmt, va_list al) {
95   static struct OutputBuf {
96     int used, allocd;
97     char *buf;
98   } bufs[2];
99
100   struct OutputBuf *b;
101   char *s, *p, *newline;
102   int l;
103
104   b= &bufs[trace];
105   s= mvasprintf(fmt,al);
106   p= s;
107
108   for (;;) {
109     newline= strchr(p,'\n');
110     if (newline) { *newline= 0; }
111     l= strlen(p);
112     if (l+b->used > b->allocd) {
113       b->allocd= l+b->used;
114       b->buf= MREALLOC(b->buf, b->allocd);
115     }
116     memcpy(b->buf+b->used, p, l);
117     b->used += l;
118     
119     if (!newline) break;
120
121     if (trace) { fputs("# ",stdout); }
122     fwrite(b->buf, 1,b->used, stdout);
123     putc('\n',stdout);
124     if (ferror(stdout) || fflush(stdout)) {
125       perror("stdout voutputstuff"); exit(12);
126     }
127     b->used= 0;
128     p= newline+1;
129   }
130
131   free(s);
132 }      
133
134 #define OF(fn,tr)                                                           \
135   static void v##fn(const char *fmt, va_list al)                            \
136      __attribute__((format(printf,1,0)));                                   \
137   static void v##fn(const char *fmt, va_list al) {                          \
138     voutputstuff(tr,fmt,al);                                                \
139   }                                                                         \
140   static void fn(const char *fmt, ...) __attribute__((format(printf,1,2))); \
141   static void fn(const char *fmt, ...) {                                    \
142     va_list al;                                                             \
143     va_start(al,fmt);                                                       \
144     v##fn(fmt,al);                                                          \
145     va_end(al);                                                             \
146   }
147 OF(output,0)
148 OF(trace,1)
149      
150 /*---------- traceing of various things ----------*/
151
152 static void trace_node(Node *node) {
153   trace("n%s",node->pname);
154 }
155
156 static void trace_nodeside(NodeSide *node) {
157   trace("n%s %c",node->node->pname,"OI"[node->side]);
158 }
159
160 static void trace_edge(Edge *edge) {
161   trace("e%s",edge->pname);
162 }
163
164 static void trace_edgeend(EdgeEnd *edge) {
165   trace("e%s %c",edge->edge->pname,"OI"[edge->end]);
166 }
167
168 static void trace_combpos(int combpos, Segment *seg) {
169   int i, weight;
170   MovFeat *f;
171   
172   trace("*");
173   weight= 1;
174   FOR_SEGMENT_MOVFEATS(i,f,seg) {
175     if (!f->movfeat) continue;
176     trace("%s%d", f->movfeat, (combpos / weight) % f->n_positions);
177     weight *= f->n_positions;
178   }
179 }
180
181 /*---------- general graph-manipulation tools ----------*/
182
183 static EdgeEnd *edgeend_otherend(EdgeEnd *thisend) {
184   return &thisend->edge->ends[!thisend->end];
185 }
186
187 static int count_edges(const NodeSide *node) {
188   int count;
189   const EdgeEnd *edge;
190   
191   for (count=0, edge=node->head;
192        edge;
193        count++, edge=edge->next);
194   return count;
195 }
196
197 static Edge *edge_create(const char *pname, double distance,
198                          MovFeat *subseg, int movpos) {
199   Edge *e;
200   int end;
201   
202   e= MMALLOC(sizeof(*e));
203   e->pname= pname;
204   e->distance= distance;
205   e->subseg= subseg;
206   e->movpos= movpos;
207   FOR_BOTH(end) {
208     e->ends[end].edge= e;
209     e->ends[end].end= end;
210   }
211   trace("[new:"); trace_edge(e); trace("]");
212   return e;
213 }
214
215 static Node *node_create(const char *pname) {
216   Node *n;
217   int side;
218
219   n= MMALLOC(sizeof(*n));
220   n->pname= pname;
221   FOR_BOTH(side) {
222     n->sides[side].node= n;
223     n->sides[side].side= side;
224     LIST_INIT(n->sides[side]);
225   }
226   LIST_LINK_TAIL(all_nodes,n);
227   return n;
228 }
229
230 /* Perhaps surprisingly, it's OK to remove nodes and edges in loops
231  * made with the macros in graph-data.h, provided that if the
232  * _current_ node or edge is removed, that happens last.  This is
233  * because removing a node or edge it doesn't actually free it or
234  * change its contents, so the thing->next is still valid afterwards.
235  * This depends on the fact that we never free anything we're done
236  * with !
237  */
238
239 static void edgeend_detach(EdgeEnd *toremove) {
240   trace("[detach:"); trace_edgeend(toremove);
241   trace("-"); trace_nodeside(toremove->node); trace("]");
242   LIST_UNLINK(*toremove->node, toremove);
243 }
244 static void edgeend_attach(EdgeEnd *toattach, NodeSide *newtarget) {
245   trace("[attach:"); trace_edgeend(toattach);
246   trace("-"); trace_nodeside(newtarget); trace("]");
247   LIST_LINK_TAIL(*newtarget, toattach);
248   toattach->node= newtarget;
249 }
250 static void edgeend_replumb(EdgeEnd *tomove, NodeSide *newtarget) {
251   edgeend_detach(tomove);
252   edgeend_attach(tomove,newtarget);
253 }
254
255 static void edge_delete(EdgeEnd *detachlast) {
256   Edge *edge;
257   edge= detachlast->edge;
258   edgeend_detach(&edge->ends[!detachlast->end]);
259   edgeend_detach(detachlast);
260   trace("[delete:"); trace_edge(edge); trace("]");
261 }
262
263 static void node_surely_orphaned(Node *node) {
264   trace("[nodeorphan:"); trace_node(node); trace("]");
265   assert(!node->sides[0].head);
266   assert(!node->sides[1].head);
267   LIST_UNLINK(all_nodes, node);
268 }
269
270 /*---------- operations - heavyweight graph redaction ----------*/
271
272   /* We combine the individual moveable features in each segment into
273    * a single `feature' with the cross-product of all of the
274    * positions.  The new feature does not appear in
275    * Segment->[n_]movfeats, but Edge->subseg and Edge->movpos are
276    * updated (ie Edge->subseg->segment->movfeats is the original array
277    * containing only the actual features, and so does not contain
278    * Edge->subseg).  It is often printed as if its name was `*' (which
279    * is not legal in input files etc.).  The positions of the
280    * *-feature are numbered according to the obvious arithmetic coding
281    * of the individual actual feature positions.  See also
282    * hostside/safety.h.
283    *
284    * The conversion has two stages:
285    *
286    * FIRST STAGE (movfeatsplitedges):
287    *
288    * We multiply up each edge of any segment with moveable features to
289    * be an edge which is a position of the *-feature.
290    *
291    * For example, if segment A1 has feature P with states 0 and 1
292    * and feature J with states 0 and 1, then the edges are translated
293    * as follows
294    *   old edge    new edges
295    *    A1          A1/P0J0 A1/P0J1 A1/P1J0 A1/P1J1
296    *    A1/P0       A1/P0J0 A1/P0J1
297    *    A1/P1                       A1/P1J0 A1/P1J1
298    *    A1/J0       A1/P0J0         A1/P1J0        
299    *    A1/J1               A1/P0J1         A1/P1J1
300    * Eg,
301    *     *----A1/P0----*   becomes   *----A1/P0J0----*
302    *                                  `---A1/P0J1---'
303    *
304    * SECOND STAGE (movfeatrmstubs):
305    *
306    * We remove stub moveable edges: whenever, at a node which has only
307    * edges which are part of the same segment, we find an edge on one
308    * side in which is not matched exactly (moveable feature settings
309    * and direction) by an edge on the other side, we delete that extra
310    * edge:
311    *
312    * is split into one node
313    * for each position and direction (directions are
314    * end0-to-side0+end1-to-side1 and end0-to-side1+end1-to-side0), to
315    * which the corresponding edges are connected.  Any resulting node
316    * which has only one edge is removed together with the edge.
317    *
318    * For example (neglecting direction for the purposes of the
319    * example), when we start with:
320    *
321    *             ,---A1/P0J0---.                  
322    *         ...*----A1/P0J1----*---A1/P0J1---*...
323    *             `---A1/P1J0---' `--A1/P1J1--'    
324    *              `--A1/P1J1--'                   
325    *
326    * the edges A1/P0J0 and A1/P1J1 are not matched on the RHS of the node,
327    * so they are removed:
328    *
329    *         ...*----A1/P0J1----*---A1/P0J1---*...
330    *             `---A1/P1J0---' `--A1/P1J1--'    
331    *
332    *
333    * THIRD STAGE:
334    *
335    * We split nodes: every node which has only edges which are part of
336    * the same segment is split into one node for each kind of edge
337    * which goes through it.  The 2nd stage ensures that this is always
338    * straightforward.
339    *
340    * Our example above turns into:
341    *
342    *         ...*----A1/P0J1----*---A1/P0J1---*...
343    *             `---A1/P1J0----*---A1/P1J1--'
344    */
345
346 static void movfeatsplitedges(void) {
347   Node *node;
348   int side, l, i, weight, combpos, end;
349   EdgeEnd *edgeend;
350   Edge *edge, *newedge;
351   Segment *segment;
352   MovFeat *subseg, *starfeature, *loopfeat;
353   char *featstr;
354   
355   trace("movfeatsplitedges ...\n");
356   FOR_ALL_NODES(node) {
357     FOR_EDGES(edge, node,side,edgeend) {
358       subseg= edge->subseg;
359       segment= subseg->segment;
360
361       trace("movfeatsplitedges "); trace_edge(edge);
362       trace(" segment %s ", segment->segname);
363       if (segment->n_movfeats<=1) { trace("no moveables\n"); continue; }
364       if (subseg == segment->starfeature) { trace("*-feature\n"); continue; }
365
366       starfeature= segment->starfeature;
367       if (starfeature) {
368         trace("old");
369       } else {
370         trace("new");
371         starfeature= MMALLOC(sizeof(*starfeature));
372         starfeature->segment= segment;
373
374         l=2;  starfeature->n_positions=1;
375         FOR_SEGMENT_MOVFEATS(i,loopfeat, segment) {
376           if (!loopfeat->movfeat) continue;
377           l += strlen(loopfeat->movfeat);
378           starfeature->n_positions *= loopfeat->n_positions;
379         }
380
381         starfeature->movfeat= featstr= MMALLOC(l);
382         strcpy(featstr, "*");
383
384         FOR_SEGMENT_MOVFEATS(i,loopfeat, segment) {
385           if (!loopfeat->movfeat) continue;
386           strcat(featstr, loopfeat->movfeat);
387         }
388
389         segment->starfeature= starfeature;
390       }
391       trace(" %s(%d) ", starfeature->movfeat, starfeature->n_positions);
392
393       if (subseg->movfeat) {
394         weight= 1;
395         FOR_SEGMENT_MOVFEATS(i,loopfeat, segment) {
396           if (loopfeat == subseg) goto found;
397           weight *= loopfeat->n_positions;
398         }
399         abort();
400       found:
401         trace("%s(x%d)",subseg->movfeat,weight);
402       } else {
403         weight= 1;
404         trace("fixed");
405       }
406
407       trace(" ...\n");
408         
409       for (combpos=0; combpos<starfeature->n_positions; combpos++) {
410         trace("movfeatsplitedges  "); trace_combpos(combpos,segment);
411         if ((combpos / weight) % subseg->n_positions != edge->movpos) {
412           trace(" excluded\n");
413           continue;
414         }
415         newedge= edge_create(masprintf("%s/*%d",edge->pname,combpos),
416                              edge->distance,
417                              starfeature,
418                              combpos);
419         FOR_BOTH(end)
420           edgeend_attach(&newedge->ends[end], edge->ends[end].node);
421       }
422       edge_delete(edgeend);
423     }
424   }
425   trace("movfeatsplitedges complete.\n");
426 }
427
428 static int movfeat_isinnernode(Node *node) {
429   int side;
430   EdgeEnd *edgeend;
431   Segment *all_segment, *segment;
432
433   trace_node(node);
434   all_segment= 0;
435   FOR_BOTH(side) {
436     if (!node->sides[side].head) {
437       trace(" %c terminus\n", "OI"[side]);
438       return 0;
439     }
440   }
441   FOR_NODE_EDGEENDS(side,edgeend, node) {
442     segment= edgeend->edge->subseg->segment;
443     if (!all_segment) {
444       all_segment= segment;
445     } else if (all_segment != segment) {
446       trace(" not inner: %s and %s\n",
447             all_segment->segname, segment->segname);
448       return 0;
449     }
450   }
451   if (!all_segment) {
452     trace(" not inner: no edges?!\n");
453     return 0;
454   }
455   trace(" inner %s ",all_segment->segname);
456   return 1;
457 }
458
459 static EdgeEnd *movfeat_findmate(EdgeEnd *key) {
460   Node *node;
461   EdgeEnd *mate;
462   int mateside;
463
464   node= key->node->node;
465   mateside= !key->node->side;
466   
467   FOR_SIDE_EDGEENDS(mate, node,mateside) {
468     if (key->edge->subseg == mate->edge->subseg &&
469         key->edge->movpos == mate->edge->movpos &&
470         key->end != mate->end)
471       return mate;
472   }
473   return 0; /* no mate - must be a stub */
474 }
475
476 static void movfeatrmstubs(void) {
477   int pass, anychange, side;
478   Node *node;
479   Edge *edge;
480   EdgeEnd *edgeend, *mate;
481
482   pass= 0;
483   do {
484     anychange= 0;
485     pass++;
486     trace("movfeatrmstubs pass %d ...\n", pass);
487     FOR_ALL_NODES(node) {
488       trace("movfeatrmstubs ");
489       if (!movfeat_isinnernode(node)) continue;
490       FOR_NODE_EDGEENDS(side,edgeend, node) {
491         edge= edgeend->edge;
492         mate= movfeat_findmate(edgeend);
493         if (mate) {
494           trace("("); trace_edgeend(edgeend); trace(")");
495           continue;
496         }
497         edge_delete(edgeend);
498         anychange++;
499       }
500       trace("\n");
501     }
502     trace("movfeatrmstubs pass %d %s\n", pass, anychange?"changed":"alldone");
503   } while (anychange);
504 }
505
506 static void movfeatsplitnodes(void) {
507   Node *node, *newnode;
508   EdgeEnd *edgeend, *mate;
509   Edge *edge;
510   int side;
511   
512   FOR_ALL_NODES(node) {
513     trace("movfeatsplitnodes ");
514     if (!movfeat_isinnernode(node)) continue;
515     if (!node->sides[0].head->next) {
516       trace("pair-trivial\n");
517       continue;
518     }
519     FOR_NODE_EDGEENDS(side,edgeend, node) {
520       edge= edgeend->edge;
521       mate= movfeat_findmate(edgeend);
522       assert(mate);
523       newnode= node_create(masprintf("%sx%d",node->pname,edge->movpos));
524       edgeend_replumb(mate, &newnode->sides[mate->node->side]);
525       edgeend_replumb(edgeend, &newnode->sides[edgeend->node->side]);
526     }
527   }
528 }
529
530 /*---------- operations - trivial graph redaction etc. ----------*/
531
532 static void trivpairnodes(void) {
533   /* Eliminate near-trivial nodes - in this case, ones which have only
534    * two edges, which come in on opposite sides, have the same
535    * subsegspec, and with the two ends identically aligned.
536    */
537   EdgeEnd *leftedge, *rightedge;
538   Node *node;
539
540   FOR_ALL_NODES(node) {
541     trace("trivpairnodes node "); trace_node(node); trace(" ");
542     if (!(count_edges(&node->sides[0])==1 &&
543           count_edges(&node->sides[1])==1)) {
544       trace("no, a non-unitary side\n");
545       continue;
546     }
547     leftedge= node->sides[0].head;
548     rightedge= node->sides[1].head;
549     if (leftedge->edge->subseg != rightedge->edge->subseg) {
550       trace("no, (sub)segments differ\n");
551       continue;
552     }
553     if (leftedge->edge->movpos != rightedge->edge->movpos) {
554       trace("no, movpos's differ\n");
555       continue;
556     }
557     if (leftedge->end == rightedge->end) {
558       trace("no, opposite alignment\n");
559       continue;
560     }
561     trace(" yes:\n");
562     if (!strchr(rightedge->edge->pname, '+')) {
563       rightedge->edge->pname=
564         masprintf("%s+", rightedge->edge->pname);
565     }
566     rightedge->edge->distance += leftedge->edge->distance;
567     edgeend_replumb(rightedge, edgeend_otherend(leftedge)->node);
568     edge_delete(leftedge);
569     trace(" trivpairnodes operation complete\n");
570   }
571 }
572
573 static void trivnullnodes(void) {
574   /* Eliminate trivial nodes - ones which have no edges. */
575   Node *node;
576   int side;
577   
578   trace("trivnullnodes ");
579   FOR_ALL_NODES(node) {
580     FOR_BOTH(side) {
581       if (node->sides[side].head)
582         goto non_null;
583     }
584     node_surely_orphaned(node);
585   non_null:;
586   }
587   trace(" trivnullnodes done.\n");
588 }
589
590 static void check(const char *why) {
591   Node *node;
592   int alloc_edone, used_edone;
593   typedef struct { Edge *edge; int endsdone; } EdgeDone;
594   EdgeDone *edone, *search_edone;
595   int side;
596   EdgeEnd *edgeend;
597   Edge *edge;
598
599   edone= 0;
600   alloc_edone= used_edone= 0;
601   
602   trace("consistency check (%s) ...\n",why);
603   FOR_ALL_NODES(node) {
604     trace(" consistency "); trace_node(node); trace("\n");
605     LIST_CHECKNODE(all_nodes,node);
606     FOR_BOTH(side) {
607       trace(" consistency "); trace_nodeside(&node->sides[side]); trace("\n");
608       assert(node->sides[side].node == node);
609       assert(node->sides[side].side == side);
610       FOR_SIDE_EDGEENDS(edgeend, node,side) {
611         trace(" consistency "); trace_nodeside(&node->sides[side]);
612         trace(" "); trace_edgeend(edgeend); trace("\n");
613         
614         edge= edgeend->edge;
615         LIST_CHECKNODE(node->sides[side], edgeend);
616         assert(edgeend == &edge->ends[edgeend->end]);
617         assert(edgeend->node == &node->sides[side]);
618         if (used_edone==alloc_edone) {
619           alloc_edone += 2; alloc_edone *= 2;
620           edone= MREALLOC(edone, alloc_edone * sizeof(*edone));
621         }
622         for (search_edone= edone;
623              search_edone < edone + used_edone && search_edone->edge != edge;
624              search_edone++);
625         if (search_edone >= edone + used_edone) {
626           search_edone->edge= edge;
627           search_edone->endsdone= 0;
628           used_edone++;
629         }
630         search_edone->endsdone++;
631         assert(search_edone->endsdone <= 2);
632       }
633     }
634   }
635   for (search_edone= edone;
636        search_edone < edone + used_edone;
637        search_edone++) {
638     trace("consistency "); trace_edge(search_edone->edge);
639     trace(" count=%d\n", search_edone->endsdone);
640     assert(search_edone->endsdone == 2);
641   }
642   trace("consistency check (%s) ok\n",why);
643 }
644
645 static void consistency(void) {
646   check("command-line request");
647 }
648
649 /*---------- operations - output ----------*/
650
651 static void printforneato(void) {
652   Node *node;
653   EdgeEnd *edgeend;
654   Edge *edge;
655   int side;
656   
657   output("digraph L {\n");
658
659   FOR_ALL_NODES(node) {
660     output("  n%sO [label=\"%s\"];\n"
661            "  n%sI [label=\"+\"];\n",
662            node->pname, node->pname, node->pname);
663     output("  n%sO -> n%sI [len=0.25 arrowsize=0];\n",
664            node->pname, node->pname);
665     FOR_EDGES(edge, node,side,edgeend) {
666       output("  n%s%c -> n%s%c [label=\"%s:",
667              edge->ends[0].node->node->pname,
668              "OI"[edge->ends[0].node->side],
669              edge->ends[1].node->node->pname,
670              "OI"[edge->ends[1].node->side],
671              edge->pname);
672       if (!edge->subseg->segment->segname) {
673         output("?");
674       } else {
675         output(edge->subseg->segment->segname);
676         if (edge->subseg->movfeat) {
677           output("/%s%d",edge->subseg->movfeat,edge->movpos);
678         }
679       }
680       output(":%.2f\"];\n",edge->distance);
681     }
682   }
683   output("}\n");
684 }
685
686 static void printforforsafety(void) {
687   Node *node;
688   Edge *edge;
689   EdgeEnd *edgeend;
690   int side, end, weight, i;
691   Segment *segment;
692   MovFeat *movfeat;
693   
694   FOR_ALL_NODES(node) {
695     FOR_EDGES(edge, node,side,edgeend) {
696       output("segment ");
697       FOR_BOTH(end) {
698         output("n%s.%d ",
699                edge->ends[end].node->node->pname,
700                edge->ends[end].node->side);
701       }
702       segment= edge->subseg->segment;
703       output(segment->segname);
704       if (segment->n_movfeats>1) {
705         if (!segment->starfeature) {
706           fprintf(stderr,"printforforsafety before movfeatsplitedges!\n");
707           exit(8);
708         }
709         output("/");
710         weight= 1;
711         FOR_SEGMENT_MOVFEATS(i,movfeat, segment) {
712           if (!movfeat->movfeat) continue;
713           output("%s%d", movfeat->movfeat,
714                  (edge->movpos / weight) % movfeat->n_positions);
715           weight *= movfeat->n_positions;
716         }
717         output("*%d", edge->movpos);
718       }
719       output(" %f\n", edge->distance);
720     }
721   }
722   output("end\n");
723 }
724
725 /*---------- main program ----------*/
726
727 typedef struct {
728   const char *name;
729   void (*fn)(void);
730 } OpInfo;
731
732 static const OpInfo opinfos[]= {
733 #define OI(x) { #x, x },
734   OI(printforneato)
735   OI(printforforsafety)
736   OI(consistency)
737   OI(movfeatsplitedges)
738   OI(movfeatrmstubs)
739   OI(movfeatsplitnodes)
740   OI(trivpairnodes)
741   OI(trivnullnodes)
742   { 0,0 }
743 };
744
745 static void usage(void) {
746   const OpInfo *oi;
747   
748   fprintf(stderr,
749           "usage: .../redactgraph <operation> [<operation>...]\n"
750           "operations:\n");
751   for (oi=opinfos; oi->name; oi++) {
752     fprintf(stderr," %s\n",oi->name);
753   }
754   fflush(stderr);
755   exit(8);
756 }
757
758 int main(int argc, const char *const *argv) {
759   const OpInfo *oi;
760   const char *arg;
761
762   if (!argv[0] || !argv[1]) usage();
763
764   while ((arg= *++argv)) {
765     for (oi=opinfos; oi->name && strcmp(arg,oi->name); oi++);
766     if (!oi->name) usage();
767     oi->fn();
768   }
769   trace("\n");
770   output("\n");
771   if (ferror(stdout) || fflush(stdout)) { perror("stdout"); exit(12); }
772   exit(0);
773 }