• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ascii-boxes / boxes / 10972725362

21 Sep 2024 12:51PM UTC coverage: 83.099% (-1.9%) from 84.973%
10972725362

push

github

tsjensen
defs

2958 of 3795 branches covered (77.94%)

Branch coverage included in aggregate %.

4796 of 5536 relevant lines covered (86.63%)

176493.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.16
/src/parsecode.c
1
/*
2
 * boxes - Command line filter to draw/remove ASCII boxes around text
3
 * Copyright (c) 1999-2024 Thomas Jensen and the boxes contributors
4
 *
5
 * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
6
 * License, version 3, as published by the Free Software Foundation.
7
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
8
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
9
 * details.
10
 * You should have received a copy of the GNU General Public License along with this program.
11
 * If not, see <https://www.gnu.org/licenses/>.
12
 *
13
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
14
 */
15

16
/*
17
 * C code used exclusively by the parser.
18
 */
19

20
#include "config.h"
21

22
#include <stdio.h>
23
#include <stdlib.h>
24
#include <string.h>
25
#include <strings.h>
26
#include <unictype.h>
27
#include <unistr.h>
28

29
#include "discovery.h"
30
#include "logging.h"
31
#include "parsecode.h"
32
#include "parsing.h"
33
#include "query.h"
34
#include "regulex.h"
35
#include "shape.h"
36
#include "tools.h"
37
#include "unicode.h"
38

39
#include "parser.h"
40

41
#include "lex.yy.h"
42

43

44
static pcre2_code *eol_pattern = NULL;
45

46

47

48
static int check_sizes(pass_to_bison *bison_args)
1,718✔
49
/*
50
 *  For the author's convenience, it is required that shapes on one side
51
 *  have equal width (vertical sides) and height (horizontal sides).
52
 *
53
 *  RETURNS:  == 0   no problem detected
54
 *            != 0   on error (prints error message, too)
55
 *
56
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
57
 */
58
{
59
    int i, j, k;
60

61
    log_debug(__FILE__, PARSER, " Parser: check_sizes()\n");
1,718✔
62

63
    for (i = 0; i < NUM_SIDES; ++i) {
8,590✔
64
        if (i == 0 || i == 2) {
6,872✔
65
            /*
66
             *  horizontal
67
             */
68
            for (j = 0; j < SHAPES_PER_SIDE - 1; ++j) {
17,180✔
69
                if (curdes.shape[sides[i][j]].height == 0) {
13,744✔
70
                    continue;
5,200✔
71
                }
72
                for (k = j + 1; k < SHAPES_PER_SIDE; ++k) {
32,572✔
73
                    if (curdes.shape[sides[i][k]].height == 0) {
24,028✔
74
                        continue;
8,100✔
75
                    }
76
                    if (curdes.shape[sides[i][j]].height != curdes.shape[sides[i][k]].height) {
15,928✔
77
                        yyerror(bison_args, "All shapes on horizontal sides must be of "
×
78
                                "equal height (%s: %d, %s: %d)\n",
79
                                shape_name[sides[i][j]], curdes.shape[sides[i][j]].height,
×
80
                                shape_name[sides[i][k]], curdes.shape[sides[i][k]].height);
×
81
                        return 1;
×
82
                    }
83
                }
7,964✔
84
            }
4,272✔
85
        }
1,718✔
86
        else {
87
            /*
88
             *  vertical
89
             */
90
            for (j = 0; j < SHAPES_PER_SIDE - 1; ++j) {
17,180✔
91
                if (curdes.shape[sides[i][j]].width == 0) {
13,744✔
92
                    continue;
6,220✔
93
                }
94
                for (k = j + 1; k < SHAPES_PER_SIDE; ++k) {
29,444✔
95
                    if (curdes.shape[sides[i][k]].width == 0) {
21,920✔
96
                        continue;
9,492✔
97
                    }
98
                    if (curdes.shape[sides[i][j]].width != curdes.shape[sides[i][k]].width) {
12,428✔
99
                        yyerror(bison_args, "All shapes on vertical sides must be of "
×
100
                                "equal width (%s: %d, %s: %d)\n",
101
                                shape_name[sides[i][j]], curdes.shape[sides[i][j]].width,
×
102
                                shape_name[sides[i][k]], curdes.shape[sides[i][k]].width);
×
103
                        return 1;
×
104
                    }
105
                }
6,214✔
106
            }
3,762✔
107
        }
108
    }
3,436✔
109

110
    return 0; /* all clear */
1,718✔
111
}
859✔
112

113

114

115
static int corner_check(pass_to_bison *bison_args)
1,716✔
116
/*
117
 *  Check that no corners are elastic.
118
 *
119
 *  RETURNS:  == 0   no problem detected
120
 *            != 0   on error
121
 *
122
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
123
 */
124
{
125
    int c;
126

127
    log_debug(__FILE__, PARSER, " Parser: corner_check()\n");
1,716✔
128

129
    for (c = 0; c < NUM_CORNERS; ++c) {
8,580✔
130
        if (curdes.shape[corners[c]].elastic) {
6,864!
131
            yyerror(bison_args, "Corners may not be elastic (%s)", shape_name[corners[c]]);
×
132
            return 1;
×
133
        }
134
    }
3,432✔
135

136
    return 0; /* all clear */
1,716✔
137
}
858✔
138

139

140

141
static shape_t non_existent_elastics(pass_to_bison *bison_args)
1,716✔
142
{
143
    shape_t i;
144

145
    log_debug(__FILE__, PARSER, " Parser: non_existent_elastics()\n");
1,716✔
146

147
    for (i = 0; i < NUM_SHAPES; ++i) {
29,172✔
148
        if (curdes.shape[i].elastic && isempty(curdes.shape + i)) {
27,456✔
149
            return i;
×
150
        }
151
    }
13,728✔
152

153
    return (shape_t) NUM_SHAPES; /* all elastic shapes exist */
1,716✔
154
}
858✔
155

156

157

158
static int insufficient_elasticity(pass_to_bison *bison_args)
1,716✔
159
{
160
    int i, j, ef;
161

162
    log_debug(__FILE__, PARSER, " Parser: insufficient_elasticity()\n");
1,716✔
163

164
    for (i = 0; i < NUM_SIDES; ++i) {
8,580✔
165
        for (j = 1, ef = 0; j < 4; ++j) {
27,456✔
166
            if (curdes.shape[sides[i][j]].elastic) {
20,592✔
167
                ++ef;
7,232✔
168
            }
3,616✔
169
        }
10,296✔
170
        if (ef != 1 && ef != 2) {
6,864!
171
            return 1; /* error */
×
172
        }
173
    }
3,432✔
174

175
    return 0; /* all clear */
1,716✔
176
}
858✔
177

178

179

180
static int adjoining_elastics(pass_to_bison *bison_args)
1,716✔
181
{
182
    int i, j, ef;
183

184
    log_debug(__FILE__, PARSER, " Parser: adjoining_elastics()\n");
1,716✔
185

186
    for (i = 0; i < NUM_SIDES; ++i) {
8,580✔
187
        ef = 0;
6,864✔
188
        for (j = 1; j < 4; ++j) {
27,456✔
189
            if (isempty(curdes.shape + sides[i][j])) {
20,592✔
190
                continue;
11,404✔
191
            }
192
            if (curdes.shape[sides[i][j]].elastic) {
9,188✔
193
                if (ef) {
7,232!
194
                    return 1; /* error detected */
×
195
                }
196
                else {
197
                    ef = 1;
7,232✔
198
                }
199
            }
3,616✔
200
            else {
201
                ef = 0;
1,956✔
202
            }
203
        }
4,594✔
204
    }
3,432✔
205

206
    return 0; /* all clear */
1,716✔
207
}
858✔
208

209

210

211
int perform_se_check(pass_to_bison *bison_args)
1,716✔
212
{
213
    shape_t s_rc;
214

215
    s_rc = non_existent_elastics(bison_args);
1,716✔
216
    if (s_rc != NUM_SHAPES) {
1,716!
217
        yyerror(bison_args, "Shape %s has been specified as elastic but doesn't exist", shape_name[s_rc]);
×
218
        return 1;
×
219
    }
220

221
    if (corner_check(bison_args)) {
1,716!
222
        /* Error message printed in check func */
223
        return 1;
×
224
    }
225

226
    if (insufficient_elasticity(bison_args)) {
1,716!
227
        yyerror(bison_args, "There must be exactly one or two elastic shapes per side");
×
228
        return 1;
×
229
    }
230

231
    if (adjoining_elastics(bison_args)) {
1,716✔
232
        yyerror(bison_args, "Two adjoining shapes may not be elastic");
×
233
        return 1;
×
234
    }
235

236
    return 0;
1,716✔
237
}
858✔
238

239

240

241
static void init_design(pass_to_bison *bison_args, design_t *design)
1,826✔
242
{
243
    memset(design, 0, sizeof(design_t));
1,826✔
244
    design->aliases = (char **) calloc(1, sizeof(char *));
1,826✔
245
    design->indentmode = DEF_INDENTMODE;
1,826✔
246
    design->defined_in = bison_args->config_file;
1,826✔
247
    design->tags = (char **) calloc(1, sizeof(char *));
1,826✔
248
}
1,826✔
249

250

251

252
/**
253
 * Reset parser to neutral state, so a new design can be parsed.
254
 * @param bison_args the parser state
255
 */
256
void recover(pass_to_bison *bison_args)
18✔
257
{
258
    bison_args->num_mandatory = 0;
18✔
259
    bison_args->time_for_se_check = 0;
18✔
260
    bison_args->num_shapespec = 0;
18✔
261

262
    /*
263
     *  Clear current design
264
     */
265
    BFREE(curdes.name);
18✔
266
    BFREE(curdes.author);
18!
267
    BFREE(curdes.aliases);
18!
268
    BFREE(curdes.designer);
18!
269
    BFREE(curdes.sample);
18✔
270
    BFREE(curdes.tags);
18!
271
    init_design(bison_args, &(curdes));
18✔
272
}
18✔
273

274

275

276
static int design_has_alias(design_t *design, char *alias)
48,666✔
277
{
278
    int result = 0;
48,666✔
279
    for (size_t aidx = 0; design->aliases[aidx] != NULL; ++aidx) {
51,782✔
280
        if (strcasecmp(alias, design->aliases[aidx]) == 0) {
3,126✔
281
            result = 1;
10✔
282
            break;
10✔
283
        }
284
    }
1,558✔
285
    return result;
48,666✔
286
}
287

288

289

290
static int design_has_name(design_t *design, char *name)
49,156✔
291
{
292
    int result = 0;
49,156✔
293
    if (strcasecmp(name, design->name) == 0) {
49,156✔
294
        result = 1;
498✔
295
    }
249✔
296
    else {
297
        result = design_has_alias(design, name);
48,658✔
298
    }
299
    return result;
49,156✔
300
}
301

302

303

304
static int full_parse_required()
2,940✔
305
{
306
    int result = 0;
2,940✔
307
    if (!opt.design_choice_by_user) {
2,940✔
308
        result = opt.r || opt.l || (opt.query != NULL && !opt.qundoc);
2,440!
309
    }
1,220✔
310
    log_debug(__FILE__, MAIN, " Parser: full_parse_required() -> %s\n", result ? "true" : "false");
2,940✔
311
    return result;
2,940✔
312
}
313

314

315

316
/**
317
 * Determine if the design currently being parsed is one that we will need.
318
 * @param bison_args the bison state
319
 * @return result flag
320
 */
321
static int design_needed(pass_to_bison *bison_args)
17,990✔
322
{
323
    if (opt.design_choice_by_user) {
17,990✔
324
        return design_has_name(&(curdes), (char *) opt.design);
16,764✔
325
    }
326
    else {
327
        if (full_parse_required() || bison_args->design_idx == 0) {
1,226!
328
            return 1;
1,226✔
329
        }
330
    }
331
    return 0;
×
332
}
8,995✔
333

334

335

336
static int design_name_exists(pass_to_bison *bison_args, char *name)
2,882✔
337
{
338
    int result = 0;
2,882✔
339
    for (int i = 0; i < bison_args->design_idx; ++i) {
35,270✔
340
        if (design_has_name(bison_args->designs + i, name)) {
32,392✔
341
            result = 1;
4✔
342
            break;
4✔
343
        }
344
    }
16,194✔
345
    return result;
2,882✔
346
}
347

348

349

350
static int alias_exists_in_child_configs(pass_to_bison *bison_args, char *alias)
1,164✔
351
{
352
    int result = 0;
1,164✔
353
    for (size_t i = 0; i < bison_args->num_child_configs; ++i) {
1,168✔
354
        if (design_has_alias(bison_args->child_configs + i, alias)) {
8✔
355
            result = 1;
4✔
356
            break;
4✔
357
        }
358
    }
2✔
359
    return result;
1,164✔
360
}
361

362

363

364
static int tag_add(pass_to_bison *bison_args, bxstr_t *tag)
4,394✔
365
{
366
    int rc = RC_SUCCESS;
4,394✔
367
    if (is_ascii_id(tag, 1)) {
4,394✔
368
        if (!array_contains0(curdes.tags, tag->ascii)) {
4,380!
369
            size_t num_tags = array_count0(curdes.tags);
4,380✔
370
            curdes.tags = (char **) realloc(curdes.tags, (num_tags + 2) * sizeof(char *));
4,380✔
371
            curdes.tags[num_tags] = (char *) strdup(tag->ascii);
4,380✔
372
            curdes.tags[num_tags + 1] = NULL;
4,380✔
373
        }
2,190✔
374
        else {
375
            yyerror(bison_args, "duplicate tag -- %s", bxs_to_output(tag));
×
376
            rc = RC_ERROR;
×
377
        }
378
    }
2,190✔
379
    else {
380
        yyerror(bison_args, "invalid tag -- %s", bxs_to_output(tag));
14✔
381
        rc = RC_ERROR;
14✔
382
    }
383
    return rc;
4,394✔
384
}
385

386

387

388
static int tag_split_add(pass_to_bison *bison_args, bxstr_t *tag)
4✔
389
{
390
    int rc = RC_SUCCESS;
4✔
391
    uint32_t *c = NULL;
4✔
392
    int vis_start = 0;
4✔
393
    int cursor = -1;
4✔
394
    do {
2✔
395
        c = bxs_strchr(tag, to_utf32(','), &cursor);
14✔
396
        bxstr_t *single_tag = bxs_trimdup(tag, vis_start, c != NULL ? (size_t) cursor : tag->num_chars_visible);
14✔
397
        int rc_add = tag_add(bison_args, single_tag);
14✔
398
        bxs_free(single_tag);
14✔
399
        if (rc_add != 0) {
14✔
400
            rc = rc_add;
10✔
401
        }
5✔
402
        vis_start = cursor + 1;
14✔
403
    } while (c != NULL);
14✔
404
    return rc;
4✔
405
}
406

407

408

409
int tag_record(pass_to_bison *bison_args, bxstr_t *tag)
4,384✔
410
{
411
    int rc = RC_SUCCESS;
4,384✔
412
    if (tag->num_chars_invisible > 0) {
4,384!
413
        yyerror(bison_args, "invalid tag");
×
414
        return RC_ERROR;
×
415
    }
416
    if (bxs_strchr(tag, to_utf32(','), NULL) != NULL) {
4,384✔
417
        rc = tag_split_add(bison_args, tag);
4✔
418
    }
2✔
419
    else {
420
        bxstr_t *trimmed = bxs_trimdup(tag, 0, tag->num_chars_visible);
4,380✔
421
        rc = tag_add(bison_args, trimmed);
4,380✔
422
        bxs_free(trimmed);
4,380✔
423
    }
424
    return rc;
4,384✔
425
}
2,192✔
426

427

428

429
/**
430
 * Rule action called when a shape list was fully parsed.
431
 * @param bison_args the parser state
432
 * @return 0: success;
433
 *         1: YYERROR must be invoked;
434
 *         2: YYABORT must be invoked
435
 */
436
int action_finalize_shapes(pass_to_bison *bison_args)
1,722✔
437
{
438
    int i, j;
439
    shape_t fshape; /* found shape */
440
    int fside;      /* first side */
441
    int sc;         /* side counter */
442
    int side;       /* effective side */
443
    int rc;         /* received return code */
444

445
    /*
446
     *  At least one shape must be specified
447
     */
448
    if (bison_args->num_shapespec < 1) {
1,722✔
449
        yyerror(bison_args, "must specify at least one non-empty shape per design");
4✔
450
        return RC_ERROR;
4✔
451
    }
452

453
    /*
454
     *  Ensure that all corners have been specified. Generate corners
455
     *  as necessary, starting at any side which already includes at
456
     *  least one shape in order to ensure correct measurements.
457
     */
458
    fshape = findshape(curdes.shape, NUM_SHAPES);
1,718✔
459
    if (fshape == NUM_SHAPES) {
1,718!
460
        yyerror(bison_args, "internal error");
×
461
        return RC_ABORT;
×
462
    }
463
    fside = on_side(fshape, 0);
1,718✔
464
    if (fside == NUM_SIDES) {
1,718!
465
        yyerror(bison_args, "internal error");
×
466
        return RC_ABORT;
×
467
    }
468

469
    for (sc = 0, side = fside; sc < NUM_SIDES; ++sc, side = (side + 1) % NUM_SIDES) {
8,590✔
470
        shape_t nshape; /* next shape */
471
        sentry_t *c;    /* corner to be processed */
472
        c = curdes.shape + sides[side][SHAPES_PER_SIDE - 1];
6,872✔
473

474
        if (isempty(c)) {
6,872✔
475
            nshape = findshape(c, SHAPES_PER_SIDE);
1,676✔
476
            if (side == BLEF || side == BRIG) {
1,676✔
477
                if (nshape == SHAPES_PER_SIDE) {
838✔
478
                    c->height = 1;
730✔
479
                }
365✔
480
                else {
481
                    c->height = c[nshape].height;
108✔
482
                }
483
                c->width = curdes.shape[fshape].width;
838✔
484
            }
419✔
485
            else {
486
                if (nshape == SHAPES_PER_SIDE) {
838✔
487
                    c->width = 1;
420✔
488
                }
210✔
489
                else {
490
                    c->width = c[nshape].width;
418✔
491
                }
492
                c->height = curdes.shape[fshape].height;
838✔
493
            }
494
            c->elastic = 0;
1,676✔
495
            rc = genshape(c->width, c->height, &(c->chars), &(c->mbcs));
1,676✔
496
            if (rc) {
1,676✔
497
                return RC_ABORT;
×
498
            }
499
        }
838✔
500

501
        fshape = sides[side][SHAPES_PER_SIDE - 1];
6,872✔
502
    }
3,436✔
503

504
    /*
505
     *  For all sides whose side shapes have not been defined, generate
506
     *  an elastic middle side shape.
507
     */
508
    for (side = 0; side < NUM_SIDES; ++side) {
8,590✔
509
        int found = 0;
6,872✔
510
        for (i = 1; i < SHAPES_PER_SIDE - 1; ++i) {
27,488✔
511
            if (isempty(curdes.shape + sides[side][i])) {
20,616✔
512
                continue;
12,606✔
513
            }
514
            else {
515
                found = 1;
8,010✔
516
            }
517
        }
4,005✔
518
        if (!found) {
6,872✔
519
            sentry_t *c = curdes.shape + sides[side][SHAPES_PER_SIDE / 2];
1,186✔
520
            if (side == BLEF || side == BRIG) {
1,186✔
521
                c->width = curdes.shape[sides[side][0]].width;
420✔
522
                c->height = 1;
420✔
523
            }
210✔
524
            else {
525
                c->width = 1;
766✔
526
                c->height = curdes.shape[sides[side][0]].height;
766✔
527
            }
528
            c->elastic = 1;
1,186✔
529
            rc = genshape(c->width, c->height, &(c->chars), &(c->mbcs));
1,186✔
530
            if (rc) {
1,186✔
531
                return RC_ABORT;
×
532
            }
533
        }
593✔
534
    }
3,436✔
535

536
    if (check_sizes(bison_args)) {
1,718!
537
        return RC_ERROR;
×
538
    }
539

540
    ++(bison_args->num_mandatory);
1,718✔
541
    if (++(bison_args->time_for_se_check) > 1) {
1,718✔
542
        if (perform_se_check(bison_args) != 0) {
×
543
            return RC_ERROR;
×
544
        }
545
    }
546

547
    /*
548
     *  Compute minimum height/width of a box of current design
549
     */
550
    for (i = 0; i < NUM_SIDES; ++i) {
8,590✔
551
        size_t c = 0;
6,872✔
552
        if (i % 2) { /* vertical sides */
6,872✔
553
            for (j = 0; j < SHAPES_PER_SIDE; ++j) {
20,616✔
554
                if (!isempty(curdes.shape + sides[i][j])) {
17,180✔
555
                    c += curdes.shape[sides[i][j]].height;
10,960✔
556
                }
5,480✔
557
            }
8,590✔
558
            if (c > curdes.minheight) {
3,436✔
559
                curdes.minheight = c;
1,862✔
560
            }
931✔
561
        }
1,718✔
562
        else { /* horizontal sides */
563
            for (j = 0; j < SHAPES_PER_SIDE; ++j) {
20,616✔
564
                if (!isempty(curdes.shape + sides[i][j])) {
17,180✔
565
                    c += curdes.shape[sides[i][j]].width;
11,980✔
566
                }
5,990✔
567
            }
8,590✔
568
            if (c > curdes.minwidth) {
3,436✔
569
                curdes.minwidth = c;
1,872✔
570
            }
936✔
571
        }
572
    }
3,436✔
573

574
    /*
575
     *  Compute height of highest shape in design
576
     */
577
    for (i = 0; i < NUM_SHAPES; ++i) {
29,206✔
578
        if (isempty(curdes.shape + i)) {
27,488✔
579
            continue;
11,420✔
580
        }
581
        if (curdes.shape[i].height > curdes.maxshapeheight) {
16,068✔
582
            curdes.maxshapeheight = curdes.shape[i].height;
2,120✔
583
        }
1,060✔
584
    }
8,034✔
585
    log_debug(__FILE__, PARSER, " Parser: Minimum box dimensions: width %d height %d\n",
1,718✔
586
                (int) curdes.minwidth, (int) curdes.minheight);
1,718✔
587
    log_debug(__FILE__, PARSER, " Parser: Maximum shape height: %d\n", (int) curdes.maxshapeheight);
1,718✔
588

589
    /*
590
     *  Set name of each shape
591
     */
592
    for (i = 0; i < NUM_SHAPES; ++i) {
29,206✔
593
        curdes.shape[i].name = i;
27,488✔
594
    }
13,744✔
595
    return RC_SUCCESS;
1,718✔
596
}
861✔
597

598

599

600
/**
601
 * Rule action called when a new box design is starting to be parsed.
602
 * @param bison_args the parser state
603
 * @param design_name the primary name of the design
604
 * @return 0: success;
605
 *         1: YYERROR must be invoked;
606
 *         2: YYABORT must be invoked
607
 */
608
int action_start_parsing_design(pass_to_bison *bison_args, char *design_name)
17,990✔
609
{
610
    bison_args->speeding = 0;
17,990✔
611
    bison_args->skipping = 0;
17,990✔
612

613
    curdes.name = (char *) strdup(design_name);
17,990✔
614
    if (curdes.name == NULL) {
17,990!
615
        perror(PROJECT);
×
616
        return RC_ABORT;
×
617
    }
618

619
    if (!design_needed(bison_args)) {
17,990✔
620
        bison_args->speeding = 1;
16,264✔
621
        log_debug(__FILE__, PARSER, " Parser: Skipping to next design (lexer doesn't know!)\n");
16,264✔
622
        return RC_ERROR; /* trigger the parser's `error` rule, which will skip to the next design */
16,264✔
623
    }
624
    return RC_SUCCESS;
1,726✔
625
}
8,995✔
626

627

628

629
int action_parent_config(pass_to_bison *bison_args, bxstr_t *filepath)
28✔
630
{
631
    if (is_debug_logging(PARSER)) {
28✔
632
        char *out_filepath = bxs_to_output(filepath);
×
633
        log_debug(__FILE__, PARSER, " Parser: parent config file specified: [%s]\n", out_filepath);
×
634
        BFREE(out_filepath);
×
635
    }
636

637
    if (bxs_is_empty(filepath)) {
28!
638
        bison_args->skipping = 1;
×
639
        yyerror(bison_args, "parent reference is empty");
×
640
        return RC_ERROR;
×
641
    }
642
    else if (strcasecmp(filepath->ascii, ":global:") == 0) { /* special token */
28✔
643
        filepath = discover_config_file(1);
×
644
        if (filepath == NULL) {
×
645
            bison_args->skipping = 1; /* prevent redundant "skipping to next design" message */
×
646
            yyerror(bison_args, "parent reference to global config which cannot be found");
×
647
            return RC_ERROR;
×
648
        }
649
    }
650
    else if (!bxs_valid_in_filename(filepath, NULL)) {
28✔
651
        yyerror(bison_args, "parent reference contains invalid characters: %s", bxs_to_output(filepath));
×
652
        return RC_ERROR;
×
653
    }
654
    else {
655
        FILE *f = bx_fopens(filepath, "r");
28✔
656
        if (f == NULL) {
28✔
657
            bison_args->skipping = 1;
×
658
            yyerror(bison_args, "parent config file not found: %s", bxs_to_output(filepath));
×
659
            return RC_ERROR;
×
660
        }
661
        else {
662
            fclose(f);
28✔
663
        }
664
    }
665
    if (is_debug_logging(PARSER)) {
28!
666
        char *out_filepath = bxs_to_output(filepath);
×
667
        log_debug(__FILE__, PARSER, " Parser: parent config file path resolved: [%s]\n", out_filepath);
×
668
        BFREE(out_filepath);
×
669
    }
670

671
    int is_new = !array_contains_bxs(bison_args->parent_configs, bison_args->num_parent_configs, filepath);
28✔
672
    if (is_new) {
28!
673
        bison_args->parent_configs
14✔
674
                = realloc(bison_args->parent_configs, (bison_args->num_parent_configs + 1) * sizeof(bxstr_t *));
42✔
675
        bison_args->parent_configs[bison_args->num_parent_configs] = filepath;
28✔
676
        ++(bison_args->num_parent_configs);
28✔
677
    }
14✔
678
    else if (is_debug_logging(PARSER)) {
×
679
        char *out_filepath = bxs_to_output(filepath);
×
680
        log_debug(__FILE__, PARSER, " Parser: duplicate parent / cycle: [%s]\n", out_filepath);
×
681
        BFREE(out_filepath);
×
682
    }
683
    return RC_SUCCESS;
28✔
684
}
14✔
685

686

687

688
int action_add_design(pass_to_bison *bison_args, char *design_primary_name, char *name_at_end)
1,718✔
689
{
690
    design_t *tmp;
691

692
    log_debug(__FILE__, PARSER, "--------- ADDING DESIGN \"%s\".\n", design_primary_name);
1,718✔
693

694
    if (strcasecmp(design_primary_name, name_at_end)) {
1,718!
695
        yyerror(bison_args, "box design name differs at BOX and END");
×
696
        return RC_ERROR;
×
697
    }
698
    if (bison_args->num_mandatory < 3) {
1,718✔
699
        yyerror(bison_args, "entries SAMPLE, SHAPES, and ELASTIC are mandatory");
2✔
700
        return RC_ERROR;
2✔
701
    }
702
    if (design_name_exists(bison_args, design_primary_name)) {
1,716✔
703
        yyerror(bison_args, "duplicate box design name -- %s", design_primary_name);
2✔
704
        return RC_ERROR;
2✔
705
    }
706

707
    bison_args->num_mandatory = 0;
1,714✔
708
    bison_args->time_for_se_check = 0;
1,714✔
709
    bison_args->num_shapespec = 0;
1,714✔
710

711
    /*
712
     *  Check if we need to continue parsing. If not, return.
713
     */
714
    if (!full_parse_required()) {
1,714✔
715
        bison_args->num_designs = bison_args->design_idx + 1;
692✔
716
        return RC_ACCEPT;
692✔
717
    }
718

719
    /*
720
     *  Allocate space for next design
721
     */
722
    ++(bison_args->design_idx);
1,022✔
723
    tmp = (design_t *) realloc(bison_args->designs, (bison_args->design_idx + 1) * sizeof(design_t));
1,022✔
724
    if (tmp == NULL) {
1,022!
725
        perror(PROJECT);
×
726
        return RC_ABORT;
×
727
    }
728
    bison_args->designs = tmp;
1,022✔
729
    init_design(bison_args, &(curdes));
1,022✔
730

731
    return RC_SUCCESS;
1,022✔
732
}
859✔
733

734

735

736
int action_record_keyword(pass_to_bison *bison_args, char *keyword, bxstr_t *value)
2,946✔
737
{
738
    if (is_debug_logging(PARSER)) {
2,946✔
739
        char *out_value = bxs_to_output(value);
×
740
        log_debug(__FILE__, PARSER, " Parser: entry rule fulfilled [%s = %s]\n", keyword, out_value);
×
741
        BFREE(out_value);
×
742
    }
743

744
    size_t error_pos = 0;
2,946✔
745
    if (!bxs_valid_in_kv_string(value, &error_pos)) {
2,946✔
746
        yyerror(bison_args, "invalid character in string value at position %d", (int) error_pos);
×
747
        return RC_ERROR;
×
748
    }
749

750
    if (strcasecmp(keyword, "author") == 0) {
2,946✔
751
        curdes.author = bxs_strdup(value);
1,548✔
752
        if (curdes.author == NULL) {
1,548✔
753
            perror(PROJECT);
×
754
            return RC_ABORT;
×
755
        }
756
    }
774✔
757
    else if (strcasecmp(keyword, "designer") == 0) {
1,398✔
758
        curdes.designer = bxs_strdup(value);
1,398✔
759
        if (curdes.designer == NULL) {
1,398!
760
            perror(PROJECT);
×
761
            return RC_ABORT;
×
762
        }
763
    }
699✔
764
    else if (strcasecmp(keyword, "tags") == 0) {
×
765
        tag_record(bison_args, value); /* discard return code (we print warnings, but tolerate the problem) */
×
766
    }
767
    else if (strcasecmp(keyword, "indent") == 0) {
×
768
        char *val = value->ascii;
×
769
        if (strcasecmp(val, "text") == 0 || strcasecmp(val, "box") == 0 || strcasecmp(val, "none") == 0) {
×
770
            curdes.indentmode = val[0];
×
771
        }
772
        else {
773
            yyerror(bison_args, "indent keyword must be followed by \"text\", \"box\", or \"none\"");
×
774
            return RC_ERROR;
×
775
        }
776
    }
777
    else {
778
        yyerror(bison_args, "internal parser error (unrecognized: %s) in line %d of %s.", keyword, __LINE__, __FILE__);
×
779
        return RC_ERROR;
×
780
    }
781
    return RC_SUCCESS;
2,946✔
782
}
1,473✔
783

784

785

786
int action_padding_entry(pass_to_bison *bison_args, char *area, int value)
1,860✔
787
{
788
    if (value < 0) {
1,860✔
789
        yyerror(bison_args, "padding must be a positive integer (%s %d) (ignored)", area, value);
×
790
    }
791
    else {
792
        size_t len1 = strlen(area);
1,860✔
793
        if (len1 <= 3 && strncasecmp("all", area, len1) == 0) {
1,860✔
794
            curdes.padding[BTOP] = value;
90✔
795
            curdes.padding[BBOT] = value;
90✔
796
            curdes.padding[BLEF] = value;
90✔
797
            curdes.padding[BRIG] = value;
90✔
798
        }
45✔
799
        else if (len1 <= 10 && strncasecmp("horizontal", area, len1) == 0) {
1,770!
800
            curdes.padding[BRIG] = value;
982✔
801
            curdes.padding[BLEF] = value;
982✔
802
        }
491✔
803
        else if (len1 <= 8 && strncasecmp("vertical", area, len1) == 0) {
788!
804
            curdes.padding[BTOP] = value;
164✔
805
            curdes.padding[BBOT] = value;
164✔
806
        }
82✔
807
        else if (len1 <= 3 && strncasecmp("top", area, len1) == 0) {
624✔
808
            curdes.padding[BTOP] = value;
144✔
809
        }
72✔
810
        else if (len1 <= 5 && strncasecmp("right", area, len1) == 0) {
480✔
811
            curdes.padding[BRIG] = value;
72✔
812
        }
36✔
813
        else if (len1 <= 4 && strncasecmp("left", area, len1) == 0) {
408✔
814
            curdes.padding[BLEF] = value;
354✔
815
        }
177✔
816
        else if (len1 <= 6 && strncasecmp("bottom", area, len1) == 0) {
54!
817
            curdes.padding[BBOT] = value;
54✔
818
        }
27✔
819
        else {
820
            yyerror(bison_args, "invalid padding area %s (ignored)", area);
×
821
        }
822
    }
823
    return RC_SUCCESS;
1,860✔
824
}
825

826

827

828
int action_init_parser(pass_to_bison *bison_args)
786✔
829
{
830
    bison_args->designs = (design_t *) calloc(1, sizeof(design_t));
786✔
831
    if (bison_args->designs == NULL) {
786!
832
        perror(PROJECT);
×
833
        return RC_ABORT;
×
834
    }
835
    bison_args->num_designs = 1;
786✔
836
    init_design(bison_args, bison_args->designs);
786✔
837
    return RC_SUCCESS;
786✔
838
}
393✔
839

840

841

842
int action_add_alias(pass_to_bison *bison_args, char *alias_name)
1,166✔
843
{
844
    if (design_name_exists(bison_args, alias_name)) {
1,166✔
845
        yyerror(bison_args, "alias already in use -- %s", alias_name);
2✔
846
        return RC_ERROR;
2✔
847
    }
848
    if (alias_exists_in_child_configs(bison_args, alias_name)) {
1,164✔
849
        log_debug(__FILE__, PARSER, " Parser: alias already used by child config, dropping: %s\n", alias_name);
4✔
850
    }
2✔
851
    else {
852
        size_t num_aliases = array_count0(curdes.aliases);
1,160✔
853
        curdes.aliases = (char **) realloc(curdes.aliases, (num_aliases + 2) * sizeof(char *));
1,160✔
854
        curdes.aliases[num_aliases] = strdup(alias_name);
1,160✔
855
        curdes.aliases[num_aliases + 1] = NULL;
1,160✔
856
    }
857
    return RC_SUCCESS;
1,164✔
858
}
583✔
859

860

861

862
static bxstr_t *adjust_eols(uint32_t *sample)
1,724✔
863
{
864
    if (eol_pattern == NULL) {
1,724✔
865
        eol_pattern = compile_pattern("(?:(\r\n?)|(\n))");
756✔
866
    }
378✔
867
    uint32_t *replaced = regex_replace(eol_pattern, opt.eol, sample, u32_strlen(sample), 1);
1,724✔
868
    bxstr_t *result = bxs_from_unicode(replaced);
1,724✔
869
    BFREE(replaced);
1,724✔
870
    return result;
1,724✔
871
}
872

873

874

875
static uint32_t *find_first_nonblank_line(bxstr_t *sample)
1,724✔
876
{
877
    uint32_t *result = sample->memory;
1,724✔
878
    uint32_t *p = sample->memory;
1,724✔
879
    while (*p != char_nul) {
1,724✔
880
        while (uc_is_blank(*p) || *p == char_cr) {
13,460✔
881
            p++;
11,736✔
882
        }
883
        if (*p == char_newline) {
1,724✔
884
            result = ++p;
×
885
        }
886
        else {
887
            break;
1,724✔
888
        }
889
    }
890
    return result;
1,724✔
891
}
892

893

894

895
int action_sample_block(pass_to_bison *bison_args, bxstr_t *sample)
1,724✔
896
{
897
    log_debug(__FILE__, PARSER, " Parser: SAMPLE block rule satisfied\n");
1,724✔
898

899
    if (curdes.sample) {
1,724!
900
        yyerror(bison_args, "duplicate SAMPLE block");
×
901
        return RC_ERROR;
×
902
    }
903

904
    uint32_t *p = find_first_nonblank_line(sample);
1,724✔
905
    bxstr_t *line = adjust_eols(p);
1,724✔
906
    if (line == NULL) {
1,724✔
907
        perror(PROJECT);
×
908
        return RC_ABORT;
×
909
    }
910

911
    if (bxs_is_empty(line)) {
1,724!
912
        yyerror(bison_args, "SAMPLE block must not be empty");
×
913
        return RC_ERROR;
×
914
    }
915
    size_t error_pos = 0;
1,724✔
916
    if (!bxs_valid_in_sample(line, &error_pos)) {
1,724✔
917
        yyerror(bison_args, "invalid character in SAMPLE block at position %d", (int) error_pos);
×
918
        return RC_ERROR;
×
919
    }
920

921
    curdes.sample = line;
1,724✔
922
    ++(bison_args->num_mandatory);
1,724✔
923
    return RC_SUCCESS;
1,724✔
924
}
862✔
925

926

927

928
int action_add_regex_rule(pass_to_bison *bison_args, char *type, reprule_t **rule_list, size_t *rule_list_len,
944✔
929
        bxstr_t *search, bxstr_t *replace, char mode)
930
{
931
    size_t n = *rule_list_len;
944✔
932

933
    if (is_debug_logging(PARSER)) {
944✔
934
        char *out_search = bxs_to_output(search);
×
935
        char *out_replace = bxs_to_output(replace);
×
936
        log_debug(__FILE__, PARSER, "Adding %s rule: \"%s\" with \"%s\" (%c)\n",
×
937
                strcmp(type, "rep") == 0 ? "replacement" : "reversion", out_search, out_replace, mode);
×
938
        BFREE(out_replace);
×
939
        BFREE(out_search);
×
940
    }
941

942
    *rule_list = (reprule_t *) realloc(*rule_list, (n + 1) * sizeof(reprule_t));
944✔
943
    if (*rule_list == NULL) {
944!
944
        perror(PROJECT);
×
945
        return RC_ABORT;
×
946
    }
947
    memset((*rule_list) + n, 0, sizeof(reprule_t));
944✔
948
    (*rule_list)[n].search = bxs_strdup(search);
944✔
949
    (*rule_list)[n].repstr = bxs_strdup(replace);
944✔
950
    if ((*rule_list)[n].search == NULL || (*rule_list)[n].repstr == NULL) {
944!
951
        perror(PROJECT);
×
952
        return RC_ABORT;
×
953
    }
954
    (*rule_list)[n].line = yyget_lineno(bison_args->lexer_state);
944✔
955
    (*rule_list)[n].mode = mode;
944✔
956
    *rule_list_len = n + 1;
944✔
957
    return RC_SUCCESS;
944✔
958
}
472✔
959

960

961

962
int action_first_shape_line(pass_to_bison *bison_args, bxstr_t *line, sentry_t *shape)
13,214✔
963
{
964
    sentry_t rval = SENTRY_INITIALIZER;
13,214✔
965

966
    log_debug(__FILE__, PARSER, "Initializing a shape entry with first line\n");
13,214✔
967

968
    size_t error_pos = 0;
13,214✔
969
    if (!bxs_valid_in_shape(line, &error_pos)) {
13,214✔
970
        yyerror(bison_args, "invalid character in shape line at position %d", (int) error_pos);
×
971
        return RC_ERROR;
×
972
    }
973

974
    rval.width = line->num_columns;
13,214✔
975
    rval.height = 1;
13,214✔
976

977
    rval.chars = (char **) malloc(sizeof(char *));
13,214✔
978
    if (rval.chars == NULL) {
13,214✔
979
        perror(PROJECT ": shape_lines21");
×
980
        return RC_ABORT;
×
981
    }
982
    rval.chars[0] = (char *) strdup(line->ascii);
13,214✔
983
    if (rval.chars[0] == NULL) {
13,214!
984
        perror(PROJECT ": shape_lines22");
×
985
        return RC_ABORT;
×
986
    }
987

988
    rval.mbcs = (bxstr_t **) malloc(sizeof(bxstr_t *));
13,214✔
989
    if (rval.mbcs == NULL) {
13,214✔
990
        perror(PROJECT ": shape_lines23");
×
991
        return RC_ABORT;
×
992
    }
993
    rval.mbcs[0] = bxs_strdup(line);
13,214✔
994
    if (rval.mbcs[0] == NULL) {
13,214!
995
        perror(PROJECT ": shape_lines24");
×
996
        return RC_ABORT;
×
997
    }
998

999
    memcpy(shape, &rval, sizeof(sentry_t));
13,214✔
1000
    return RC_SUCCESS;
13,214✔
1001
}
6,607✔
1002

1003

1004

1005
int action_add_shape_line(pass_to_bison *bison_args, sentry_t *shape, bxstr_t *line)
20,564✔
1006
{
1007
    log_debug(__FILE__, PARSER, "Extending a shape entry\n");
20,564✔
1008

1009
    size_t slen = line->num_columns;
20,564✔
1010
    if (slen != shape->width) {
20,564!
1011
        yyerror(bison_args, "all elements of a shape spec must be of equal length");
×
1012
        return RC_ERROR;
×
1013
    }
1014

1015
    size_t error_pos = 0;
20,564✔
1016
    if (!bxs_valid_in_shape(line, &error_pos)) {
20,564✔
1017
        yyerror(bison_args, "invalid character in shape line at position %d", (int) error_pos);
×
1018
        return RC_ERROR;
×
1019
    }
1020

1021
    shape->height++;
20,564✔
1022

1023
    char **tmp = (char **) realloc(shape->chars, shape->height * sizeof(char *));
20,564✔
1024
    if (tmp == NULL) {
20,564✔
1025
        perror(PROJECT ": shape_lines11");
×
1026
        return RC_ABORT;
×
1027
    }
1028
    shape->chars = tmp;
20,564✔
1029
    shape->chars[shape->height - 1] = (char *) strdup(line->ascii);
20,564✔
1030
    if (shape->chars[shape->height - 1] == NULL) {
20,564!
1031
        perror(PROJECT ": shape_lines12");
×
1032
        return RC_ABORT;
×
1033
    }
1034

1035
    bxstr_t **mtmp = (bxstr_t **) realloc(shape->mbcs, shape->height * sizeof(bxstr_t *));
20,564✔
1036
    if (mtmp == NULL) {
20,564✔
1037
        perror(PROJECT ": shape_lines13");
×
1038
        return RC_ABORT;
×
1039
    }
1040
    shape->mbcs = mtmp;
20,564✔
1041
    shape->mbcs[shape->height - 1] = bxs_strdup(line);
20,564✔
1042
    if (shape->mbcs[shape->height - 1] == NULL) {
20,564✔
1043
        perror(PROJECT ": shape_lines14");
×
1044
        return RC_ABORT;
×
1045
    }
1046

1047
    return RC_SUCCESS;
20,564✔
1048
}
10,282✔
1049

1050

1051
/* vim: set cindent sw=4: */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc