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

ascii-boxes / boxes / 7314850954

24 Dec 2023 01:12PM UTC coverage: 88.826% (+2.5%) from 86.336%
7314850954

push

github

tsjensen
Use -ggdb3 option for more detailed debug info

2869 of 3408 branches covered (0.0%)

Branch coverage included in aggregate %.

4619 of 5022 relevant lines covered (91.98%)

202175.9 hits per line

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

81.41
/src/parsecode.c
1
/*
2
 * boxes - Command line filter to draw/remove ASCII boxes around text
3
 * Copyright (c) 1999-2023 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 "parsecode.h"
31
#include "parsing.h"
32
#include "query.h"
33
#include "regulex.h"
34
#include "shape.h"
35
#include "tools.h"
36
#include "unicode.h"
37

38
#include "parser.h"
39

40
#include "lex.yy.h"
41

42

43
static pcre2_code *eol_pattern = NULL;
44

45
static pcre2_code *semver_pattern = NULL;
46

47

48

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

62
    #ifdef PARSER_DEBUG
63
        fprintf(stderr, " Parser: check_sizes()\n");
64
    #endif
65

66
    for (i = 0; i < NUM_SIDES; ++i) {
8,580✔
67
        if (i == 0 || i == 2) {
6,864✔
68
            /*
69
             *  horizontal
70
             */
71
            for (j = 0; j < SHAPES_PER_SIDE - 1; ++j) {
17,160✔
72
                if (curdes.shape[sides[i][j]].height == 0) {
13,728✔
73
                    continue;
5,192✔
74
                }
75
                for (k = j + 1; k < SHAPES_PER_SIDE; ++k) {
32,540✔
76
                    if (curdes.shape[sides[i][k]].height == 0) {
24,004✔
77
                        continue;
8,088✔
78
                    }
79
                    if (curdes.shape[sides[i][j]].height != curdes.shape[sides[i][k]].height) {
15,916✔
80
                        yyerror(bison_args, "All shapes on horizontal sides must be of "
×
81
                                "equal height (%s: %d, %s: %d)\n",
82
                                shape_name[sides[i][j]], curdes.shape[sides[i][j]].height,
×
83
                                shape_name[sides[i][k]], curdes.shape[sides[i][k]].height);
×
84
                        return 1;
×
85
                    }
86
                }
7,958✔
87
            }
4,268✔
88
        }
1,716✔
89
        else {
90
            /*
91
             *  vertical
92
             */
93
            for (j = 0; j < SHAPES_PER_SIDE - 1; ++j) {
17,160✔
94
                if (curdes.shape[sides[i][j]].width == 0) {
13,728✔
95
                    continue;
6,212✔
96
                }
97
                for (k = j + 1; k < SHAPES_PER_SIDE; ++k) {
29,412✔
98
                    if (curdes.shape[sides[i][k]].width == 0) {
21,896✔
99
                        continue;
9,480✔
100
                    }
101
                    if (curdes.shape[sides[i][j]].width != curdes.shape[sides[i][k]].width) {
12,416✔
102
                        yyerror(bison_args, "All shapes on vertical sides must be of "
×
103
                                "equal width (%s: %d, %s: %d)\n",
104
                                shape_name[sides[i][j]], curdes.shape[sides[i][j]].width,
×
105
                                shape_name[sides[i][k]], curdes.shape[sides[i][k]].width);
×
106
                        return 1;
×
107
                    }
108
                }
6,208✔
109
            }
3,758✔
110
        }
111
    }
3,432✔
112

113
    return 0; /* all clear */
1,716✔
114
}
858✔
115

116

117

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

130
    #ifdef PARSER_DEBUG
131
        fprintf(stderr, " Parser: corner_check()\n");
132
    #endif
133

134
    for (c = 0; c < NUM_CORNERS; ++c) {
8,570✔
135
        if (curdes.shape[corners[c]].elastic) {
6,856!
136
            yyerror(bison_args, "Corners may not be elastic (%s)", shape_name[corners[c]]);
×
137
            return 1;
×
138
        }
139
    }
3,428✔
140

141
    return 0; /* all clear */
1,714✔
142
}
857✔
143

144

145

146
static shape_t non_existent_elastics(pass_to_bison *bison_args)
1,714✔
147
{
148
    shape_t i;
149

150
    #ifdef PARSER_DEBUG
151
        fprintf(stderr, " Parser: non_existent_elastics()\n");
152
    #endif
153

154
    for (i = 0; i < NUM_SHAPES; ++i) {
29,138✔
155
        if (curdes.shape[i].elastic && isempty(curdes.shape + i)) {
27,424✔
156
            return i;
×
157
        }
158
    }
13,712✔
159

160
    return (shape_t) NUM_SHAPES; /* all elastic shapes exist */
1,714✔
161
}
857✔
162

163

164

165
static int insufficient_elasticity(pass_to_bison *bison_args)
1,714✔
166
{
167
    int i, j, ef;
168

169
    #ifdef PARSER_DEBUG
170
        fprintf(stderr, " Parser: insufficient_elasticity()\n");
171
    #endif
172

173
    for (i = 0; i < NUM_SIDES; ++i) {
8,570✔
174
        for (j = 1, ef = 0; j < 4; ++j) {
27,424✔
175
            if (curdes.shape[sides[i][j]].elastic) {
20,568✔
176
                ++ef;
7,224✔
177
            }
3,612✔
178
        }
10,284✔
179
        if (ef != 1 && ef != 2) {
6,856!
180
            return 1; /* error */
×
181
        }
182
    }
3,428✔
183

184
    return 0; /* all clear */
1,714✔
185
}
857✔
186

187

188

189
static int adjoining_elastics(pass_to_bison *bison_args)
1,714✔
190
{
191
    int i, j, ef;
192

193
    #ifdef PARSER_DEBUG
194
        fprintf(stderr, " Parser: adjoining_elastics()\n");
195
    #endif
196

197
    for (i = 0; i < NUM_SIDES; ++i) {
8,570✔
198
        ef = 0;
6,856✔
199
        for (j = 1; j < 4; ++j) {
27,424✔
200
            if (isempty(curdes.shape + sides[i][j])) {
20,568✔
201
                continue;
11,388✔
202
            }
203
            if (curdes.shape[sides[i][j]].elastic) {
9,180✔
204
                if (ef) {
7,224!
205
                    return 1; /* error detected */
×
206
                }
207
                else {
208
                    ef = 1;
7,224✔
209
                }
210
            }
3,612✔
211
            else {
212
                ef = 0;
1,956✔
213
            }
214
        }
4,590✔
215
    }
3,428✔
216

217
    return 0; /* all clear */
1,714✔
218
}
857✔
219

220

221

222
int perform_se_check(pass_to_bison *bison_args)
1,714✔
223
{
224
    shape_t s_rc;
225

226
    s_rc = non_existent_elastics(bison_args);
1,714✔
227
    if (s_rc != NUM_SHAPES) {
1,714!
228
        yyerror(bison_args, "Shape %s has been specified as elastic but doesn't exist", shape_name[s_rc]);
×
229
        return 1;
×
230
    }
231

232
    if (corner_check(bison_args)) {
1,714!
233
        /* Error message printed in check func */
234
        return 1;
×
235
    }
236

237
    if (insufficient_elasticity(bison_args)) {
1,714!
238
        yyerror(bison_args, "There must be exactly one or two elastic shapes per side");
×
239
        return 1;
×
240
    }
241

242
    if (adjoining_elastics(bison_args)) {
1,714✔
243
        yyerror(bison_args, "Two adjoining shapes may not be elastic");
×
244
        return 1;
×
245
    }
246

247
    return 0;
1,714✔
248
}
857✔
249

250

251

252
static void init_design(pass_to_bison *bison_args, design_t *design)
1,822✔
253
{
254
    memset(design, 0, sizeof(design_t));
1,822✔
255
    design->aliases = (char **) calloc(1, sizeof(char *));
1,822✔
256
    design->indentmode = DEF_INDENTMODE;
1,822✔
257
    design->defined_in = bison_args->config_file;
1,822✔
258
    design->tags = (char **) calloc(1, sizeof(char *));
1,822✔
259
}
1,822✔
260

261

262

263
/**
264
 * Reset parser to neutral state, so a new design can be parsed.
265
 * @param bison_args the parser state
266
 */
267
void recover(pass_to_bison *bison_args)
18✔
268
{
269
    bison_args->num_mandatory = 0;
18✔
270
    bison_args->time_for_se_check = 0;
18✔
271
    bison_args->num_shapespec = 0;
18✔
272

273
    /*
274
     *  Clear current design
275
     */
276
    BFREE(curdes.name);
18✔
277
    BFREE(curdes.author);
18!
278
    BFREE(curdes.aliases);
18!
279
    BFREE(curdes.designer);
18!
280
    BFREE(curdes.created);
18!
281
    BFREE(curdes.revision);
18!
282
    BFREE(curdes.revdate);
18!
283
    BFREE(curdes.sample);
18✔
284
    BFREE(curdes.tags);
18!
285
    init_design(bison_args, &(curdes));
18✔
286
}
18✔
287

288

289

290
static int design_has_alias(design_t *design, char *alias)
47,152✔
291
{
292
    int result = 0;
47,152✔
293
    for (size_t aidx = 0; design->aliases[aidx] != NULL; ++aidx) {
47,328✔
294
        if (strcasecmp(alias, design->aliases[aidx]) == 0) {
186✔
295
            result = 1;
10✔
296
            break;
10✔
297
        }
298
    }
88✔
299
    return result;
47,152✔
300
}
301

302

303

304
static int design_has_name(design_t *design, char *name)
47,640✔
305
{
306
    int result = 0;
47,640✔
307
    if (strcasecmp(name, design->name) == 0) {
47,640✔
308
        result = 1;
496✔
309
    }
248✔
310
    else {
311
        result = design_has_alias(design, name);
47,144✔
312
    }
313
    return result;
47,640✔
314
}
315

316

317

318
static int full_parse_required()
2,938✔
319
{
320
    int result = 0;
2,938✔
321
    if (!opt.design_choice_by_user) {
2,938✔
322
        result = opt.r || opt.l || (opt.query != NULL && !query_is_undoc());
2,440!
323
    }
1,220✔
324
    #ifdef DEBUG
325
        fprintf(stderr, " Parser: full_parse_required() -> %s\n", result ? "true" : "false");
326
    #endif
327
    return result;
2,938✔
328
}
329

330

331

332
/**
333
 * Determine if the design currently being parsed is one that we will need.
334
 * @param bison_args the bison state
335
 * @return result flag
336
 */
337
static int design_needed(pass_to_bison *bison_args)
17,986✔
338
{
339
    if (opt.design_choice_by_user) {
17,986✔
340
        return design_has_name(&(curdes), (char *) opt.design);
16,760✔
341
    }
342
    else {
343
        if (full_parse_required() || bison_args->design_idx == 0) {
1,226!
344
            return 1;
1,226✔
345
        }
346
    }
347
    return 0;
×
348
}
8,993✔
349

350

351

352
static int design_name_exists(pass_to_bison *bison_args, char *name)
1,804✔
353
{
354
    int result = 0;
1,804✔
355
    for (int i = 0; i < bison_args->design_idx; ++i) {
32,680✔
356
        if (design_has_name(bison_args->designs + i, name)) {
30,880✔
357
            result = 1;
4✔
358
            break;
4✔
359
        }
360
    }
15,438✔
361
    return result;
1,804✔
362
}
363

364

365

366
static int alias_exists_in_child_configs(pass_to_bison *bison_args, char *alias)
88✔
367
{
368
    int result = 0;
88✔
369
    for (size_t i = 0; i < bison_args->num_child_configs; ++i) {
92✔
370
        if (design_has_alias(bison_args->child_configs + i, alias)) {
8✔
371
            result = 1;
4✔
372
            break;
4✔
373
        }
374
    }
2✔
375
    return result;
88✔
376
}
377

378

379

380
static int tag_add(pass_to_bison *bison_args, bxstr_t *tag)
4,394✔
381
{
382
    int rc = RC_SUCCESS;
4,394✔
383
    if (is_ascii_id(tag, 1)) {
4,394✔
384
        if (!array_contains0(curdes.tags, tag->ascii)) {
4,380!
385
            size_t num_tags = array_count0(curdes.tags);
4,380✔
386
            curdes.tags = (char **) realloc(curdes.tags, (num_tags + 2) * sizeof(char *));
4,380✔
387
            curdes.tags[num_tags] = (char *) strdup(tag->ascii);
4,380✔
388
            curdes.tags[num_tags + 1] = NULL;
4,380✔
389
        }
2,190✔
390
        else {
391
            yyerror(bison_args, "duplicate tag -- %s", bxs_to_output(tag));
×
392
            rc = RC_ERROR;
×
393
        }
394
    }
2,190✔
395
    else {
396
        yyerror(bison_args, "invalid tag -- %s", bxs_to_output(tag));
14✔
397
        rc = RC_ERROR;
14✔
398
    }
399
    return rc;
4,394✔
400
}
401

402

403

404
static int tag_split_add(pass_to_bison *bison_args, bxstr_t *tag)
1,492✔
405
{
406
    int rc = RC_SUCCESS;
1,492✔
407
    uint32_t *c = NULL;
1,492✔
408
    int vis_start = 0;
1,492✔
409
    int cursor = -1;
1,492✔
410
    do {
746✔
411
        c = bxs_strchr(tag, to_utf32(','), &cursor);
4,190✔
412
        bxstr_t *single_tag = bxs_trimdup(tag, vis_start, c != NULL ? (size_t) cursor : tag->num_chars_visible);
4,190✔
413
        int rc_add = tag_add(bison_args, single_tag);
4,190✔
414
        bxs_free(single_tag);
4,190✔
415
        if (rc_add != 0) {
4,190✔
416
            rc = rc_add;
10✔
417
        }
5✔
418
        vis_start = cursor + 1;
4,190✔
419
    } while (c != NULL);
4,190✔
420
    return rc;
1,492✔
421
}
422

423

424

425
int tag_record(pass_to_bison *bison_args, bxstr_t *tag)
1,696✔
426
{
427
    int rc = RC_SUCCESS;
1,696✔
428
    if (tag->num_chars_invisible > 0) {
1,696!
429
        yyerror(bison_args, "invalid tag");
×
430
        return RC_ERROR;
×
431
    }
432
    if (bxs_strchr(tag, to_utf32(','), NULL) != NULL) {
1,696✔
433
        rc = tag_split_add(bison_args, tag);
1,492✔
434
    }
746✔
435
    else {
436
        bxstr_t *trimmed = bxs_trimdup(tag, 0, tag->num_chars_visible);
204✔
437
        rc = tag_add(bison_args, trimmed);
204✔
438
        bxs_free(trimmed);
204✔
439
    }
440
    return rc;
1,696✔
441
}
848✔
442

443

444

445
/**
446
 * Rule action called when a shape list was fully parsed.
447
 * @param bison_args the parser state
448
 * @return 0: success;
449
 *         1: YYERROR must be invoked;
450
 *         2: YYABORT must be invoked
451
 */
452
int action_finalize_shapes(pass_to_bison *bison_args)
1,720✔
453
{
454
    int i, j;
455
    shape_t fshape; /* found shape */
456
    int fside;      /* first side */
457
    int sc;         /* side counter */
458
    int side;       /* effective side */
459
    int rc;         /* received return code */
460

461
    /*
462
     *  At least one shape must be specified
463
     */
464
    if (bison_args->num_shapespec < 1) {
1,720✔
465
        yyerror(bison_args, "must specify at least one non-empty shape per design");
4✔
466
        return RC_ERROR;
4✔
467
    }
468

469
    /*
470
     *  Ensure that all corners have been specified. Generate corners
471
     *  as necessary, starting at any side which already includes at
472
     *  least one shape in order to ensure correct measurements.
473
     */
474
    fshape = findshape(curdes.shape, NUM_SHAPES);
1,716✔
475
    if (fshape == NUM_SHAPES) {
1,716!
476
        yyerror(bison_args, "internal error");
×
477
        return RC_ABORT;
×
478
    }
479
    fside = on_side(fshape, 0);
1,716✔
480
    if (fside == NUM_SIDES) {
1,716!
481
        yyerror(bison_args, "internal error");
×
482
        return RC_ABORT;
×
483
    }
484

485
    for (sc = 0, side = fside; sc < NUM_SIDES; ++sc, side = (side + 1) % NUM_SIDES) {
8,580✔
486
        shape_t nshape; /* next shape */
487
        sentry_t *c;    /* corner to be processed */
488
        c = curdes.shape + sides[side][SHAPES_PER_SIDE - 1];
6,864✔
489

490
        if (isempty(c)) {
6,864✔
491
            nshape = findshape(c, SHAPES_PER_SIDE);
1,668✔
492
            if (side == BLEF || side == BRIG) {
1,668✔
493
                if (nshape == SHAPES_PER_SIDE) {
834✔
494
                    c->height = 1;
726✔
495
                }
363✔
496
                else {
497
                    c->height = c[nshape].height;
108✔
498
                }
499
                c->width = curdes.shape[fshape].width;
834✔
500
            }
417✔
501
            else {
502
                if (nshape == SHAPES_PER_SIDE) {
834✔
503
                    c->width = 1;
418✔
504
                }
209✔
505
                else {
506
                    c->width = c[nshape].width;
416✔
507
                }
508
                c->height = curdes.shape[fshape].height;
834✔
509
            }
510
            c->elastic = 0;
1,668✔
511
            rc = genshape(c->width, c->height, &(c->chars), &(c->mbcs));
1,668✔
512
            if (rc) {
1,668✔
513
                return RC_ABORT;
×
514
            }
515
        }
834✔
516

517
        fshape = sides[side][SHAPES_PER_SIDE - 1];
6,864✔
518
    }
3,432✔
519

520
    /*
521
     *  For all sides whose side shapes have not been defined, generate
522
     *  an elastic middle side shape.
523
     */
524
    for (side = 0; side < NUM_SIDES; ++side) {
8,580✔
525
        int found = 0;
6,864✔
526
        for (i = 1; i < SHAPES_PER_SIDE - 1; ++i) {
27,456✔
527
            if (isempty(curdes.shape + sides[side][i])) {
20,592✔
528
                continue;
12,584✔
529
            }
530
            else {
531
                found = 1;
8,008✔
532
            }
533
        }
4,004✔
534
        if (!found) {
6,864✔
535
            sentry_t *c = curdes.shape + sides[side][SHAPES_PER_SIDE / 2];
1,180✔
536
            if (side == BLEF || side == BRIG) {
1,180✔
537
                c->width = curdes.shape[sides[side][0]].width;
418✔
538
                c->height = 1;
418✔
539
            }
209✔
540
            else {
541
                c->width = 1;
762✔
542
                c->height = curdes.shape[sides[side][0]].height;
762✔
543
            }
544
            c->elastic = 1;
1,180✔
545
            rc = genshape(c->width, c->height, &(c->chars), &(c->mbcs));
1,180✔
546
            if (rc) {
1,180✔
547
                return RC_ABORT;
×
548
            }
549
        }
590✔
550
    }
3,432✔
551

552
    if (check_sizes(bison_args)) {
1,716!
553
        return RC_ERROR;
×
554
    }
555

556
    ++(bison_args->num_mandatory);
1,716✔
557
    if (++(bison_args->time_for_se_check) > 1) {
1,716✔
558
        if (perform_se_check(bison_args) != 0) {
×
559
            return RC_ERROR;
×
560
        }
561
    }
562

563
    /*
564
     *  Compute minimum height/width of a box of current design
565
     */
566
    for (i = 0; i < NUM_SIDES; ++i) {
8,580✔
567
        size_t c = 0;
6,864✔
568
        if (i % 2) { /* vertical sides */
6,864✔
569
            for (j = 0; j < SHAPES_PER_SIDE; ++j) {
20,592✔
570
                if (!isempty(curdes.shape + sides[i][j])) {
17,160✔
571
                    c += curdes.shape[sides[i][j]].height;
10,948✔
572
                }
5,474✔
573
            }
8,580✔
574
            if (c > curdes.minheight) {
3,432✔
575
                curdes.minheight = c;
1,860✔
576
            }
930✔
577
        }
1,716✔
578
        else { /* horizontal sides */
579
            for (j = 0; j < SHAPES_PER_SIDE; ++j) {
20,592✔
580
                if (!isempty(curdes.shape + sides[i][j])) {
17,160✔
581
                    c += curdes.shape[sides[i][j]].width;
11,968✔
582
                }
5,984✔
583
            }
8,580✔
584
            if (c > curdes.minwidth) {
3,432✔
585
                curdes.minwidth = c;
1,870✔
586
            }
935✔
587
        }
588
    }
3,432✔
589

590
    /*
591
     *  Compute height of highest shape in design
592
     */
593
    for (i = 0; i < NUM_SHAPES; ++i) {
29,172✔
594
        if (isempty(curdes.shape + i)) {
27,456✔
595
            continue;
11,404✔
596
        }
597
        if (curdes.shape[i].height > curdes.maxshapeheight) {
16,052✔
598
            curdes.maxshapeheight = curdes.shape[i].height;
2,118✔
599
        }
1,059✔
600
    }
8,026✔
601
    #ifdef PARSER_DEBUG
602
        fprintf(stderr, " Parser: Minimum box dimensions: width %d height %d\n",
603
                (int) curdes.minwidth, (int) curdes.minheight);
604
        fprintf(stderr, " Parser: Maximum shape height: %d\n", (int) curdes.maxshapeheight);
605
    #endif
606

607
    /*
608
     *  Set name of each shape
609
     */
610
    for (i = 0; i < NUM_SHAPES; ++i) {
29,172✔
611
        curdes.shape[i].name = i;
27,456✔
612
    }
13,728✔
613
    return RC_SUCCESS;
1,716✔
614
}
860✔
615

616

617

618
/**
619
 * Rule action called when a new box design is starting to be parsed.
620
 * @param bison_args the parser state
621
 * @param design_name the primary name of the design
622
 * @return 0: success;
623
 *         1: YYERROR must be invoked;
624
 *         2: YYABORT must be invoked
625
 */
626
int action_start_parsing_design(pass_to_bison *bison_args, char *design_name)
17,986✔
627
{
628
    bison_args->speeding = 0;
17,986✔
629
    bison_args->skipping = 0;
17,986✔
630

631
    curdes.name = (char *) strdup(design_name);
17,986✔
632
    if (curdes.name == NULL) {
17,986!
633
        perror(PROJECT);
×
634
        return RC_ABORT;
×
635
    }
636

637
    if (!design_needed(bison_args)) {
17,986✔
638
        bison_args->speeding = 1;
16,262✔
639
        #ifdef PARSER_DEBUG
640
                fprintf(stderr, " Parser: Skipping to next design (lexer doesn't know!)\n");
641
        #endif
642
        return RC_ERROR; /* trigger the parser's `error` rule, which will skip to the next design */
16,262✔
643
    }
644
    return RC_SUCCESS;
1,724✔
645
}
8,993✔
646

647

648

649
int action_parent_config(pass_to_bison *bison_args, bxstr_t *filepath)
26✔
650
{
651
    #ifdef PARSER_DEBUG
652
        fprintf(stderr, " Parser: parent config file specified: [%s]\n", bxs_to_output(filepath));
653
    #endif
654
    if (bxs_is_empty(filepath)) {
26!
655
        bison_args->skipping = 1;
×
656
        yyerror(bison_args, "parent reference is empty");
×
657
        return RC_ERROR;
×
658
    }
659
    else if (strcasecmp(filepath->ascii, ":global:") == 0) { /* special token */
26✔
660
        filepath = discover_config_file(1);
×
661
        if (filepath == NULL) {
×
662
            bison_args->skipping = 1; /* prevent redundant "skipping to next design" message */
×
663
            yyerror(bison_args, "parent reference to global config which cannot be found");
×
664
            return RC_ERROR;
×
665
        }
666
    }
667
    else if (!bxs_valid_in_filename(filepath, NULL)) {
26✔
668
        yyerror(bison_args, "parent reference contains invalid characters: %s", bxs_to_output(filepath));
×
669
        return RC_ERROR;
×
670
    }
671
    else {
672
        FILE *f = bx_fopens(filepath, "r");
26✔
673
        if (f == NULL) {
26✔
674
            bison_args->skipping = 1;
×
675
            yyerror(bison_args, "parent config file not found: %s", bxs_to_output(filepath));
×
676
            return RC_ERROR;
×
677
        }
678
        else {
679
            fclose(f);
26✔
680
        }
681
    }
682
    #ifdef PARSER_DEBUG
683
        fprintf(stderr, " Parser: parent config file path resolved: [%s]\n", bxs_to_output(filepath));
684
    #endif
685

686
    int is_new = !array_contains_bxs(bison_args->parent_configs, bison_args->num_parent_configs, filepath);
26✔
687
    if (is_new) {
26!
688
        bison_args->parent_configs
13✔
689
                = realloc(bison_args->parent_configs, (bison_args->num_parent_configs + 1) * sizeof(bxstr_t *));
39✔
690
        bison_args->parent_configs[bison_args->num_parent_configs] = filepath;
26✔
691
        ++(bison_args->num_parent_configs);
26✔
692
    }
13✔
693
    else {
694
        #ifdef PARSER_DEBUG
695
            fprintf(stderr, " Parser: duplicate parent / cycle: [%s]\n", bxs_to_output(filepath));
696
        #endif
697
    }
698
    return RC_SUCCESS;
26✔
699
}
13✔
700

701

702

703
int action_add_design(pass_to_bison *bison_args, char *design_primary_name, char *name_at_end)
1,716✔
704
{
705
    design_t *tmp;
706

707
    #ifdef PARSER_DEBUG
708
        fprintf(stderr, "--------- ADDING DESIGN \"%s\".\n", design_primary_name);
709
    #endif
710

711
    if (strcasecmp(design_primary_name, name_at_end)) {
1,716!
712
        yyerror(bison_args, "box design name differs at BOX and END");
×
713
        return RC_ERROR;
×
714
    }
715
    if (bison_args->num_mandatory < 3) {
1,716✔
716
        yyerror(bison_args, "entries SAMPLE, SHAPES, and ELASTIC are mandatory");
2✔
717
        return RC_ERROR;
2✔
718
    }
719
    if (design_name_exists(bison_args, design_primary_name)) {
1,714✔
720
        yyerror(bison_args, "duplicate box design name -- %s", design_primary_name);
2✔
721
        return RC_ERROR;
2✔
722
    }
723

724
    bison_args->num_mandatory = 0;
1,712✔
725
    bison_args->time_for_se_check = 0;
1,712✔
726
    bison_args->num_shapespec = 0;
1,712✔
727

728
    /*
729
     *  Check if we need to continue parsing. If not, return.
730
     */
731
    if (!full_parse_required()) {
1,712✔
732
        bison_args->num_designs = bison_args->design_idx + 1;
690✔
733
        return RC_ACCEPT;
690✔
734
    }
735

736
    /*
737
     *  Allocate space for next design
738
     */
739
    ++(bison_args->design_idx);
1,022✔
740
    tmp = (design_t *) realloc(bison_args->designs, (bison_args->design_idx + 1) * sizeof(design_t));
1,022✔
741
    if (tmp == NULL) {
1,022!
742
        perror(PROJECT);
×
743
        return RC_ABORT;
×
744
    }
745
    bison_args->designs = tmp;
1,022✔
746
    init_design(bison_args, &(curdes));
1,022✔
747

748
    return RC_SUCCESS;
1,022✔
749
}
858✔
750

751

752

753
static int is_semantic_version(char *version)
1,526✔
754
{
755
    if (semver_pattern == NULL) {
1,526✔
756
        /* Not a strict semver, "1" or "1.0" are accepted. */
757
        semver_pattern = compile_pattern(
674✔
758
            "^(0|[1-9]\\d*)(?:\\.(0|[1-9]\\d*))?(?:\\.(0|[1-9]\\d*))?(?:[+-][a-zA-Z0-9\\.+-]+)?$");
759
    }
337✔
760
    return regex_match(semver_pattern, version);
1,526✔
761
}
762

763

764

765
int action_record_keyword(pass_to_bison *bison_args, char *keyword, bxstr_t *value)
7,142✔
766
{
767
    #ifdef PARSER_DEBUG
768
        fprintf(stderr, " Parser: entry rule fulfilled [%s = %s]\n", keyword, bxs_to_output(value));
769
    #endif
770

771
    size_t error_pos = 0;
7,142✔
772
    if (!bxs_valid_in_kv_string(value, &error_pos)) {
7,142!
773
        yyerror(bison_args, "invalid character in string value at position %d", (int) error_pos);
×
774
        return RC_ERROR;
×
775
    }
776

777
    if (strcasecmp(keyword, "author") == 0) {
7,142✔
778
        curdes.author = bxs_strdup(value);
1,548✔
779
        if (curdes.author == NULL) {
1,548✔
780
            perror(PROJECT);
×
781
            return RC_ABORT;
×
782
        }
783
    }
774✔
784
    else if (strcasecmp(keyword, "designer") == 0) {
5,594✔
785
        curdes.designer = bxs_strdup(value);
1,398✔
786
        if (curdes.designer == NULL) {
1,398✔
787
            perror(PROJECT);
×
788
            return RC_ABORT;
×
789
        }
790
    }
699✔
791
    else if (strcasecmp(keyword, "revision") == 0) {
4,196✔
792
        if (is_semantic_version(value->ascii)) {
1,526!
793
            curdes.revision = (char *) strdup(value->ascii);
1,526✔
794
            if (curdes.revision == NULL) {
1,526✔
795
                perror(PROJECT);
×
796
                return RC_ABORT;
×
797
            }
798
        }
763✔
799
        else {
800
            yyerror(bison_args, "revision is not a version number in line %d of %s", __LINE__, __FILE__);
×
801
            return RC_ERROR;
×
802
        }
803
    }
763✔
804
    else if (strcasecmp(keyword, "created") == 0) {
2,670✔
805
        curdes.created = bxs_strdup(value);
1,506✔
806
        if (curdes.created == NULL) {
1,506✔
807
            perror(PROJECT);
×
808
            return RC_ABORT;
×
809
        }
810
    }
753✔
811
    else if (strcasecmp(keyword, "revdate") == 0) {
1,164✔
812
        curdes.revdate = bxs_strdup(value);
1,164✔
813
        if (curdes.revdate == NULL) {
1,164!
814
            perror(PROJECT);
×
815
            return RC_ABORT;
×
816
        }
817
    }
582✔
818
    else if (strcasecmp(keyword, "tags") == 0) {
×
819
        tag_record(bison_args, value); /* discard return code (we print warnings, but tolerate the problem) */
×
820
    }
821
    else if (strcasecmp(keyword, "indent") == 0) {
×
822
        char *val = value->ascii;
×
823
        if (strcasecmp(val, "text") == 0 || strcasecmp(val, "box") == 0 || strcasecmp(val, "none") == 0) {
×
824
            curdes.indentmode = val[0];
×
825
        }
826
        else {
827
            yyerror(bison_args, "indent keyword must be followed by \"text\", \"box\", or \"none\"");
×
828
            return RC_ERROR;
×
829
        }
830
    }
831
    else {
832
        yyerror(bison_args, "internal parser error (unrecognized: %s) in line %d of %s.", keyword, __LINE__, __FILE__);
×
833
        return RC_ERROR;
×
834
    }
835
    return RC_SUCCESS;
7,142✔
836
}
3,571✔
837

838

839

840
int action_padding_entry(pass_to_bison *bison_args, char *area, int value)
1,860✔
841
{
842
    if (value < 0) {
1,860✔
843
        yyerror(bison_args, "padding must be a positive integer (%s %d) (ignored)", area, value);
×
844
    }
845
    else {
846
        size_t len1 = strlen(area);
1,860✔
847
        if (len1 <= 3 && strncasecmp("all", area, len1) == 0) {
1,860✔
848
            curdes.padding[BTOP] = value;
90✔
849
            curdes.padding[BBOT] = value;
90✔
850
            curdes.padding[BLEF] = value;
90✔
851
            curdes.padding[BRIG] = value;
90✔
852
        }
45✔
853
        else if (len1 <= 10 && strncasecmp("horizontal", area, len1) == 0) {
1,770!
854
            curdes.padding[BRIG] = value;
982✔
855
            curdes.padding[BLEF] = value;
982✔
856
        }
491✔
857
        else if (len1 <= 8 && strncasecmp("vertical", area, len1) == 0) {
788!
858
            curdes.padding[BTOP] = value;
164✔
859
            curdes.padding[BBOT] = value;
164✔
860
        }
82✔
861
        else if (len1 <= 3 && strncasecmp("top", area, len1) == 0) {
624✔
862
            curdes.padding[BTOP] = value;
144✔
863
        }
72✔
864
        else if (len1 <= 5 && strncasecmp("right", area, len1) == 0) {
480✔
865
            curdes.padding[BRIG] = value;
72✔
866
        }
36✔
867
        else if (len1 <= 4 && strncasecmp("left", area, len1) == 0) {
408✔
868
            curdes.padding[BLEF] = value;
354✔
869
        }
177✔
870
        else if (len1 <= 6 && strncasecmp("bottom", area, len1) == 0) {
54!
871
            curdes.padding[BBOT] = value;
54✔
872
        }
27✔
873
        else {
874
            yyerror(bison_args, "invalid padding area %s (ignored)", area);
×
875
        }
876
    }
877
    return RC_SUCCESS;
1,860✔
878
}
879

880

881

882
int action_init_parser(pass_to_bison *bison_args)
782✔
883
{
884
    bison_args->designs = (design_t *) calloc(1, sizeof(design_t));
782✔
885
    if (bison_args->designs == NULL) {
782!
886
        perror(PROJECT);
×
887
        return RC_ABORT;
×
888
    }
889
    bison_args->num_designs = 1;
782✔
890
    init_design(bison_args, bison_args->designs);
782✔
891
    return RC_SUCCESS;
782✔
892
}
391✔
893

894

895

896
int action_add_alias(pass_to_bison *bison_args, char *alias_name)
90✔
897
{
898
    if (design_name_exists(bison_args, alias_name)) {
90✔
899
        yyerror(bison_args, "alias already in use -- %s", alias_name);
2✔
900
        return RC_ERROR;
2✔
901
    }
902
    if (alias_exists_in_child_configs(bison_args, alias_name)) {
88✔
903
        #ifdef PARSER_DEBUG
904
            fprintf(stderr, " Parser: alias already used by child config, dropping: %s\n", alias_name);
905
        #endif
906
    }
2✔
907
    else {
908
        size_t num_aliases = array_count0(curdes.aliases);
84✔
909
        curdes.aliases = (char **) realloc(curdes.aliases, (num_aliases + 2) * sizeof(char *));
84✔
910
        curdes.aliases[num_aliases] = strdup(alias_name);
84✔
911
        curdes.aliases[num_aliases + 1] = NULL;
84✔
912
    }
913
    return RC_SUCCESS;
88✔
914
}
45✔
915

916

917

918
static bxstr_t *adjust_eols(uint32_t *sample)
1,722✔
919
{
920
    if (eol_pattern == NULL) {
1,722✔
921
        eol_pattern = compile_pattern("(?(?=\r)(\r\n?)|(\n))");
754✔
922
    }
377✔
923
    uint32_t *replaced = regex_replace(eol_pattern, opt.eol, sample, u32_strlen(sample), 1);
1,722✔
924
    bxstr_t *result = bxs_from_unicode(replaced);
1,722✔
925
    BFREE(replaced);
1,722✔
926
    return result;
1,722✔
927
}
928

929

930

931
static uint32_t *find_first_nonblank_line(bxstr_t *sample)
1,722✔
932
{
933
    uint32_t *result = sample->memory;
1,722✔
934
    uint32_t *p = sample->memory;
1,722✔
935
    while (*p != char_nul) {
1,722✔
936
        while (uc_is_blank(*p) || *p == char_cr) {
13,450✔
937
            p++;
11,728✔
938
        }
939
        if (*p == char_newline) {
1,722✔
940
            result = ++p;
×
941
        }
942
        else {
943
            break;
1,722✔
944
        }
945
    }
946
    return result;
1,722✔
947
}
948

949

950

951
int action_sample_block(pass_to_bison *bison_args, bxstr_t *sample)
1,722✔
952
{
953
    #ifdef PARSER_DEBUG
954
        fprintf(stderr, " Parser: SAMPLE block rule satisfied\n");
955
    #endif
956

957
    if (curdes.sample) {
1,722!
958
        yyerror(bison_args, "duplicate SAMPLE block");
×
959
        return RC_ERROR;
×
960
    }
961

962
    uint32_t *p = find_first_nonblank_line(sample);
1,722✔
963
    bxstr_t *line = adjust_eols(p);
1,722✔
964
    if (line == NULL) {
1,722✔
965
        perror(PROJECT);
×
966
        return RC_ABORT;
×
967
    }
968

969
    if (bxs_is_empty(line)) {
1,722!
970
        yyerror(bison_args, "SAMPLE block must not be empty");
×
971
        return RC_ERROR;
×
972
    }
973
    size_t error_pos = 0;
1,722✔
974
    if (!bxs_valid_in_sample(line, &error_pos)) {
1,722✔
975
        yyerror(bison_args, "invalid character in SAMPLE block at position %d", (int) error_pos);
×
976
        return RC_ERROR;
×
977
    }
978

979
    curdes.sample = line;
1,722✔
980
    ++(bison_args->num_mandatory);
1,722✔
981
    return RC_SUCCESS;
1,722✔
982
}
861✔
983

984

985

986
int action_add_regex_rule(pass_to_bison *bison_args, char *type, reprule_t **rule_list, size_t *rule_list_len,
944✔
987
        bxstr_t *search, bxstr_t *replace, char mode)
988
{
989
    size_t n = *rule_list_len;
944✔
990

991
    UNUSED(type); /* used only in PARSER_DEBUG mode */
472✔
992
    #ifdef PARSER_DEBUG
993
        fprintf(stderr, "Adding %s rule: \"%s\" with \"%s\" (%c)\n",
994
                strcmp(type, "rep") == 0 ? "replacement" : "reversion",
995
                bxs_to_output(search), bxs_to_output(replace), mode);
996
    #endif
997

998
    *rule_list = (reprule_t *) realloc(*rule_list, (n + 1) * sizeof(reprule_t));
944✔
999
    if (*rule_list == NULL) {
944!
1000
        perror(PROJECT);
×
1001
        return RC_ABORT;
×
1002
    }
1003
    memset((*rule_list) + n, 0, sizeof(reprule_t));
944✔
1004
    (*rule_list)[n].search = bxs_strdup(search);
944✔
1005
    (*rule_list)[n].repstr = bxs_strdup(replace);
944✔
1006
    if ((*rule_list)[n].search == NULL || (*rule_list)[n].repstr == NULL) {
944!
1007
        perror(PROJECT);
×
1008
        return RC_ABORT;
×
1009
    }
1010
    (*rule_list)[n].line = yyget_lineno(bison_args->lexer_state);
944✔
1011
    (*rule_list)[n].mode = mode;
944✔
1012
    *rule_list_len = n + 1;
944✔
1013
    return RC_SUCCESS;
944✔
1014
}
472✔
1015

1016

1017

1018
int action_first_shape_line(pass_to_bison *bison_args, bxstr_t *line, sentry_t *shape)
13,212✔
1019
{
1020
    sentry_t rval = SENTRY_INITIALIZER;
13,212✔
1021

1022
    #ifdef PARSER_DEBUG
1023
        fprintf(stderr, "Initializing a shape entry with first line\n");
1024
    #endif
1025

1026
    size_t error_pos = 0;
13,212✔
1027
    if (!bxs_valid_in_shape(line, &error_pos)) {
13,212✔
1028
        yyerror(bison_args, "invalid character in shape line at position %d", (int) error_pos);
×
1029
        return RC_ERROR;
×
1030
    }
1031

1032
    rval.width = line->num_columns;
13,212✔
1033
    rval.height = 1;
13,212✔
1034

1035
    rval.chars = (char **) malloc(sizeof(char *));
13,212✔
1036
    if (rval.chars == NULL) {
13,212✔
1037
        perror(PROJECT ": shape_lines21");
×
1038
        return RC_ABORT;
×
1039
    }
1040
    rval.chars[0] = (char *) strdup(line->ascii);
13,212✔
1041
    if (rval.chars[0] == NULL) {
13,212!
1042
        perror(PROJECT ": shape_lines22");
×
1043
        return RC_ABORT;
×
1044
    }
1045

1046
    rval.mbcs = (bxstr_t **) malloc(sizeof(bxstr_t *));
13,212✔
1047
    if (rval.mbcs == NULL) {
13,212✔
1048
        perror(PROJECT ": shape_lines23");
×
1049
        return RC_ABORT;
×
1050
    }
1051
    rval.mbcs[0] = bxs_strdup(line);
13,212✔
1052
    if (rval.mbcs[0] == NULL) {
13,212!
1053
        perror(PROJECT ": shape_lines24");
×
1054
        return RC_ABORT;
×
1055
    }
1056

1057
    memcpy(shape, &rval, sizeof(sentry_t));
13,212✔
1058
    return RC_SUCCESS;
13,212✔
1059
}
6,606✔
1060

1061

1062

1063
int action_add_shape_line(pass_to_bison *bison_args, sentry_t *shape, bxstr_t *line)
20,564✔
1064
{
1065
    #ifdef PARSER_DEBUG
1066
        fprintf(stderr, "Extending a shape entry\n");
1067
    #endif
1068

1069
    size_t slen = line->num_columns;
20,564✔
1070
    if (slen != shape->width) {
20,564!
1071
        yyerror(bison_args, "all elements of a shape spec must be of equal length");
×
1072
        return RC_ERROR;
×
1073
    }
1074

1075
    size_t error_pos = 0;
20,564✔
1076
    if (!bxs_valid_in_shape(line, &error_pos)) {
20,564✔
1077
        yyerror(bison_args, "invalid character in shape line at position %d", (int) error_pos);
×
1078
        return RC_ERROR;
×
1079
    }
1080

1081
    shape->height++;
20,564✔
1082

1083
    char **tmp = (char **) realloc(shape->chars, shape->height * sizeof(char *));
20,564✔
1084
    if (tmp == NULL) {
20,564✔
1085
        perror(PROJECT ": shape_lines11");
×
1086
        return RC_ABORT;
×
1087
    }
1088
    shape->chars = tmp;
20,564✔
1089
    shape->chars[shape->height - 1] = (char *) strdup(line->ascii);
20,564✔
1090
    if (shape->chars[shape->height - 1] == NULL) {
20,564!
1091
        perror(PROJECT ": shape_lines12");
×
1092
        return RC_ABORT;
×
1093
    }
1094

1095
    bxstr_t **mtmp = (bxstr_t **) realloc(shape->mbcs, shape->height * sizeof(bxstr_t *));
20,564✔
1096
    if (mtmp == NULL) {
20,564✔
1097
        perror(PROJECT ": shape_lines13");
×
1098
        return RC_ABORT;
×
1099
    }
1100
    shape->mbcs = mtmp;
20,564✔
1101
    shape->mbcs[shape->height - 1] = bxs_strdup(line);
20,564✔
1102
    if (shape->mbcs[shape->height - 1] == NULL) {
20,564✔
1103
        perror(PROJECT ": shape_lines14");
×
1104
        return RC_ABORT;
×
1105
    }
1106

1107
    return RC_SUCCESS;
20,564✔
1108
}
10,282✔
1109

1110

1111
/* 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