varnish-cache/lib/libvarnishapi/vxp_parse.c
1
/*-
2
 * Copyright (c) 2006 Verdens Gang AS
3
 * Copyright (c) 2006-2015 Varnish Software AS
4
 * All rights reserved.
5
 *
6
 * Author: Martin Blix Grydeland <martin@varnish-software.com>
7
 *
8
 * Redistribution and use in source and binary forms, with or without
9
 * modification, are permitted provided that the following conditions
10
 * are met:
11
 * 1. Redistributions of source code must retain the above copyright
12
 *    notice, this list of conditions and the following disclaimer.
13
 * 2. Redistributions in binary form must reproduce the above copyright
14
 *    notice, this list of conditions and the following disclaimer in the
15
 *    documentation and/or other materials provided with the distribution.
16
 *
17
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
21
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27
 * SUCH DAMAGE.
28
 *
29
 */
30
31
#include "config.h"
32
33
#include <ctype.h>
34
#include <math.h>
35
#include <stdio.h>
36
37
#include "vdef.h"
38
#include "vas.h"
39
#include "miniobj.h"
40
41
#include "vbm.h"
42
#include "vnum.h"
43
#include "vqueue.h"
44
#include "vre.h"
45
#include "vsb.h"
46
47
#include "vapi/vsl.h"
48
49
#include "vsl_api.h"
50
#include "vxp.h"
51
52
static void vxp_expr_or(struct vxp *vxp, struct vex **pvex);
53
54
static struct vex *
55 78
vex_alloc(const struct vxp *vxp)
56
{
57
        struct vex *vex;
58
59 78
        ALLOC_OBJ(vex, VEX_MAGIC);
60 78
        AN(vex);
61 78
        vex->options = vxp->vex_options;
62 78
        return (vex);
63
}
64
65
static void
66 70
vxp_expr_lhs(struct vxp *vxp, struct vex_lhs **plhs)
67
{
68
        char *p;
69
        int i;
70
71 70
        AN(plhs);
72 70
        AZ(*plhs);
73 70
        ALLOC_OBJ(*plhs, VEX_LHS_MAGIC);
74 70
        AN(*plhs);
75 70
        (*plhs)->tags = vbit_new(SLT__MAX);
76 70
        (*plhs)->level = -1;
77
78 70
        if (vxp->t->tok == '{') {
79
                /* Transaction level limits */
80 4
                vxp_NextToken(vxp);
81 4
                if (vxp->t->tok != VAL) {
82 0
                        VSB_printf(vxp->sb, "Expected integer got '%.*s' ",
83 0
                            PF(vxp->t));
84 0
                        vxp_ErrWhere(vxp, vxp->t, -1);
85 0
                        return;
86
                }
87 4
                (*plhs)->level = (int)strtol(vxp->t->dec, &p, 0);
88 4
                if ((*plhs)->level < 0) {
89 0
                        VSB_printf(vxp->sb, "Expected positive integer ");
90 0
                        vxp_ErrWhere(vxp, vxp->t, -1);
91 0
                        return;
92
                }
93 4
                if (*p == '-') {
94 1
                        (*plhs)->level_pm = -1;
95 1
                        p++;
96 3
                } else if (*p == '+') {
97 1
                        (*plhs)->level_pm = 1;
98 1
                        p++;
99
                }
100 4
                if (*p) {
101 0
                        VSB_printf(vxp->sb, "Syntax error in level limit ");
102 0
                        vxp_ErrWhere(vxp, vxp->t, -1);
103 0
                        return;
104
                }
105 4
                vxp_NextToken(vxp);
106 4
                ExpectErr(vxp, '}');
107 4
                vxp_NextToken(vxp);
108
        }
109
110
        while (1) {
111
                /* The tags this expression applies to */
112 73
                if (vxp->t->tok == VXID) {
113 12
                        (*plhs)->vxid++;
114 12
                        i = 0;
115 61
                } else if (vxp->t->tok != VAL) {
116 0
                        VSB_printf(vxp->sb, "Expected VSL tag name got '%.*s' ",
117 0
                            PF(vxp->t));
118 0
                        vxp_ErrWhere(vxp, vxp->t, -1);
119 0
                        return;
120
                } else {
121 61
                        (*plhs)->taglist++;
122 61
                        i = VSL_Glob2Tags(vxp->t->dec, -1, vsl_vbm_bitset,
123 61
                            (*plhs)->tags);
124
                }
125 73
                if (i == -1) {
126 0
                        VSB_printf(vxp->sb, "Tag name matches zero tags ");
127 0
                        vxp_ErrWhere(vxp, vxp->t, -1);
128 0
                        return;
129
                }
130 73
                if (i == -2) {
131 0
                        VSB_printf(vxp->sb, "Tag name is ambiguous ");
132 0
                        vxp_ErrWhere(vxp, vxp->t, -1);
133 0
                        return;
134
                }
135 73
                if (i == -3) {
136 0
                        VSB_printf(vxp->sb, "Syntax error in tag name ");
137 0
                        vxp_ErrWhere(vxp, vxp->t, -1);
138 0
                        return;
139
                }
140 73
                assert(i > 0 || vxp->t->tok == VXID);
141 73
                vxp_NextToken(vxp);
142 73
                if (vxp->t->tok != ',')
143 70
                        break;
144 3
                vxp_NextToken(vxp);
145 3
        }
146
147 70
        if (vxp->t->tok == ':') {
148
                /* Record prefix */
149 1
                vxp_NextToken(vxp);
150 1
                if (vxp->t->tok != VAL) {
151 0
                        VSB_printf(vxp->sb, "Expected string got '%.*s' ",
152 0
                            PF(vxp->t));
153 0
                        vxp_ErrWhere(vxp, vxp->t, -1);
154 0
                        return;
155
                }
156 1
                AN(vxp->t->dec);
157 1
                (*plhs)->prefix = strdup(vxp->t->dec);
158 1
                AN((*plhs)->prefix);
159 1
                (*plhs)->prefixlen = strlen((*plhs)->prefix);
160 1
                vxp_NextToken(vxp);
161
        }
162
163 70
        if (vxp->t->tok == '[') {
164
                /* LHS field [] */
165 3
                vxp_NextToken(vxp);
166 3
                if (vxp->t->tok != VAL) {
167 0
                        VSB_printf(vxp->sb, "Expected integer got '%.*s' ",
168 0
                            PF(vxp->t));
169 0
                        vxp_ErrWhere(vxp, vxp->t, -1);
170 0
                        return;
171
                }
172 3
                (*plhs)->field = (int)strtol(vxp->t->dec, &p, 0);
173 3
                if (*p || (*plhs)->field <= 0) {
174 0
                        VSB_printf(vxp->sb, "Expected positive integer ");
175 0
                        vxp_ErrWhere(vxp, vxp->t, -1);
176 0
                        return;
177
                }
178 3
                vxp_NextToken(vxp);
179 3
                ExpectErr(vxp, ']');
180 3
                vxp_NextToken(vxp);
181
        }
182
183 70
        if ((*plhs)->vxid == 0)
184 59
                return;
185
186 20
        if ((*plhs)->vxid > 1 || (*plhs)->level >= 0 ||
187 25
            (*plhs)->field > 0 || (*plhs)->prefixlen > 0 ||
188 8
            (*plhs)->taglist > 0) {
189 4
                VSB_printf(vxp->sb, "Unexpected taglist selection for vxid ");
190 4
                vxp_ErrWhere(vxp, vxp->t, -1);
191
        }
192
}
193
194
static void
195 28
vxp_expr_num(struct vxp *vxp, struct vex_rhs **prhs, unsigned vxid)
196
{
197
        char *endptr;
198
199 28
        AN(prhs);
200 28
        AZ(*prhs);
201 28
        if (vxp->t->tok != VAL) {
202 0
                VSB_printf(vxp->sb, "Expected number got '%.*s' ", PF(vxp->t));
203 0
                vxp_ErrWhere(vxp, vxp->t, -1);
204 0
                return;
205
        }
206 28
        AN(vxp->t->dec);
207 28
        ALLOC_OBJ(*prhs, VEX_RHS_MAGIC);
208 28
        AN(*prhs);
209 28
        if (strchr(vxp->t->dec, '.')) {
210 8
                (*prhs)->type = VEX_FLOAT;
211 8
                (*prhs)->val_float = VNUM(vxp->t->dec);
212 8
                if (isnan((*prhs)->val_float)) {
213 0
                        VSB_printf(vxp->sb, "Floating point parse error ");
214 0
                        vxp_ErrWhere(vxp, vxp->t, -1);
215 0
                        return;
216
                }
217
        } else {
218 20
                (*prhs)->type = VEX_INT;
219 20
                (*prhs)->val_int = strtoll(vxp->t->dec, &endptr, 0);
220 40
                while (isspace(*endptr))
221 0
                        endptr++;
222 20
                if (*endptr != '\0') {
223 0
                        VSB_printf(vxp->sb, "Integer parse error ");
224 0
                        vxp_ErrWhere(vxp, vxp->t, -1);
225 0
                        return;
226
                }
227
        }
228 28
        if (vxid && (*prhs)->type != VEX_INT) {
229 2
                VSB_printf(vxp->sb, "Expected integer got '%.*s' ",
230 2
                    PF(vxp->t));
231 1
                vxp_ErrWhere(vxp, vxp->t, 0);
232 1
                return;
233
        }
234 27
        vxp_NextToken(vxp);
235
}
236
237
static void
238 3
vxp_expr_str(struct vxp *vxp, struct vex_rhs **prhs)
239
{
240
241 3
        AN(prhs);
242 3
        AZ(*prhs);
243 3
        if (vxp->t->tok != VAL) {
244 0
                VSB_printf(vxp->sb, "Expected string got '%.*s' ", PF(vxp->t));
245 0
                vxp_ErrWhere(vxp, vxp->t, -1);
246 3
                return;
247
        }
248 3
        AN(vxp->t->dec);
249 3
        ALLOC_OBJ(*prhs, VEX_RHS_MAGIC);
250 3
        AN(*prhs);
251 3
        (*prhs)->type = VEX_STRING;
252 3
        (*prhs)->val_string = strdup(vxp->t->dec);
253 3
        AN((*prhs)->val_string);
254 3
        (*prhs)->val_stringlen = strlen((*prhs)->val_string);
255 3
        vxp_NextToken(vxp);
256
}
257
258
static void
259 13
vxp_expr_regex(struct vxp *vxp, struct vex_rhs **prhs)
260
{
261
        const char *errptr;
262
        int erroff;
263
264
        /* XXX: Caseless option */
265
266 13
        AN(prhs);
267 13
        AZ(*prhs);
268 13
        if (vxp->t->tok != VAL) {
269 0
                VSB_printf(vxp->sb, "Expected regular expression got '%.*s' ",
270 0
                    PF(vxp->t));
271 0
                vxp_ErrWhere(vxp, vxp->t, -1);
272 0
                return;
273
        }
274 13
        AN(vxp->t->dec);
275 13
        ALLOC_OBJ(*prhs, VEX_RHS_MAGIC);
276 13
        AN(*prhs);
277 13
        (*prhs)->type = VEX_REGEX;
278 13
        (*prhs)->val_string = strdup(vxp->t->dec);
279 13
        (*prhs)->val_regex = VRE_compile(vxp->t->dec, vxp->vre_options,
280
            &errptr, &erroff);
281 13
        if ((*prhs)->val_regex == NULL) {
282 0
                AN(errptr);
283 0
                VSB_printf(vxp->sb, "Regular expression error: %s ", errptr);
284 0
                vxp_ErrWhere(vxp, vxp->t, erroff);
285 0
                return;
286
        }
287 13
        vxp_NextToken(vxp);
288
}
289
290
static void
291 7
vxp_vxid_cmp(struct vxp *vxp)
292
{
293
294 7
        switch (vxp->t->tok) {
295
        /* Valid operators */
296
        case T_EQ:              /* == */
297
        case '<':               /* < */
298
        case '>':               /* > */
299
        case T_GEQ:             /* >= */
300
        case T_LEQ:             /* <= */
301
        case T_NEQ:             /* != */
302 3
                break;
303
304
        /* Error */
305
        default:
306 8
                VSB_printf(vxp->sb, "Expected vxid operator got '%.*s' ",
307 8
                    PF(vxp->t));
308 4
                vxp_ErrWhere(vxp, vxp->t, -1);
309
        }
310 7
}
311
312
/*
313
 * SYNTAX:
314
 *   expr_cmp:
315
 *     lhs
316
 *     lhs <operator> num|str|regex
317
 */
318
319
static void
320 70
vxp_expr_cmp(struct vxp *vxp, struct vex **pvex)
321
{
322
323 70
        AN(pvex);
324 70
        AZ(*pvex);
325 70
        *pvex = vex_alloc(vxp);
326 70
        AN(*pvex);
327 70
        vxp_expr_lhs(vxp, &(*pvex)->lhs);
328 70
        ERRCHK(vxp);
329
330 66
        if ((*pvex)->lhs->vxid) {
331 7
                vxp_vxid_cmp(vxp);
332 7
                ERRCHK(vxp);
333
        }
334
335
        /* Test operator */
336 62
        switch (vxp->t->tok) {
337
338
        /* Single lhs expressions don't take any more tokens */
339
        case EOI:
340
        case T_AND:
341
        case T_OR:
342
        case ')':
343 18
                (*pvex)->tok = T_TRUE;
344 18
                return;
345
346
        /* Valid operators */
347
        case T_EQ:              /* == */
348
        case '<':               /* < */
349
        case '>':               /* > */
350
        case T_GEQ:             /* >= */
351
        case T_LEQ:             /* <= */
352
        case T_NEQ:             /* != */
353
        case T_SEQ:             /* eq */
354
        case T_SNEQ:            /* ne */
355
        case '~':               /* ~ */
356
        case T_NOMATCH:         /* !~ */
357 44
                (*pvex)->tok = vxp->t->tok;
358 44
                break;
359
360
        /* Error */
361
        default:
362 0
                VSB_printf(vxp->sb, "Expected operator got '%.*s' ",
363 0
                    PF(vxp->t));
364 0
                vxp_ErrWhere(vxp, vxp->t, -1);
365 0
                return;
366
        }
367 44
        vxp_NextToken(vxp);
368 44
        ERRCHK(vxp);
369
370
        /* Value */
371 44
        switch ((*pvex)->tok) {
372
        case '\0':
373 0
                WRONG("Missing token");
374
                break;
375
        case T_EQ:              /* == */
376
        case '<':               /* < */
377
        case '>':               /* > */
378
        case T_GEQ:             /* >= */
379
        case T_LEQ:             /* <= */
380
        case T_NEQ:             /* != */
381 28
                vxp_expr_num(vxp, &(*pvex)->rhs, (*pvex)->lhs->vxid);
382 28
                break;
383
        case T_SEQ:             /* eq */
384
        case T_SNEQ:            /* ne */
385 3
                vxp_expr_str(vxp, &(*pvex)->rhs);
386 3
                break;
387
        case '~':               /* ~ */
388
        case T_NOMATCH:         /* !~ */
389 13
                vxp_expr_regex(vxp, &(*pvex)->rhs);
390 13
                break;
391
        default:
392 0
                INCOMPL();
393
        }
394
}
395
396
/*
397
 * SYNTAX:
398
 *   expr_group:
399
 *     '(' expr_or ')'
400
 *     expr_not
401
 */
402
403
static void
404 71
vxp_expr_group(struct vxp *vxp, struct vex **pvex)
405
{
406
407 71
        AN(pvex);
408 71
        AZ(*pvex);
409
410 71
        if (vxp->t->tok == '(') {
411 1
                SkipToken(vxp, '(');
412 1
                vxp_expr_or(vxp, pvex);
413 1
                ERRCHK(vxp);
414 1
                SkipToken(vxp, ')');
415 1
                return;
416
        }
417
418 70
        vxp_expr_cmp(vxp, pvex);
419
}
420
421
/*
422
 * SYNTAX:
423
 *   expr_not:
424
 *     'not' expr_group
425
 *     expr_group
426
 */
427
428
static void
429 71
vxp_expr_not(struct vxp *vxp, struct vex **pvex)
430
{
431
432 71
        AN(pvex);
433 71
        AZ(*pvex);
434
435 71
        if (vxp->t->tok == T_NOT) {
436 1
                *pvex = vex_alloc(vxp);
437 1
                AN(*pvex);
438 1
                (*pvex)->tok = vxp->t->tok;
439 1
                vxp_NextToken(vxp);
440 1
                vxp_expr_group(vxp, &(*pvex)->a);
441 72
                return;
442
        }
443
444 70
        vxp_expr_group(vxp, pvex);
445
}
446
447
/*
448
 * SYNTAX:
449
 *   expr_and:
450
 *     expr_not { 'and' expr_not }*
451
 */
452
453
static void
454 68
vxp_expr_and(struct vxp *vxp, struct vex **pvex)
455
{
456
        struct vex *a;
457
458 68
        AN(pvex);
459 68
        AZ(*pvex);
460 68
        vxp_expr_not(vxp, pvex);
461 68
        ERRCHK(vxp);
462 121
        while (vxp->t->tok == T_AND) {
463 3
                a = *pvex;
464 3
                *pvex = vex_alloc(vxp);
465 3
                AN(*pvex);
466 3
                (*pvex)->tok = vxp->t->tok;
467 3
                (*pvex)->a = a;
468 3
                vxp_NextToken(vxp);
469 3
                ERRCHK(vxp);
470 3
                vxp_expr_not(vxp, &(*pvex)->b);
471 3
                ERRCHK(vxp);
472
        }
473
}
474
475
/*
476
 * SYNTAX:
477
 *   expr_or:
478
 *     expr_and { 'or' expr_and }*
479
 */
480
481
static void
482 64
vxp_expr_or(struct vxp *vxp, struct vex **pvex)
483
{
484
        struct vex *a;
485
486 64
        AN(pvex);
487 64
        AZ(*pvex);
488 64
        vxp_expr_and(vxp, pvex);
489 64
        ERRCHK(vxp);
490 114
        while (vxp->t->tok == T_OR) {
491 4
                a = *pvex;
492 4
                *pvex = vex_alloc(vxp);
493 4
                AN(*pvex);
494 4
                (*pvex)->tok = vxp->t->tok;
495 4
                (*pvex)->a = a;
496 4
                vxp_NextToken(vxp);
497 4
                ERRCHK(vxp);
498 4
                vxp_expr_and(vxp, &(*pvex)->b);
499 4
                ERRCHK(vxp);
500
        }
501
}
502
503
/*
504
 * SYNTAX:
505
 *   expr:
506
 *     expr_or EOI
507
 */
508
509
static void
510 63
vxp_expr(struct vxp *vxp, struct vex **pvex)
511
{
512 63
        vxp_expr_or(vxp, pvex);
513 63
        ERRCHK(vxp);
514 54
        ExpectErr(vxp, EOI);
515
}
516
517
/*
518
 * Build a struct vex tree from the token list in vxp
519
 */
520
521
struct vex *
522 63
vxp_Parse(struct vxp *vxp)
523
{
524 63
        struct vex *vex = NULL;
525
526 63
        vxp->t = VTAILQ_FIRST(&vxp->tokens);
527 63
        if (vxp->t == NULL)
528 0
                return (NULL);
529
530 63
        vxp_expr(vxp, &vex);
531
532 63
        if (vxp->err) {
533 9
                if (vex)
534 9
                        vex_Free(&vex);
535 9
                AZ(vex);
536 9
                return (NULL);
537
        }
538
539 54
        return (vex);
540
}
541
542
/*
543
 * Free a struct vex tree
544
 */
545
546
void
547 78
vex_Free(struct vex **pvex)
548
{
549
550 78
        if ((*pvex)->lhs != NULL) {
551 70
                if ((*pvex)->lhs->tags != NULL)
552 70
                        vbit_destroy((*pvex)->lhs->tags);
553 70
                if ((*pvex)->lhs->prefix != NULL)
554 1
                        free((*pvex)->lhs->prefix);
555 70
                FREE_OBJ((*pvex)->lhs);
556
        }
557 78
        if ((*pvex)->rhs != NULL) {
558 44
                if ((*pvex)->rhs->val_string)
559 16
                        free((*pvex)->rhs->val_string);
560 44
                if ((*pvex)->rhs->val_regex)
561 13
                        VRE_free(&(*pvex)->rhs->val_regex);
562 44
                FREE_OBJ((*pvex)->rhs);
563
        }
564 78
        if ((*pvex)->a != NULL) {
565 8
                vex_Free(&(*pvex)->a);
566 8
                AZ((*pvex)->a);
567
        }
568 78
        if ((*pvex)->b != NULL) {
569 7
                vex_Free(&(*pvex)->b);
570 7
                AZ((*pvex)->b);
571
        }
572 78
        FREE_OBJ(*pvex);
573 78
        *pvex = NULL;
574 78
}
575
576
#ifdef VXP_DEBUG
577
578
static void
579
vex_print_rhs(const struct vex_rhs *rhs)
580
{
581
582
        CHECK_OBJ_NOTNULL(rhs, VEX_RHS_MAGIC);
583
        fprintf(stderr, "rhs=");
584
        switch (rhs->type) {
585
        case VEX_INT:
586
                fprintf(stderr, "INT(%jd)", (intmax_t)rhs->val_int);
587
                break;
588
        case VEX_FLOAT:
589
                fprintf(stderr, "FLOAT(%f)", rhs->val_float);
590
                break;
591
        case VEX_STRING:
592
                AN(rhs->val_string);
593
                fprintf(stderr, "STRING(%s)", rhs->val_string);
594
                break;
595
        case VEX_REGEX:
596
                AN(rhs->val_string);
597
                AN(rhs->val_regex);
598
                fprintf(stderr, "REGEX(%s)", rhs->val_string);
599
                break;
600
        default:
601
                WRONG("rhs type");
602
                break;
603
        }
604
}
605
606
static void
607
vex_print_tags(const struct vbitmap *vbm)
608
{
609
        int i;
610
        int first = 1;
611
612
        for (i = 0; i < SLT__MAX; i++) {
613
                if (VSL_tags[i] == NULL)
614
                        continue;
615
                if (!vbit_test(vbm, i))
616
                        continue;
617
                if (first)
618
                        first = 0;
619
                else
620
                        fprintf(stderr, ",");
621
                fprintf(stderr, "%s", VSL_tags[i]);
622
        }
623
}
624
625
static void
626
vex_print(const struct vex *vex, int indent)
627
{
628
        CHECK_OBJ_NOTNULL(vex, VEX_MAGIC);
629
630
        fprintf(stderr, "%*s%s", indent, "", vxp_tnames[vex->tok]);
631
        if (vex->lhs != NULL) {
632
                CHECK_OBJ_NOTNULL(vex->lhs, VEX_LHS_MAGIC);
633
                AN(vex->lhs->tags);
634
                fprintf(stderr, " lhs=");
635
                if (vex->lhs->level >= 0)
636
                        fprintf(stderr, "{%d%s}", vex->lhs->level,
637
                            vex->lhs->level_pm < 0 ? "-" :
638
                            vex->lhs->level_pm > 0 ? "+" : "");
639
                fprintf(stderr, "(");
640
                vex_print_tags(vex->lhs->tags);
641
                fprintf(stderr, ")");
642
                if (vex->lhs->prefix) {
643
                        assert(vex->lhs->prefixlen == strlen(vex->lhs->prefix));
644
                        fprintf(stderr, ":%s", vex->lhs->prefix);
645
                }
646
                if (vex->lhs->field > 0)
647
                        fprintf(stderr, "[%d]", vex->lhs->field);
648
        }
649
        if (vex->rhs != NULL) {
650
                fprintf(stderr, " ");
651
                vex_print_rhs(vex->rhs);
652
        }
653
        fprintf(stderr, "\n");
654
        if (vex->a != NULL)
655
                vex_print(vex->a, indent + 2);
656
        if (vex->b != NULL)
657
                vex_print(vex->b, indent + 2);
658
}
659
660
void
661
vex_PrintTree(const struct vex *vex)
662
{
663
664
        CHECK_OBJ_NOTNULL(vex, VEX_MAGIC);
665
        fprintf(stderr, "VEX tree:\n");
666
        vex_print(vex, 2);
667
}
668
669
#endif /* VXP_DEBUG */