From b51724f3b17190d62204033eb677828a1150387a Mon Sep 17 00:00:00 2001 From: Thomas Jensen Date: Fri, 2 Apr 2021 15:45:04 +0200 Subject: [PATCH] Handle tags as a real list in memory and config file #23 The previous keyword-based syntax remains supported for forwards compatibility reasons. --- src/boxes.c | 19 +++++--- src/boxes.in.h | 2 +- src/lexer.l | 8 +++- src/parser.y | 127 +++++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 140 insertions(+), 16 deletions(-) diff --git a/src/boxes.c b/src/boxes.c index b2eb3e8..a30965a 100644 --- a/src/boxes.c +++ b/src/boxes.c @@ -164,8 +164,7 @@ static int process_commandline(int argc, char *argv[]) /* * Intercept '--help' and '-?' cases first, as they are not supported by getopt() */ - if (argc >= 2 && argv[1] != NULL - && (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)) { + if (argc >= 2 && argv[1] != NULL && (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)) { usage_long(stdout); return 42; } @@ -632,8 +631,8 @@ static int build_design(design_t **adesigns, const char *cld) dp->padding[BLEF] = 1; dp->defined_in = "(command line)"; - dp->tags = (char *) calloc(10, sizeof(char)); - strcpy(dp->tags, "transient"); + dp->tags = (char **) calloc(2, sizeof(char *)); + dp->tags[0] = "transient"; dp->shape[W].height = 1; dp->shape[W].width = strlen(cld); @@ -901,8 +900,16 @@ static int list_styles() empty_side(opt.design->shape, BTOP) && empty_side(opt.design->shape, BBOT) ? "no" : "yes"); - fprintf(opt.outfile, "Tags: %s\n", - d->tags ? d->tags : "(none)"); + fprintf(opt.outfile, "Tags: "); + size_t tidx = 0; + while(d->tags[tidx] != NULL) { + fprintf(opt.outfile, "%s%s", tidx > 0 ? ", " : "", d->tags[tidx]); + ++tidx; + } + if (tidx == 0) { + fprintf(opt.outfile, "none"); + } + fprintf(opt.outfile, "\n"); fprintf(opt.outfile, "Elastic Shapes: "); sstart = 0; diff --git a/src/boxes.in.h b/src/boxes.in.h index 66750a8..bfff602 100644 --- a/src/boxes.in.h +++ b/src/boxes.in.h @@ -105,7 +105,7 @@ typedef struct { size_t minwidth; size_t minheight; int padding[ANZ_SIDES]; - char *tags; + char **tags; char *defined_in; /* path to config file where this was defined */ reprule_t *current_rule; diff --git a/src/lexer.l b/src/lexer.l index 4519c99..5c5d500 100644 --- a/src/lexer.l +++ b/src/lexer.l @@ -266,6 +266,12 @@ ends[ \t\r]*$ { } +Tags { + #ifdef LEXER_DEBUG + fprintf (stderr, " YTAGS: %s\n", yytext); + #endif + return YTAGS; +} Elastic { BEGIN(ELASTIC); @@ -327,7 +333,7 @@ Once { yylval->c = 'o'; return YRXPFLAG; } } -author|designer|tags|created|revision|revdate|indent { +author|designer|created|revision|revdate|indent { /* * general key words */ diff --git a/src/parser.y b/src/parser.y index 1d5392d..9f5cf02 100644 --- a/src/parser.y +++ b/src/parser.y @@ -326,6 +326,7 @@ static void init_design(pass_to_bison *bison_args, design_t *design) design->aliases = (char **) calloc(1, sizeof(char *)); design->indentmode = DEF_INDENTMODE; design->defined_in = bison_args->config_file; + design->tags = (char **) calloc(1, sizeof(char *)); } @@ -413,6 +414,109 @@ static int design_name_exists(pass_to_bison *bison_args, char *name) return result; } + + +static int tag_is_valid(char *tag) +{ + const size_t len = strlen(tag); + return len > 0 + && strspn(tag, "abcdefghijklmnopqrstuvwxyz-0123456789") == len + && strchr("abcdefghijklmnopqrstuvwxyz", tag[0]) != NULL + && tag[len - 1] != '-' + && strstr(tag, "--") == NULL; +} + + + +static int tag_add(pass_to_bison *bison_args, char *tag) +{ + int rc = 0; + if (tag_is_valid(tag)) { + if (!array_contains0(curdes.tags, tag)) { + size_t num_tags = array_count0(curdes.tags); + curdes.tags = (char **) realloc(curdes.tags, (num_tags + 2) * sizeof(char *)); + curdes.tags[num_tags] = tag; + curdes.tags[num_tags + 1] = NULL; + } + else { + yyerror(bison_args, "duplicate tag -- %s", tag); + rc = 1; + } + } + else { + yyerror(bison_args, "invalid tag -- %s", tag); + rc = 2; + } + return rc; +} + + + +static char *tag_next_comma(char *s) +{ + char *comma = strchr(s, ','); + if (comma == NULL) { + comma = s + strlen(s); + } + return comma; +} + + + +static char *tag_trim(char *s, char *e) +{ + if (s > e || (s == e && *s == '\0')) { + return strdup(""); + } + while (s <= e && (*s == ' ' || *s == '\t')) { + ++s; + } + while (e > s && (*e == ' ' || *e == '\t')) { + --e; + } + return strndup(s, e - s + 1); +} + + + +static int tag_split_add(pass_to_bison *bison_args, char *tag) +{ + int rc = 0; + char *s = tag; + char *c = NULL; + do { + c = tag_next_comma(s); + char *single_tag = tag_trim(s, c - 1); + int rc_add = tag_add(bison_args, single_tag); + if (rc_add != 0) { + rc = rc_add; + } + s = c + 1; + } while (*c != '\0'); + return rc; +} + + + +/** + * Add tag to list of current design's tag after validity checking. + * @param bison_args the parser state + * @param tag a single tag, or a comma-separated list of tags + * @return error code, 0 on success, anything else on failure + */ +static int tag_record(pass_to_bison *bison_args, char *tag) +{ + int rc = 0; + if (strchr(tag, ',') != NULL) { + rc = tag_split_add(bison_args, tag); + } + else { + char *trimmed = tag_trim(tag, tag + strlen(tag) - 1); + rc = tag_add(bison_args, trimmed); + } + return rc; +} + } @@ -438,7 +542,7 @@ static int design_name_exists(pass_to_bison *bison_args, char *name) } %token YPARENT YSHAPES YELASTIC YPADDING YSAMPLE YENDSAMPLE YBOX YEND YUNREC -%token YREPLACE YREVERSE YTO YWITH YCHGDEL +%token YREPLACE YREVERSE YTO YWITH YCHGDEL YTAGS %token KEYWORD %token WORD %token STRING @@ -575,10 +679,7 @@ alias: WORD yyerror(bison_args, "alias already in use -- %s", $1); YYERROR; } - size_t num_aliases = 0; - while (curdes.aliases[num_aliases] != NULL) { - ++num_aliases; - } + size_t num_aliases = array_count0(curdes.aliases); curdes.aliases = (char **) realloc(curdes.aliases, (num_aliases + 2) * sizeof(char *)); curdes.aliases[num_aliases] = strdup($1); curdes.aliases[num_aliases + 1] = NULL; @@ -670,6 +771,16 @@ layout YEND WORD layout: layout entry | layout block | entry | block ; +tag_entry: STRING + { + if (tag_record(bison_args, $1) != 0) { + YYABORT; + } + } + +tag_list: tag_entry | tag_list ',' tag_entry; + + entry: KEYWORD STRING { #ifdef PARSER_DEBUG @@ -711,9 +822,7 @@ entry: KEYWORD STRING } } else if (strcasecmp ($1, "tags") == 0) { - curdes.tags = (char *) strdup ($2); - if (curdes.tags == NULL) { - perror (PROJECT); + if (tag_record(bison_args, $2) != 0) { YYABORT; } } @@ -772,6 +881,8 @@ entry: KEYWORD STRING chg_strdelims(bison_args, $2[0], $2[1]); } +| YTAGS '(' tag_list ')' | YTAGS tag_entry + | WORD STRING { #ifdef PARSER_DEBUG