Graphviz 13.1.3~dev.20250813.1130
Loading...
Searching...
No Matches
position.c
Go to the documentation of this file.
1/*************************************************************************
2 * Copyright (c) 2011 AT&T Intellectual Property
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * https://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors: Details at https://graphviz.org
9 *************************************************************************/
10
11
12/*
13 * position(g): set ND_coord(n) (x and y) for all nodes n of g, using GD_rank(g).
14 * (the graph may be modified by merging certain edges with a common endpoint.)
15 * the coordinates are computed by constructing and ranking an auxiliary graph.
16 * then leaf nodes are inserted in the fast graph. cluster boundary nodes are
17 * created and correctly separated.
18 */
19
20#include <common/geomprocs.h>
21#include <dotgen/dot.h>
22#include <dotgen/aspect.h>
23#include <math.h>
24#include <stdbool.h>
25#include <stdlib.h>
26#include <util/alloc.h>
27#include <util/gv_math.h>
28
29static int nsiter2(graph_t * g);
30static void create_aux_edges(graph_t * g);
31static void remove_aux_edges(graph_t * g);
32static void set_xcoords(graph_t * g);
33static void set_ycoords(graph_t * g);
34static void set_aspect(graph_t *g);
35static void expand_leaves(graph_t * g);
36static void make_lrvn(graph_t * g);
37static void contain_nodes(graph_t * g);
38static bool idealsize(graph_t * g, double);
39
40#if defined(DEBUG) && DEBUG > 1
41static void
42dumpNS (graph_t * g)
43{
44 node_t* n = GD_nlist(g);
45 elist el;
46 edge_t* e;
47
48 while (n) {
49 el = ND_out(n);
50 for (size_t i = 0; i < el.size; i++) {
51 e = el.list[i];
52 fprintf (stderr, "%s(%x) -> ", agnameof(agtail(e)),agtail(e));
53 fprintf (stderr, "%s(%x) : %d\n", agnameof(aghead(e)), aghead(e),
54 ED_minlen(e));
55 }
56 n = ND_next(n);
57 }
58}
59#endif
60
61static double
62largeMinlen (double l)
63{
65 "Edge length %f larger than maximum %d allowed.\nCheck for overwide "
66 "node(s).\n",
67 l, INT_MAX);
68 return INT_MAX;
69}
70
71/* When source and/or sink nodes are defined, it is possible that
72 * after the auxiliary edges are added, the graph may still have 2 or
73 * 3 components. To fix this, we put trivial constraints connecting the
74 * first items of each rank.
75 */
76static void
78{
79 int i, j, r;
80 bool found;
81 node_t* tp;
82 node_t* hp;
83 node_t* sn;
84 edge_t* e;
85 rank_t* rp;
86
87 for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
88 rp = GD_rank(g)+r;
89 found = false;
90 tp = NULL;
91 for (i = 0; i < rp->n; i++) {
92 tp = rp->v[i];
93 if (ND_save_out(tp).list) {
94 for (j = 0; (e = ND_save_out(tp).list[j]); j++) {
95 if (ND_rank(aghead(e)) > r || ND_rank(agtail(e)) > r) {
96 found = true;
97 break;
98 }
99 }
100 if (found) break;
101 }
102 if (ND_save_in(tp).list) {
103 for (j = 0; (e = ND_save_in(tp).list[j]); j++) {
104 if (ND_rank(agtail(e)) > r || ND_rank(aghead(e)) > r) {
105 found = true;
106 break;
107 }
108 }
109 if (found) break;
110 }
111 }
112 if (found || !tp) continue;
113 tp = rp->v[0];
114 if (r < GD_maxrank(g)) hp = (rp+1)->v[0];
115 else hp = (rp-1)->v[0];
116 assert (hp);
117 sn = virtual_node(g);
119 make_aux_edge(sn, tp, 0, 0);
120 make_aux_edge(sn, hp, 0, 0);
121 ND_rank(sn) = MIN(ND_rank(tp), ND_rank(hp));
122 }
123}
124
126 if (GD_nlist(g) == NULL)
127 return; /* ignore empty graph */
128 mark_lowclusters(g); /* we could remove from splines.c now */
129 set_ycoords(g);
130 if (Concentrate)
132 expand_leaves(g);
133 if (flat_edges(g))
134 set_ycoords(g);
136 if (rank(g, 2, nsiter2(g))) { /* LR balance == 2 */
137 connectGraph (g);
138 const int rank_result = rank(g, 2, nsiter2(g));
139 assert(rank_result == 0);
140 (void)rank_result;
141 }
142 set_xcoords(g);
143 set_aspect(g);
144 remove_aux_edges(g); /* must come after set_aspect since we now
145 * use GD_ln and GD_rn for bbox width.
146 */
147}
148
149static int nsiter2(graph_t * g)
150{
151 int maxiter = INT_MAX;
152 char *s;
153
154 if ((s = agget(g, "nslimit")))
155 maxiter = scale_clamp(agnnodes(g), atof(s));
156 return maxiter;
157}
158
159static bool go(node_t *u, node_t *v) {
160 int i;
161 edge_t *e;
162
163 if (u == v)
164 return true;
165 for (i = 0; (e = ND_out(u).list[i]); i++) {
166 if (go(aghead(e), v))
167 return true;
168 }
169 return false;
170}
171
172static bool canreach(node_t *u, node_t *v) {
173 return go(u, v);
174}
175
176edge_t *make_aux_edge(node_t * u, node_t * v, double len, int wt)
177{
178 Agedgepair_t* e2 = gv_alloc(sizeof(Agedgepair_t));
179 AGTYPE(&e2->in) = AGINEDGE;
180 AGTYPE(&e2->out) = AGOUTEDGE;
181 e2->out.base.data = gv_alloc(sizeof(Agedgeinfo_t));
182 edge_t *const e = &e2->out;
183
184 agtail(e) = u;
185 aghead(e) = v;
186 if (len > INT_MAX)
187 len = largeMinlen (len);
188 ED_minlen(e) = ROUND(len);
189 ED_weight(e) = wt;
190 fast_edge(e);
191 return e;
192}
193
195{
196 int i, j, n_in;
197 node_t *n;
198
199 /* allocate space for aux edge lists */
200 for (n = GD_nlist(g); n; n = ND_next(n)) {
201 ND_save_in(n) = ND_in(n);
202 ND_save_out(n) = ND_out(n);
203 for (i = 0; ND_out(n).list[i]; i++);
204 for (j = 0; ND_in(n).list[j]; j++);
205 n_in = i + j;
206 alloc_elist(n_in + 3, ND_in(n));
207 alloc_elist(3, ND_out(n));
208 }
209}
210
211static void
213{
214 int i, j;
215 int m0;
216 double width;
217 int sep[2];
218 int nodesep; /* separation between nodes on same rank */
219 edge_t *e, *e0, *e1;
220 node_t *u, *v, *t0, *h0;
221 rank_t *rank = GD_rank(g);
222
223 /* Use smaller separation on odd ranks if g has edge labels */
224 if (GD_has_labels(g->root) & EDGE_LABEL) {
225 sep[0] = GD_nodesep(g);
226 sep[1] = 5;
227 }
228 else {
229 sep[1] = sep[0] = GD_nodesep(g);
230 }
231 /* make edges to constrain left-to-right ordering */
232 for (i = GD_minrank(g); i <= GD_maxrank(g); i++) {
233 double last = ND_rank(rank[i].v[0]) = 0;
234 nodesep = sep[i & 1];
235 for (j = 0; j < rank[i].n; j++) {
236 u = rank[i].v[j];
237 ND_mval(u) = ND_rw(u); /* keep it somewhere safe */
238 if (ND_other(u).size > 0) { /* compute self size */
239 /* FIX: dot assumes all self-edges go to the right. This
240 * is no longer true, though makeSelfEdge still attempts to
241 * put as many as reasonable on the right. The dot code
242 * should be modified to allow a box reflecting the placement
243 * of all self-edges, and use that to reposition the nodes.
244 * Note that this would not only affect left and right
245 * positioning but may also affect interrank spacing.
246 */
247 double sw = 0; // self width
248 for (size_t k = 0; (e = ND_other(u).list[k]); k++) {
249 if (agtail(e) == aghead(e)) {
250 sw += selfRightSpace (e);
251 }
252 }
253 ND_rw(u) += sw; /* increment to include self edges */
254 }
255 v = rank[i].v[j + 1];
256 if (v) {
257 width = ND_rw(u) + ND_lw(v) + nodesep;
258 e0 = make_aux_edge(u, v, width, 0);
259 last = (ND_rank(v) = last + width);
260 }
261
262 /* constraints from labels of flat edges on previous rank */
263 if ((e = ND_alg(u))) {
264 e0 = ND_save_out(u).list[0];
265 e1 = ND_save_out(u).list[1];
266 if (ND_order(aghead(e0)) > ND_order(aghead(e1))) {
267 SWAP(&e0, &e1);
268 }
269 m0 = ED_minlen(e) * GD_nodesep(g) / 2;
270 double m1 = m0 + ND_rw(aghead(e0)) + ND_lw(agtail(e0));
271 /* these guards are needed because the flat edges
272 * work very poorly with cluster layout */
273 if (!canreach(agtail(e0), aghead(e0)))
274 make_aux_edge(aghead(e0), agtail(e0), m1,
275 ED_weight(e));
276 m1 = m0 + ND_rw(agtail(e1)) + ND_lw(aghead(e1));
277 if (!canreach(aghead(e1), agtail(e1)))
278 make_aux_edge(agtail(e1), aghead(e1), m1,
279 ED_weight(e));
280 }
281
282 /* position flat edge endpoints */
283 for (size_t k = 0; k < ND_flat_out(u).size; k++) {
284 e = ND_flat_out(u).list[k];
285 if (ND_order(agtail(e)) < ND_order(aghead(e))) {
286 t0 = agtail(e);
287 h0 = aghead(e);
288 } else {
289 t0 = aghead(e);
290 h0 = agtail(e);
291 }
292
293 width = ND_rw(t0) + ND_lw(h0);
294 m0 = ED_minlen(e) * GD_nodesep(g) + width;
295
296 if ((e0 = find_fast_edge(t0, h0))) {
297 /* flat edge between adjacent neighbors
298 * ED_dist contains the largest label width.
299 */
300 m0 = MAX(m0, width + GD_nodesep(g) + ROUND(ED_dist(e)));
301 ED_minlen(e0) = MAX(ED_minlen(e0), m0);
302 ED_weight(e0) = MAX(ED_weight(e0), ED_weight(e));
303 }
304 else if (!ED_label(e)) {
305 /* unlabeled flat edge between non-neighbors
306 * ED_minlen(e) is max of ED_minlen of all equivalent
307 * edges.
308 */
309 make_aux_edge(t0, h0, m0, ED_weight(e));
310 }
311 /* labeled flat edges between non-neighbors have already
312 * been constrained by the label above.
313 */
314 }
315 }
316 }
317}
318
320static void make_edge_pairs(graph_t * g)
321{
322 int i, m0, m1;
323 node_t *n, *sn;
324 edge_t *e;
325
326 for (n = GD_nlist(g); n; n = ND_next(n)) {
327 if (ND_save_out(n).list)
328 for (i = 0; (e = ND_save_out(n).list[i]); i++) {
329 sn = virtual_node(g);
331 m0 = (ED_head_port(e).p.x - ED_tail_port(e).p.x);
332 if (m0 > 0)
333 m1 = 0;
334 else {
335 m1 = -m0;
336 m0 = 0;
337 }
338 make_aux_edge(sn, agtail(e), m0 + 1, ED_weight(e));
339 make_aux_edge(sn, aghead(e), m1 + 1, ED_weight(e));
340 ND_rank(sn) =
341 MIN(ND_rank(agtail(e)) - m0 - 1,
342 ND_rank(aghead(e)) - m1 - 1);
343 }
344 }
345}
346
348{
349 int c;
350 edge_t *e;
351
352 if (g != dot_root(g)) {
353 contain_nodes(g);
354 if ((e = find_fast_edge(GD_ln(g),GD_rn(g)))) /* maybe from lrvn()?*/
355 ED_weight(e) += 128;
356 else
357 make_aux_edge(GD_ln(g), GD_rn(g), 1, 128); /* clust compaction edge */
358 }
359 for (c = 1; c <= GD_n_cluster(g); c++)
361}
362
364 edge_t *e;
365
366 if (ND_node_type(v) != VIRTUAL)
367 return false;
368 for (e = ND_save_out(v).list[0]; ED_to_orig(e); e = ED_to_orig(e));
369 if (agcontains(g, agtail(e)))
370 return false;
371 if (agcontains(g, aghead(e)))
372 return false;
373 return true;
374}
375
376/* Guarantee nodes outside the cluster g are placed outside of it.
377 * This is done by adding constraints to make sure such nodes have
378 * a gap of margin from the left or right bounding box node ln or rn.
379 *
380 * We could probably reduce some of these constraints by checking if
381 * the node is in a cluster, since elsewhere we make constrain a
382 * separate between clusters. Also, we should be able to skip the
383 * first loop if g is the root graph.
384 */
386{
387 int i, c, r, margin;
388 node_t *u, *v;
389
390 margin = late_int (g, G_margin, CL_OFFSET, 0);
391 for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
392 if (GD_rank(g)[r].n == 0)
393 continue;
394 v = GD_rank(g)[r].v[0];
395 if (v == NULL)
396 continue;
397 for (i = ND_order(v) - 1; i >= 0; i--) {
398 u = GD_rank(dot_root(g))[r].v[i];
399 /* can't use "is_a_vnode_of" because elists are swapped */
400 if (ND_node_type(u) == NORMAL || vnode_not_related_to(g, u)) {
401 make_aux_edge(u, GD_ln(g), margin + ND_rw(u), 0);
402 break;
403 }
404 }
405 for (i = ND_order(v) + GD_rank(g)[r].n; i < GD_rank(dot_root(g))[r].n;
406 i++) {
407 u = GD_rank(dot_root(g))[r].v[i];
408 if (ND_node_type(u) == NORMAL || vnode_not_related_to(g, u)) {
409 make_aux_edge(GD_rn(g), u, margin + ND_lw(u), 0);
410 break;
411 }
412 }
413 }
414
415 for (c = 1; c <= GD_n_cluster(g); c++)
417}
418
419/* Make sure boxes of subclusters of g are offset from the
420 * box of g. This is done by a constraint between the left and
421 * right bounding box nodes ln and rn of g and a subcluster.
422 * The gap needs to include any left or right labels.
423 */
424static void contain_subclust(graph_t * g)
425{
426 int margin, c;
427 graph_t *subg;
428
429 margin = late_int (g, G_margin, CL_OFFSET, 0);
430 make_lrvn(g);
431 for (c = 1; c <= GD_n_cluster(g); c++) {
432 subg = GD_clust(g)[c];
433 make_lrvn(subg);
434 make_aux_edge(GD_ln(g), GD_ln(subg),
435 margin + GD_border(g)[LEFT_IX].x, 0);
436 make_aux_edge(GD_rn(subg), GD_rn(g),
437 margin + GD_border(g)[RIGHT_IX].x, 0);
438 contain_subclust(subg);
439 }
440}
441
442/* Guarantee space between subcluster of g.
443 * This is done by adding a constraint between the right bbox node rn
444 * of the left cluster and the left bbox node ln of the right cluster.
445 * This is only done if the two clusters overlap in some rank.
446 */
448{
449 int i, j, margin;
450 graph_t *low, *high;
451 graph_t *left, *right;
452
453 margin = late_int (g, G_margin, CL_OFFSET, 0);
454 for (i = 1; i <= GD_n_cluster(g); i++)
455 make_lrvn(GD_clust(g)[i]);
456 for (i = 1; i <= GD_n_cluster(g); i++) {
457 for (j = i + 1; j <= GD_n_cluster(g); j++) {
458 low = GD_clust(g)[i];
459 high = GD_clust(g)[j];
460 if (GD_minrank(low) > GD_minrank(high)) {
461 SWAP(&low, &high);
462 }
463 if (GD_maxrank(low) < GD_minrank(high))
464 continue;
465 if (ND_order(GD_rank(low)[GD_minrank(high)].v[0])
466 < ND_order(GD_rank(high)[GD_minrank(high)].v[0])) {
467 left = low;
468 right = high;
469 } else {
470 left = high;
471 right = low;
472 }
473 make_aux_edge(GD_rn(left), GD_ln(right), margin, 0);
474 }
476 }
477}
478
479/* create constraints for:
480 * node containment in clusters,
481 * cluster containment in clusters,
482 * separation of sibling clusters.
483 */
484static void pos_clusters(graph_t * g)
485{
486 if (GD_n_cluster(g) > 0) {
491 }
492}
493
494static void compress_graph(graph_t * g)
495{
496 double x;
497 pointf p;
498
499 if (GD_drawing(g)->ratio_kind != R_COMPRESS)
500 return;
501 p = GD_drawing(g)->size;
502 if (p.x * p.y <= 1)
503 return;
504 contain_nodes(g);
505 if (!GD_flip(g))
506 x = p.x;
507 else
508 x = p.y;
509
510 /* Guard against huge size attribute since max. edge length is USHRT_MAX
511 * A warning might be called for. Also, one could check that the graph
512 * already fits GD_drawing(g)->size and return immediately.
513 */
514 x = MIN(x,USHRT_MAX);
515 make_aux_edge(GD_ln(g), GD_rn(g), x, 1000);
516}
517
518static void create_aux_edges(graph_t * g)
519{
523 pos_clusters(g);
525}
526
527static void remove_aux_edges(graph_t * g)
528{
529 int i;
530 node_t *n, *nnext, *nprev;
531 edge_t *e;
532
533 for (n = GD_nlist(g); n; n = ND_next(n)) {
534 for (i = 0; (e = ND_out(n).list[i]); i++) {
535 free(e->base.data);
536 free(e);
537 }
538 free_list(ND_out(n));
539 free_list(ND_in(n));
540 ND_out(n) = ND_save_out(n);
541 ND_in(n) = ND_save_in(n);
542 }
543 /* cannot be merged with previous loop */
544 nprev = NULL;
545 for (n = GD_nlist(g); n; n = nnext) {
546 nnext = ND_next(n);
547 if (ND_node_type(n) == SLACKNODE) {
548 if (nprev)
549 ND_next(nprev) = nnext;
550 else
551 GD_nlist(g) = nnext;
552 if (nnext != NULL) {
553 ND_prev(nnext) = nprev;
554 }
555 free(n->base.data);
556 free(n);
557 } else
558 nprev = n;
559 }
560}
561
563static void
565{
566 int i, j;
567 node_t *v;
568 rank_t *rank = GD_rank(g);
569
570 for (i = GD_minrank(g); i <= GD_maxrank(g); i++) {
571 for (j = 0; j < rank[i].n; j++) {
572 v = rank[i].v[j];
573 ND_coord(v).x = ND_rank(v);
574 ND_rank(v) = i;
575 }
576 }
577}
578
579/* Expand cluster height by delta, adding half to top
580 * and half to bottom. If the bottom expansion exceeds the
581 * ht1 of the rank, shift the ranks in the cluster up.
582 * If the top expansion, including any shift from the bottom
583 * expansion, exceeds to ht2 of the rank, shift the ranks above
584 * the cluster up.
585 *
586 * FIX: There can be excess space between ranks. Not sure where this is
587 * coming from but it could be cleaned up.
588 */
589static void adjustSimple(graph_t *g, double delta, int margin_total) {
590 int r;
591 double deltop;
592 graph_t *root = dot_root(g);
593 rank_t *rank = GD_rank(root);
594 int maxr = GD_maxrank(g);
595 int minr = GD_minrank(g);
596
597 const double bottom = (delta + 1) / 2;
598 const double delbottom = GD_ht1(g) + bottom - (rank[maxr].ht1 - margin_total);
599 if (delbottom > 0) {
600 for (r = maxr; r >= minr; r--) {
601 if (rank[r].n > 0)
602 ND_coord(rank[r].v[0]).y += delbottom;
603 }
604 deltop = GD_ht2(g) + (delta-bottom) + delbottom - (rank[minr].ht2 - margin_total);
605 }
606 else
607 deltop = GD_ht2(g) + (delta-bottom) - (rank[minr].ht2 - margin_total);
608 if (deltop > 0) {
609 for (r = minr-1; r >= GD_minrank(root); r--) {
610 if (rank[r].n > 0)
611 ND_coord(rank[r].v[0]).y += deltop;
612 }
613 }
614 GD_ht2(g) += delta - bottom;
615 GD_ht1(g) += bottom;
616}
617
618/* Recursively adjust ranks to take into account
619 * wide cluster labels when rankdir=LR.
620 * We divide the extra space between the top and bottom.
621 * Adjust the ht1 and ht2 values in the process.
622 */
623static void adjustRanks(graph_t * g, int margin_total)
624{
625 double lht; /* label height */
626 double rht; /* height between top and bottom ranks */
627 int maxr, minr, margin;
628 int c;
629 double delta, ht1, ht2;
630
632 if (g == dot_root(g))
633 margin = 0;
634 else
635 margin = late_int (g, G_margin, CL_OFFSET, 0);
636
637 ht1 = GD_ht1(g);
638 ht2 = GD_ht2(g);
639
640 for (c = 1; c <= GD_n_cluster(g); c++) {
641 graph_t *subg = GD_clust(g)[c];
642 adjustRanks(subg, margin+margin_total);
643 if (GD_maxrank(subg) == GD_maxrank(g))
644 ht1 = fmax(ht1, GD_ht1(subg) + margin);
645 if (GD_minrank(subg) == GD_minrank(g))
646 ht2 = fmax(ht2, GD_ht2(subg) + margin);
647 }
648
649 GD_ht1(g) = ht1;
650 GD_ht2(g) = ht2;
651
652 if (g != dot_root(g) && GD_label(g)) {
653 lht = MAX(GD_border(g)[LEFT_IX].y, GD_border(g)[RIGHT_IX].y);
654 maxr = GD_maxrank(g);
655 minr = GD_minrank(g);
656 rht = ND_coord(rank[minr].v[0]).y - ND_coord(rank[maxr].v[0]).y;
657 delta = lht - (rht + ht1 + ht2);
658 if (delta > 0) {
659 adjustSimple(g, delta, margin_total);
660 }
661 }
662
663 /* update the global ranks */
664 if (g != dot_root(g)) {
665 rank[GD_minrank(g)].ht2 = fmax(rank[GD_minrank(g)].ht2, GD_ht2(g));
666 rank[GD_maxrank(g)].ht1 = fmax(rank[GD_maxrank(g)].ht1, GD_ht1(g));
667 }
668}
669
670/* recursively compute cluster ht requirements. assumes GD_ht1(subg) and ht2
671 * are computed from primitive nodes only. updates ht1 and ht2 to reflect
672 * cluster nesting and labels. also maintains global rank ht1 and ht2.
673 * Return true if some cluster has a label.
674 */
675static int clust_ht(Agraph_t * g)
676{
677 int c;
678 double ht1, ht2;
679 graph_t *subg;
681 int margin, haveClustLabel = 0;
682
683 if (g == dot_root(g))
684 margin = CL_OFFSET;
685 else
686 margin = late_int (g, G_margin, CL_OFFSET, 0);
687
688 ht1 = GD_ht1(g);
689 ht2 = GD_ht2(g);
690
691 /* account for sub-clusters */
692 for (c = 1; c <= GD_n_cluster(g); c++) {
693 subg = GD_clust(g)[c];
694 haveClustLabel |= clust_ht(subg);
695 if (GD_maxrank(subg) == GD_maxrank(g))
696 ht1 = MAX(ht1, GD_ht1(subg) + margin);
697 if (GD_minrank(subg) == GD_minrank(g))
698 ht2 = MAX(ht2, GD_ht2(subg) + margin);
699 }
700
701 /* account for a possible cluster label in clusters */
702 /* room for root graph label is handled in dotneato_postprocess */
703 if (g != dot_root(g) && GD_label(g)) {
704 haveClustLabel = 1;
705 if (!GD_flip(agroot(g))) {
706 ht1 += GD_border(g)[BOTTOM_IX].y;
707 ht2 += GD_border(g)[TOP_IX].y;
708 }
709 }
710 GD_ht1(g) = ht1;
711 GD_ht2(g) = ht2;
712
713 /* update the global ranks */
714 if (g != dot_root(g)) {
715 rank[GD_minrank(g)].ht2 = MAX(rank[GD_minrank(g)].ht2, ht2);
716 rank[GD_maxrank(g)].ht1 = MAX(rank[GD_maxrank(g)].ht1, ht1);
717 }
718
719 return haveClustLabel;
720}
721
722/* set y coordinates of nodes, a rank at a time */
723static void set_ycoords(graph_t * g)
724{
725 int i, j, r;
726 double ht2, maxht, delta, d0, d1;
727 node_t *n;
728 edge_t *e;
729 rank_t *rank = GD_rank(g);
730 graph_t *clust;
731 int lbl;
732
733 ht2 = maxht = 0;
734
735 /* scan ranks for tallest nodes. */
736 for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
737 for (i = 0; i < rank[r].n; i++) {
738 n = rank[r].v[i];
739
740 /* assumes symmetry, ht1 = ht2 */
741 ht2 = ND_ht(n) / 2;
742
743
744 /* have to look for high self-edge labels, too */
745 if (ND_other(n).list)
746 for (j = 0; (e = ND_other(n).list[j]); j++) {
747 if (agtail(e) == aghead(e)) {
748 if (ED_label(e))
749 ht2 = fmax(ht2, ED_label(e)->dimen.y / 2);
750 }
751 }
752
753 /* update global rank ht */
754 if (rank[r].pht2 < ht2)
755 rank[r].pht2 = rank[r].ht2 = ht2;
756 if (rank[r].pht1 < ht2)
757 rank[r].pht1 = rank[r].ht1 = ht2;
758
759 /* update nearest enclosing cluster rank ht */
760 if ((clust = ND_clust(n))) {
761 int yoff = (clust == g ? 0 : late_int (clust, G_margin, CL_OFFSET, 0));
762 if (ND_rank(n) == GD_minrank(clust))
763 GD_ht2(clust) = fmax(GD_ht2(clust), ht2 + yoff);
764 if (ND_rank(n) == GD_maxrank(clust))
765 GD_ht1(clust) = fmax(GD_ht1(clust), ht2 + yoff);
766 }
767 }
768 }
769
770 /* scan sub-clusters */
771 lbl = clust_ht(g);
772
773 /* make the initial assignment of ycoords to leftmost nodes by ranks */
774 maxht = 0;
775 r = GD_maxrank(g);
776 ND_coord(rank[r].v[0]).y = rank[r].ht1;
777 while (--r >= GD_minrank(g)) {
778 d0 = rank[r + 1].pht2 + rank[r].pht1 + GD_ranksep(g); /* prim node sep */
779 d1 = rank[r + 1].ht2 + rank[r].ht1 + CL_OFFSET; /* cluster sep */
780 delta = fmax(d0, d1);
781 if (rank[r].n > 0) /* this may reflect some problem */
782 ND_coord(rank[r].v[0]).y = ND_coord(rank[r + 1].v[0]).y + delta;
783#ifdef DEBUG
784 else
785 fprintf(stderr, "dot set_ycoords: rank %d is empty\n",
786 rank[r].n);
787#endif
788 maxht = fmax(maxht, delta);
789 }
790
791 /* If there are cluster labels and the drawing is rotated, we need special processing to
792 * allocate enough room. We use adjustRanks for this, and then recompute the maxht if
793 * the ranks are to be equally spaced. This seems simpler and appears to work better than
794 * handling equal spacing as a special case.
795 */
796 if (lbl && GD_flip(g)) {
797 adjustRanks(g, 0);
798 if (GD_exact_ranksep(g)) { /* recompute maxht */
799 maxht = 0;
800 r = GD_maxrank(g);
801 d0 = ND_coord(rank[r].v[0]).y;
802 while (--r >= GD_minrank(g)) {
803 d1 = ND_coord(rank[r].v[0]).y;
804 delta = d1 - d0;
805 maxht = fmax(maxht, delta);
806 d0 = d1;
807 }
808 }
809 }
810
811 /* re-assign if ranks are equally spaced */
812 if (GD_exact_ranksep(g)) {
813 for (r = GD_maxrank(g) - 1; r >= GD_minrank(g); r--)
814 if (rank[r].n > 0) /* this may reflect the same problem :-() */
815 ND_coord(rank[r].v[0]).y = ND_coord(rank[r + 1].v[0]).y + maxht;
816 }
817
818 /* copy ycoord assignment from leftmost nodes to others */
819 for (n = GD_nlist(g); n; n = ND_next(n))
820 ND_coord(n).y = ND_coord(rank[ND_rank(n)].v[0]).y;
821}
822
823/* Compute bounding box of g.
824 * The x limits of clusters are given by the x positions of ln and rn.
825 * This information is stored in the rank field, since it was calculated
826 * using network simplex.
827 * For the root graph, we don't enforce all the constraints on lr and
828 * rn, so we traverse the nodes and subclusters.
829 */
830static void dot_compute_bb(graph_t * g, graph_t * root)
831{
832 int r, c;
833 double x, offset;
834 node_t *v;
835 pointf LL, UR;
836
837 if (g == dot_root(g)) {
838 LL.x = INT_MAX;
839 UR.x = -INT_MAX;
840 for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
841 int rnkn = GD_rank(g)[r].n;
842 if (rnkn == 0)
843 continue;
844 if ((v = GD_rank(g)[r].v[0]) == NULL)
845 continue;
846 for (c = 1; ND_node_type(v) != NORMAL && c < rnkn; c++)
847 v = GD_rank(g)[r].v[c];
848 if (ND_node_type(v) == NORMAL) {
849 x = ND_coord(v).x - ND_lw(v);
850 LL.x = MIN(LL.x, x);
851 }
852 else continue;
853 /* At this point, we know the rank contains a NORMAL node */
854 v = GD_rank(g)[r].v[rnkn - 1];
855 for (c = rnkn-2; ND_node_type(v) != NORMAL; c--)
856 v = GD_rank(g)[r].v[c];
857 x = ND_coord(v).x + ND_rw(v);
858 UR.x = MAX(UR.x, x);
859 }
860 offset = CL_OFFSET;
861 for (c = 1; c <= GD_n_cluster(g); c++) {
862 x = (double)(GD_bb(GD_clust(g)[c]).LL.x - offset);
863 LL.x = MIN(LL.x, x);
864 x = (double)(GD_bb(GD_clust(g)[c]).UR.x + offset);
865 UR.x = MAX(UR.x, x);
866 }
867 } else {
868 LL.x = ND_rank(GD_ln(g));
869 UR.x = ND_rank(GD_rn(g));
870 }
871 LL.y = ND_coord(GD_rank(root)[GD_maxrank(g)].v[0]).y - GD_ht1(g);
872 UR.y = ND_coord(GD_rank(root)[GD_minrank(g)].v[0]).y + GD_ht2(g);
873 GD_bb(g).LL = LL;
874 GD_bb(g).UR = UR;
875}
876
877static void rec_bb(graph_t * g, graph_t * root)
878{
879 int c;
880 for (c = 1; c <= GD_n_cluster(g); c++)
881 rec_bb(GD_clust(g)[c], root);
882 dot_compute_bb(g, root);
883}
884
885/* Recursively rescale all bounding boxes using scale factors
886 * xf and yf. We assume all the bboxes have been computed.
887 */
888static void scale_bb(graph_t *g, double xf, double yf) {
889 int c;
890
891 for (c = 1; c <= GD_n_cluster(g); c++)
892 scale_bb(GD_clust(g)[c], xf, yf);
893 GD_bb(g).LL.x *= xf;
894 GD_bb(g).LL.y *= yf;
895 GD_bb(g).UR.x *= xf;
896 GD_bb(g).UR.y *= yf;
897}
898
899/* Set bounding boxes and, if ratio is set, rescale graph.
900 * Note that if some dimension shrinks, there may be problems
901 * with labels.
902 */
903static void set_aspect(graph_t *g) {
904 double xf = 0.0, yf = 0.0, actual, desired;
905 node_t *n;
906 bool filled;
907
908 rec_bb(g, g);
909 if (GD_maxrank(g) > 0 && GD_drawing(g)->ratio_kind) {
910 pointf sz = sub_pointf(GD_bb(g).UR, GD_bb(g).LL); // normalize
911 if (GD_flip(g)) {
912 sz = exch_xyf(sz);
913 }
914 bool scale_it = true;
915 if (GD_drawing(g)->ratio_kind == R_AUTO)
916 filled = idealsize(g, .5);
917 else
918 filled = GD_drawing(g)->ratio_kind == R_FILL;
919 if (filled) {
920 /* fill is weird because both X and Y can stretch */
921 if (GD_drawing(g)->size.x <= 0)
922 scale_it = false;
923 else {
924 xf = GD_drawing(g)->size.x / sz.x;
925 yf = GD_drawing(g)->size.y / sz.y;
926 if (xf < 1.0 || yf < 1.0) {
927 if (xf < yf) {
928 yf /= xf;
929 xf = 1.0;
930 } else {
931 xf /= yf;
932 yf = 1.0;
933 }
934 }
935 }
936 } else if (GD_drawing(g)->ratio_kind == R_EXPAND) {
937 if (GD_drawing(g)->size.x <= 0)
938 scale_it = false;
939 else {
940 xf = GD_drawing(g)->size.x / GD_bb(g).UR.x;
941 yf = GD_drawing(g)->size.y / GD_bb(g).UR.y;
942 if (xf > 1.0 && yf > 1.0) {
943 const double scale = fmin(xf, yf);
944 xf = yf = scale;
945 } else
946 scale_it = false;
947 }
948 } else if (GD_drawing(g)->ratio_kind == R_VALUE) {
949 desired = GD_drawing(g)->ratio;
950 actual = sz.y / sz.x;
951 if (actual < desired) {
952 yf = desired / actual;
953 xf = 1.0;
954 } else {
955 xf = actual / desired;
956 yf = 1.0;
957 }
958 } else
959 scale_it = false;
960 if (scale_it) {
961 if (GD_flip(g)) {
962 SWAP(&xf, &yf);
963 }
964 for (n = GD_nlist(g); n; n = ND_next(n)) {
965 ND_coord(n).x = round(ND_coord(n).x * xf);
966 ND_coord(n).y = round(ND_coord(n).y * yf);
967 }
968 scale_bb(g, xf, yf);
969 }
970 }
971}
972
973/* make space for the leaf nodes of each rank */
974static void make_leafslots(graph_t * g)
975{
976 int i, j, r;
977 node_t *v;
978
979 for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
980 j = 0;
981 for (i = 0; i < GD_rank(g)[r].n; i++) {
982 v = GD_rank(g)[r].v[i];
983 ND_order(v) = j;
984 if (ND_ranktype(v) == LEAFSET)
985 j = j + ND_UF_size(v);
986 else
987 j++;
988 }
989 if (j <= GD_rank(g)[r].n)
990 continue;
991 node_t **new_v = gv_calloc(j + 1, sizeof(node_t*));
992 for (i = GD_rank(g)[r].n - 1; i >= 0; i--) {
993 v = GD_rank(g)[r].v[i];
994 new_v[ND_order(v)] = v;
995 }
996 GD_rank(g)[r].n = j;
997 new_v[j] = NULL;
998 free(GD_rank(g)[r].v);
999 GD_rank(g)[r].v = new_v;
1000 }
1001}
1002
1004{
1005 return ED_head_port(e).defined == ED_head_port(f).defined
1006 && ((ED_head_port(e).p.x == ED_head_port(f).p.x &&
1007 ED_head_port(e).p.y == ED_head_port(f).p.y)
1008 || !ED_head_port(e).defined)
1009 && ((ED_tail_port(e).p.x == ED_tail_port(f).p.x &&
1010 ED_tail_port(e).p.y == ED_tail_port(f).p.y)
1011 || !ED_tail_port(e).defined);
1012}
1013
1014static void expand_leaves(graph_t * g)
1015{
1016 int i, d;
1017 node_t *n;
1018 edge_t *e, *f;
1019
1020 make_leafslots(g);
1021 for (n = GD_nlist(g); n; n = ND_next(n)) {
1022 if (ND_other(n).list)
1023 for (i = 0; (e = ND_other(n).list[i]); i++) {
1024 if ((d = ND_rank(aghead(e)) - ND_rank(aghead(e))) == 0)
1025 continue;
1026 f = ED_to_orig(e);
1027 if (!ports_eq(e, f)) {
1028 zapinlist(&(ND_other(n)), e);
1029 if (d == 1)
1030 fast_edge(e);
1031 /*else unitize(e); ### */
1032 i--;
1033 }
1034 }
1035 }
1036}
1037
1038/* Add left and right slacknodes to a cluster which
1039 * are used in the LP to constrain nodes not in g but
1040 * sharing its ranks to be to the left or right of g
1041 * by a specified amount.
1042 * The slacknodes ln and rn give the x position of the
1043 * left and right side of the cluster's bounding box. In
1044 * particular, any cluster labels on the left or right side
1045 * are inside.
1046 * If a cluster has a label, and we have rankdir!=LR, we make
1047 * sure the cluster is wide enough for the label. Note that
1048 * if the label is wider than the cluster, the nodes in the
1049 * cluster may not be centered.
1050 */
1051static void make_lrvn(graph_t * g)
1052{
1053 node_t *ln, *rn;
1054
1055 if (GD_ln(g))
1056 return;
1057 ln = virtual_node(dot_root(g));
1058 ND_node_type(ln) = SLACKNODE;
1059 rn = virtual_node(dot_root(g));
1060 ND_node_type(rn) = SLACKNODE;
1061
1062 if (GD_label(g) && g != dot_root(g) && !GD_flip(agroot(g))) {
1063 const double w = fmax(GD_border(g)[BOTTOM_IX].x, GD_border(g)[TOP_IX].x);
1064 make_aux_edge(ln, rn, w, 0);
1065 }
1066
1067 GD_ln(g) = ln;
1068 GD_rn(g) = rn;
1069}
1070
1071/* make left and right bounding box virtual nodes ln and rn
1072 * constrain interior nodes
1073 */
1074static void contain_nodes(graph_t * g)
1075{
1076 int margin, r;
1077 node_t *ln, *rn, *v;
1078
1079 margin = late_int (g, G_margin, CL_OFFSET, 0);
1080 make_lrvn(g);
1081 ln = GD_ln(g);
1082 rn = GD_rn(g);
1083 for (r = GD_minrank(g); r <= GD_maxrank(g); r++) {
1084 if (GD_rank(g)[r].n == 0)
1085 continue;
1086 v = GD_rank(g)[r].v[0];
1087 if (v == NULL) {
1088 agerrorf("contain_nodes clust %s rank %d missing node\n",
1089 agnameof(g), r);
1090 continue;
1091 }
1092 make_aux_edge(ln, v,
1093 ND_lw(v) + margin + GD_border(g)[LEFT_IX].x, 0);
1094 v = GD_rank(g)[r].v[GD_rank(g)[r].n - 1];
1095 make_aux_edge(v, rn,
1096 ND_rw(v) + margin + GD_border(g)[RIGHT_IX].x, 0);
1097 }
1098}
1099
1100/* set g->drawing->size to a reasonable default.
1101 * returns a boolean to indicate if drawing is to
1102 * be scaled and filled */
1103static bool idealsize(graph_t * g, double minallowed)
1104{
1105 double xf, yf, f, R;
1106 pointf b, relpage, margin;
1107
1108 /* try for one page */
1109 relpage = GD_drawing(g)->page;
1110 if (relpage.x < 0.001 || relpage.y < 0.001)
1111 return false; /* no page was specified */
1112 margin = GD_drawing(g)->margin;
1113 relpage = sub_pointf(relpage, margin);
1114 relpage = sub_pointf(relpage, margin);
1115 b.x = GD_bb(g).UR.x;
1116 b.y = GD_bb(g).UR.y;
1117 xf = relpage.x / b.x;
1118 yf = relpage.y / b.y;
1119 if (xf >= 1.0 && yf >= 1.0)
1120 return false; /* fits on one page */
1121
1122 f = MIN(xf, yf);
1123 xf = yf = MAX(f, minallowed);
1124
1125 R = ceil(xf * b.x / relpage.x);
1126 xf = R * relpage.x / b.x;
1127 R = ceil(yf * b.y / relpage.y);
1128 yf = R * relpage.y / b.y;
1129 GD_drawing(g)->size.x = b.x * xf;
1130 GD_drawing(g)->size.y = b.y * yf;
1131 return true;
1132}
static agxbuf last
last message
Definition agerror.c:29
Memory allocation wrappers that exit on failure.
static void * gv_calloc(size_t nmemb, size_t size)
Definition alloc.h:26
static void * gv_alloc(size_t size)
Definition alloc.h:47
#define MIN(a, b)
Definition arith.h:28
#define ROUND(f)
Definition arith.h:48
#define right(i)
Definition closest.c:80
int late_int(void *obj, attrsym_t *attr, int defaultValue, int minimum)
Definition utils.c:36
void dot_concentrate(graph_t *g)
Definition conc.c:200
#define BOTTOM_IX
Definition const.h:111
#define TOP_IX
Definition const.h:113
#define NORMAL
Definition const.h:24
#define CL_OFFSET
Definition const.h:151
#define EDGE_LABEL
Definition const.h:176
#define LEFT_IX
Definition const.h:114
#define SLACKNODE
Definition const.h:26
#define VIRTUAL
Definition const.h:25
#define LEAFSET
Definition const.h:39
#define RIGHT_IX
Definition const.h:112
Agraph_t * dot_root(void *p)
Definition dotinit.c:520
int flat_edges(Agraph_t *)
Definition flat.c:257
void zapinlist(elist *, Agedge_t *)
remove e from list and fill hole with last member of list
Definition fastgr.c:94
Agedge_t * fast_edge(Agedge_t *)
Definition fastgr.c:69
Agedge_t * find_fast_edge(Agnode_t *, Agnode_t *)
Definition fastgr.c:41
Agnode_t * virtual_node(Agraph_t *)
Definition fastgr.c:198
#define left
Definition dthdr.h:12
geometric functions (e.g. on points and boxes)
static pointf sub_pointf(pointf p, pointf q)
Definition geomprocs.h:97
static pointf scale(double c, pointf p)
Definition geomprocs.h:155
static pointf exch_xyf(pointf p)
Definition geomprocs.h:133
bool Concentrate
Definition globals.h:59
Agsym_t * G_margin
Definition globals.h:72
static double len(glCompPoint p)
Definition glutils.c:136
void free(void *)
node NULL
Definition grammar.y:181
int agnnodes(Agraph_t *g)
Definition graph.c:155
char * agget(void *obj, char *name)
Definition attr.c:448
#define ED_to_orig(e)
Definition types.h:598
#define ED_dist(e)
Definition types.h:602
#define ED_minlen(e)
Definition types.h:592
#define agtail(e)
Definition cgraph.h:988
#define ED_weight(e)
Definition types.h:603
#define aghead(e)
Definition cgraph.h:989
#define ED_head_port(e)
Definition types.h:588
#define ED_label(e)
Definition types.h:589
#define ED_tail_port(e)
Definition types.h:597
void agerrorf(const char *fmt,...)
Definition agerror.c:165
#define GD_minrank(g)
Definition types.h:384
#define GD_maxrank(g)
Definition types.h:382
#define GD_drawing(g)
Definition types.h:353
#define GD_border(g)
Definition types.h:359
#define GD_has_labels(g)
Definition types.h:368
#define GD_clust(g)
Definition types.h:360
#define GD_rn(g)
Definition types.h:398
#define GD_rank(g)
Definition types.h:395
#define GD_bb(g)
Definition types.h:354
#define GD_nlist(g)
Definition types.h:393
#define GD_n_cluster(g)
Definition types.h:389
#define GD_ht2(g)
Definition types.h:372
#define GD_label(g)
Definition types.h:374
#define GD_nodesep(g)
Definition types.h:394
#define GD_ln(g)
Definition types.h:381
#define GD_ht1(g)
Definition types.h:371
#define GD_flip(g)
Definition types.h:378
#define GD_exact_ranksep(g)
Definition types.h:363
#define GD_ranksep(g)
Definition types.h:397
#define ND_rank(n)
Definition types.h:523
#define ND_prev(n)
Definition types.h:521
#define ND_ht(n)
Definition types.h:500
#define ND_next(n)
Definition types.h:510
#define ND_clust(n)
Definition types.h:489
#define ND_other(n)
Definition types.h:514
#define ND_save_in(n)
Definition types.h:526
#define ND_alg(n)
Definition types.h:484
#define ND_flat_out(n)
Definition types.h:493
#define ND_rw(n)
Definition types.h:525
#define ND_node_type(n)
Definition types.h:511
#define ND_lw(n)
Definition types.h:506
#define ND_save_out(n)
Definition types.h:527
#define ND_mval(n)
Definition types.h:508
#define ND_order(n)
Definition types.h:513
#define ND_UF_size(n)
Definition types.h:487
#define ND_ranktype(n)
Definition types.h:524
#define ND_coord(n)
Definition types.h:490
#define ND_in(n)
Definition types.h:501
#define ND_out(n)
Definition types.h:515
char * agnameof(void *)
returns a string descriptor for the object.
Definition id.c:143
#define AGTYPE(obj)
returns AGRAPH, AGNODE, or AGEDGE depending on the type of the object
Definition cgraph.h:216
int agcontains(Agraph_t *, void *obj)
returns non-zero if obj is a member of (sub)graph
Definition obj.c:233
Agraph_t * agroot(void *obj)
Definition obj.c:168
@ AGOUTEDGE
Definition cgraph.h:207
@ AGINEDGE
Definition cgraph.h:207
Arithmetic helper functions.
static int scale_clamp(int original, double scale)
scale up or down a non-negative integer, clamping to [0, INT_MAX]
Definition gv_math.h:73
#define SWAP(a, b)
Definition gv_math.h:131
void mark_lowclusters(Agraph_t *root)
Definition cluster.c:398
#define delta
Definition maze.c:136
int rank(graph_t *g, int balance, int maxiter)
Definition ns.c:1044
static void set_ycoords(graph_t *g)
Definition position.c:723
static bool vnode_not_related_to(graph_t *g, node_t *v)
Definition position.c:363
static void separate_subclust(graph_t *g)
Definition position.c:447
static void scale_bb(graph_t *g, double xf, double yf)
Definition position.c:888
static void contain_subclust(graph_t *g)
Definition position.c:424
static void set_aspect(graph_t *g)
Definition position.c:903
static void rec_bb(graph_t *g, graph_t *root)
Definition position.c:877
static void expand_leaves(graph_t *g)
Definition position.c:1014
void dot_position(graph_t *g)
Definition position.c:125
static void connectGraph(graph_t *g)
Definition position.c:77
static void compress_graph(graph_t *g)
Definition position.c:494
static bool idealsize(graph_t *g, double)
Definition position.c:1103
static void contain_clustnodes(graph_t *g)
Definition position.c:347
static void dot_compute_bb(graph_t *g, graph_t *root)
Definition position.c:830
static void make_lrvn(graph_t *g)
Definition position.c:1051
static void make_leafslots(graph_t *g)
Definition position.c:974
static bool canreach(node_t *u, node_t *v)
Definition position.c:172
static void set_xcoords(graph_t *g)
Set x coords of nodes.
Definition position.c:564
static bool go(node_t *u, node_t *v)
Definition position.c:159
static void adjustSimple(graph_t *g, double delta, int margin_total)
Definition position.c:589
static int nsiter2(graph_t *g)
Definition position.c:149
static double largeMinlen(double l)
Definition position.c:62
static int clust_ht(Agraph_t *g)
Definition position.c:675
static void remove_aux_edges(graph_t *g)
Definition position.c:527
static void contain_nodes(graph_t *g)
Definition position.c:1074
static void make_LR_constraints(graph_t *g)
Definition position.c:212
edge_t * make_aux_edge(node_t *u, node_t *v, double len, int wt)
Definition position.c:176
static void make_edge_pairs(graph_t *g)
make virtual edge pairs corresponding to input edges
Definition position.c:320
static void adjustRanks(graph_t *g, int margin_total)
Definition position.c:623
static void pos_clusters(graph_t *g)
Definition position.c:484
static void create_aux_edges(graph_t *g)
Definition position.c:518
static void allocate_aux_edges(graph_t *g)
Definition position.c:194
int ports_eq(edge_t *e, edge_t *f)
Definition position.c:1003
static void keepout_othernodes(graph_t *g)
Definition position.c:385
double selfRightSpace(edge_t *e)
Definition splines.c:1144
Agobj_t base
Definition cgraph.h:269
Agedge_t in
Definition cgraph.h:276
Agedge_t out
Definition cgraph.h:276
Agobj_t base
Definition cgraph.h:260
Agrec_t * data
stores programmer-defined data, access with AGDATA
Definition cgraph.h:212
graph or subgraph
Definition cgraph.h:424
Agraph_t * root
subgraphs - ancestors
Definition cgraph.h:433
Definition types.h:251
edge_t ** list
Definition types.h:252
size_t size
Definition types.h:253
double x
Definition geom.h:29
double y
Definition geom.h:29
node_t ** v
Definition types.h:202
int n
Definition types.h:201
#define free_list(L)
Definition types.h:272
#define alloc_elist(n, L)
Definition types.h:267
@ R_AUTO
Definition types.h:216
@ R_COMPRESS
Definition types.h:216
@ R_VALUE
Definition types.h:216
@ R_FILL
Definition types.h:216
@ R_EXPAND
Definition types.h:216
Definition grammar.c:90
#define MAX(a, b)
Definition write.c:32