>From 7ff05a8dd902f567fbd6a0ae8136b27fd5213a3e Mon Sep 17 00:00:00 2001 From: Brand Huntsman Date: Wed, 17 Jan 2018 01:10:47 -0700 Subject: [PATCH] syntax variables Signed-off-by: Brand Huntsman --- src/nano.h | 9 +++ src/rcfile.c | 217 ++++++++++++++++++++++++++++++++++++++++++++++++++- syntax/nanorc.nanorc | 28 +++++-- 3 files changed, 244 insertions(+), 10 deletions(-) diff --git a/src/nano.h b/src/nano.h index 55abf1e8..f27c6585 100644 --- a/src/nano.h +++ b/src/nano.h @@ -212,6 +212,13 @@ typedef struct regexlisttype { /* The next regex. */ } regexlisttype; +typedef struct definetype { + char *key; + char *value; + int value_length; + struct definetype *next; +} definetype; + typedef struct syntaxtype { char *name; /* The name of this syntax. */ @@ -233,6 +240,8 @@ typedef struct syntaxtype { /* The colors and their regexes used in this syntax. */ int nmultis; /* How many multiline regex strings this syntax has. */ + definetype *define; + /* The defines and their key:value used in this syntax. */ struct syntaxtype *next; /* Next syntax. */ } syntaxtype; diff --git a/src/rcfile.c b/src/rcfile.c index eece5a8e..24cf4b62 100644 --- a/src/rcfile.c +++ b/src/rcfile.c @@ -135,6 +135,10 @@ static syntaxtype *live_syntax; /* The syntax that is currently being parsed. */ static colortype *lastcolor = NULL; /* The end of the color list for the current syntax. */ +static regex_t re_define_key; + /* The compiled regex for define keys. */ +static regex_t re_define_placeholder; + /* The compiled regex for placeholders in interpolated strings. */ #endif /* Report an error in an rcfile, printing it to stderr. */ @@ -316,6 +320,11 @@ void parse_syntax(char *ptr) live_syntax->color = NULL; lastcolor = NULL; live_syntax->nmultis = 0; + live_syntax->define = NULL; + regcomp(&re_define_key, + "^[a-zA-Z_][a-zA-Z0-9_]*$", REG_EXTENDED | REG_NOSUB); + regcomp(&re_define_placeholder, + "^[$][{][a-zA-Z_][a-zA-Z0-9_]*[}]", REG_EXTENDED); /* Hook the new syntax in at the top of the list. */ live_syntax->next = syntaxes; @@ -615,6 +624,162 @@ short color_to_short(const char *colorname, bool *bright) return -1; } +#define MAX_INTERPOLATED_PLACEHOLDERS 32 + +/* Replace up to MAX_INTERPOLATED_PLACEHOLDERS ${key} + * placeholders in string with defined values. */ +char *interpolate_string(char *str) +{ + struct { + char *start; + char *next; + char *replace; + int replace_length; + } matches[MAX_INTERPOLATED_PLACEHOLDERS]; + int n = 0, length = 0; + char *p = str; + regmatch_t match; + while (*p != '\0') { + if (regexec(&re_define_placeholder, p, 1, &match, 0) == 0) { + char * start = p; + p += match.rm_eo; + *(p-1) = '\0'; + + char *key = start + 2; + definetype *define = live_syntax->define; + while (define != NULL) { + if (strcasecmp(define->key, key) == 0) + break; + define = define->next; + } + + if (define == NULL) { + /* Ignore placeholder. */ + rcfile_error(N_("Unknown define key \"%s\""), key); + *(p-1) = '}'; + length += match.rm_eo; + continue; + } + + if (n >= MAX_INTERPOLATED_PLACEHOLDERS) { + /* Ignore placeholder. */ + rcfile_error( + N_("Exceeded limit of %d interpolated placeholders"), + MAX_INTERPOLATED_PLACEHOLDERS); + *(p-1) = '}'; + length += match.rm_eo; + continue; + } + + *start = '\0'; + matches[n].start = start; + matches[n].next = p; + matches[n].replace = define->value; + matches[n].replace_length = define->value_length; + n++; + length += define->value_length; + } else { + p++; + length++; + } + } + + if (n == 0) { + rcfile_error(N_("No placeholders found in interpolated string")); + return mallocstrcpy(NULL, str); + } + + char *result = malloc(length+1); + p = result; + for (int i = 0; i < n; i++) { + /* Copy uninterpolated section to result. */ + strcpy(p, str); + p += matches[i].start - str; + /* Copy interpolated value to result. */ + strcpy(p, matches[i].replace); + p += matches[i].replace_length; + /* Advance to next uninterpolated section. */ + str = matches[i].next; + } + /* Copy final uninterpolated section to result. */ + strcpy(p, str); + + return result; +} + +/* Parse the key:value string in the line at ptr, + * and add it to the current file's associated defines. */ +void parse_define(char *ptr) +{ + char *key, *value; + + if (!opensyntax) { + rcfile_error( + N_("A '%s' command requires a preceding 'syntax' command"), + "define"); + return; + } + + if (*ptr == '\0') { + rcfile_error(N_("Missing key for 'define' command")); + return; + } + + /* Get key. */ + key = ptr; + ptr = parse_next_word(ptr); + + /* Validate key. */ + if (regexec(&re_define_key, key, 0, NULL, 0) != 0) { + rcfile_error(N_("Invalid key \"%s\" for 'define' command"), key); + return; + } + + /* Check for duplicate keys. */ + for (definetype *d = live_syntax->define; d != NULL; d = d->next) { + if (strcasecmp(d->key, key) == 0) { + rcfile_error(N_("Duplicate key \"%s\" for 'define' command"), key); + return; + } + } + + bool interpolate = FALSE; + if (*ptr == '$' && *(ptr+1) == '"') { + ptr++; + interpolate = TRUE; + } + + /* Get value string. */ + if (*ptr != '"') { + rcfile_error(N_("Regex strings must begin and end with a \" character")); + return; + } + + value = ++ptr; + ptr = parse_next_regex(ptr); + if (ptr == NULL) { + rcfile_error(N_("Missing value for 'define' command")); + return; + } + + if (*value == '\0') { + rcfile_error(N_("Empty regex string")); + return; + } + + /* Initialize a new define struct, + * and hook it in at the head of the linked list. */ + definetype *newdefine = (definetype *)nmalloc(sizeof(definetype)); + newdefine->key = mallocstrcpy(NULL, key); + if (interpolate) + newdefine->value = interpolate_string(value); + else + newdefine->value = mallocstrcpy(NULL, value); + newdefine->value_length = strlen(newdefine->value); + newdefine->next = live_syntax->define; + live_syntax->define = newdefine; +} + /* Parse the color string in the line at ptr, and add it to the current * file's associated colors. rex_flags are the regex compilation flags * to use, excluding or including REG_ICASE for case (in)sensitivity. */ @@ -655,12 +820,21 @@ void parse_colors(char *ptr, int rex_flags) /* Whether the start expression was valid. */ bool expectend = FALSE; /* Whether to expect an end= line. */ + bool interpolate = FALSE; + /* Whether to interpolate regex string. */ + char *interpolated_item = NULL; + /* The interpolated regex string. */ if (strncasecmp(ptr, "start=", 6) == 0) { ptr += 6; expectend = TRUE; } + if (*ptr == '$' && *(ptr+1) == '"') { + ptr++; + interpolate = TRUE; + } + if (*ptr != '"') { rcfile_error( N_("Regex strings must begin and end with a \" character")); @@ -676,6 +850,9 @@ void parse_colors(char *ptr, int rex_flags) if (*item == '\0') { rcfile_error(N_("Empty regex string")); goodstart = FALSE; + } else if (interpolate) { + interpolated_item = interpolate_string(item); + goodstart = nregcomp(interpolated_item, rex_flags); } else goodstart = nregcomp(item, rex_flags); @@ -689,7 +866,10 @@ void parse_colors(char *ptr, int rex_flags) newcolor->bright = bright; newcolor->rex_flags = rex_flags; - newcolor->start_regex = mallocstrcpy(NULL, item); + if (interpolated_item != NULL) + newcolor->start_regex = interpolated_item; + else + newcolor->start_regex = mallocstrcpy(NULL, item); newcolor->start = NULL; newcolor->end_regex = NULL; @@ -714,6 +894,14 @@ void parse_colors(char *ptr, int rex_flags) } ptr += 4; + + interpolated_item = NULL; + if (*ptr == '$' && *(ptr+1) != '"') { + ptr++; + interpolate = TRUE; + } else + interpolate = FALSE; + if (*ptr != '"') { rcfile_error(N_("Regex strings must begin and end with a \" character")); continue; @@ -735,8 +923,14 @@ void parse_colors(char *ptr, int rex_flags) continue; /* If it's valid, save the ending regex string. */ - if (nregcomp(item, rex_flags)) - newcolor->end_regex = mallocstrcpy(NULL, item); + if (interpolate) { + char *interpolated_item = interpolate_string(item); + if (nregcomp(interpolated_item, rex_flags)) + newcolor->end_regex = interpolated_item; + } else { + if (nregcomp(item, rex_flags)) + newcolor->end_regex = mallocstrcpy(NULL, item); + } /* Lame way to skip another static counter. */ newcolor->id = live_syntax->nmultis; @@ -997,6 +1191,8 @@ void parse_rcfile(FILE *rcstream, bool syntax_only) #else ; #endif + else if (strcasecmp(keyword, "define") == 0) + parse_define(ptr); else if (strcasecmp(keyword, "color") == 0) parse_colors(ptr, NANO_REG_EXTENDED); else if (strcasecmp(keyword, "icolor") == 0) @@ -1197,6 +1393,21 @@ void parse_rcfile(FILE *rcstream, bool syntax_only) live_syntax->name); opensyntax = FALSE; + + if (live_syntax != NULL) { + /* Free the defines. */ + definetype *d = live_syntax->define; + while (d != NULL) { + definetype *next_define = d->next; + free(d->key); + free(d->value); + free(d); + d = next_define; + } + live_syntax->define = NULL; + regfree(&re_define_key); + regfree(&re_define_placeholder); + } #endif free(buf); diff --git a/syntax/nanorc.nanorc b/syntax/nanorc.nanorc index 9882d6b0..f8e84e03 100644 --- a/syntax/nanorc.nanorc +++ b/syntax/nanorc.nanorc @@ -3,28 +3,42 @@ syntax "nanorc" "\.?nanorc$" comment "#" +define color_names "white|black|red|blue|green|yellow|magenta|cyan" +define color $"(bright)?(${color_names})?(,(${color_names}))?" +define ui_color_opts "(function|key|number|selected|status|title)color" +define key_bindings "((\^([[:alpha:]]|[]0-9\^_]|Space)|M-([[:alpha:]]|[]!"#$%&'()*+,./0-9:;<=>address@hidden|}~-]|Space))|F([1-9]|1[0-6])|Ins|Del)" +define menus "(all|main|search|replace(with)?|gotoline|writeout|insert|ext(ernal)?cmd|help|spell|linter|browser|whereisfile|gotodir)" + +define bind_cmd $"^[[:space:]]*bind[[:space:]]+${key_bindings}[[:space:]]+[[:alpha:]]+[[:space:]]+${menus}" +define unbind_cmd $"^[[:space:]]*unbind[[:space:]]+${key_bindings}[[:space:]]+${menus}" + # Possible errors and parameters icolor brightred "^[[:space:]]*((un)?(bind|set)|include|syntax|header|magic|comment|linter|formatter|i?color|extendsyntax).*$" +# EOL Comments +color brightblue $"(^[[:space:]]*(set|unset).*|${bind_cmd}|${unbind_cmd})[[:space:]]+#.*$" + # Keywords +icolor brightgreen "^[[:space:]]*define[[:space:]]+[a-z_][a-z0-9_]*\>" icolor brightgreen "^[[:space:]]*(set|unset)[[:space:]]+(allow_insecure_backup|atblanks|autoindent|backup|backwards|boldtext|casesensitive|constantshow|cutfromcursor|fill[[:space:]]+-?[[:digit:]]+|historylog|linenumbers|locking|morespace|mouse|multibuffer|noconvert|nohelp|nopauses|nonewlines|nowrap|positionlog|preserve|quickblank|quiet|rebinddelete|rebindkeypad|regexp|showcursor|smarthome|smooth|softwrap|suspend|tabsize[[:space:]]+[1-9][0-9]*|tabstospaces|tempfile|trimblanks|unix|view|wordbounds)\>" -icolor yellow "^[[:space:]]*set[[:space:]]+((function|key|number|selected|status|title)color)[[:space:]]+(bright)?(white|black|red|blue|green|yellow|magenta|cyan)?(,(white|black|red|blue|green|yellow|magenta|cyan))?\>" -icolor brightgreen "^[[:space:]]*set[[:space:]]+(backupdir|brackets|functioncolor|keycolor|matchbrackets|numbercolor|operatingdir|punct|quotestr|selectedcolor|speller|statuscolor|titlecolor|whitespace|wordchars)[[:space:]]+" -icolor brightgreen "^[[:space:]]*bind[[:space:]]+((\^([[:alpha:]]|[]0-9\^_]|Space)|M-([[:alpha:]]|[]!"#$%&'()*+,./0-9:;<=>address@hidden|}~-]|Space))|F([1-9]|1[0-6])|Ins|Del)[[:space:]]+[[:alpha:]]+[[:space:]]+(all|main|search|replace(with)?|gotoline|writeout|insert|ext(ernal)?cmd|help|spell|linter|browser|whereisfile|gotodir)([[:space:]]+#|[[:space:]]*$)" -icolor brightgreen "^[[:space:]]*unbind[[:space:]]+((\^([[:alpha:]]|[]0-9\^_]|Space)|M-([[:alpha:]]|[]!"#$%&'()*+,./0-9:;<=>address@hidden|}~-]|Space))|F([1-9]|1[0-6])|Ins|Del)[[:space:]]+(all|main|search|replace(with)?|gotoline|writeout|insert|ext(ernal)?cmd|help|spell|linter|browser|whereisfile|gotodir)([[:space:]]+#|[[:space:]]*$)" +icolor yellow $"^[[:space:]]*set[[:space:]]+(${ui_color_opts})[[:space:]]+${color}\>" +icolor brightgreen $"^[[:space:]]*set[[:space:]]+(${ui_color_opts}|backupdir|brackets|matchbrackets|operatingdir|punct|quotestr|speller|whitespace|wordchars)[[:space:]]+" +icolor brightgreen $"${bind_cmd}\>" +icolor brightgreen $"${unbind_cmd}\>" icolor brightgreen "^[[:space:]]*extendsyntax[[:space:]]+[[:alpha:]]+[[:space:]]+(i?color|header|magic|comment|linter|formatter)[[:space:]]+.*$" icolor brightgreen "^[[:space:]]*(linter|formatter)[[:space:]]+[[:alpha:]]+" -icolor green "^[[:space:]]*((un)?(bind|set)|include|syntax|header|magic|comment|linter|formatter|extendsyntax)\>" +icolor green "^[[:space:]]*((un)?(bind|set)|include|syntax|header|magic|comment|linter|formatter|extendsyntax|define)\>" # Strings +color brightgreen "[[:space:]][$]"" color brightmagenta "".+"([[:space:]]|$)" # Colors -icolor yellow "^[[:space:]]*i?color[[:space:]]*(bright)?(white|black|red|blue|green|yellow|magenta|cyan)?(,(white|black|red|blue|green|yellow|magenta|cyan))?\>" +icolor yellow $"^[[:space:]]*i?color[[:space:]]*${color}\>" icolor magenta "^[[:space:]]*i?color\>" "\<(start|end)=" # Comments -color brightblue "(^|[[:space:]]+)#.*$" +color brightblue "^[[:space:]]*#.*$" color cyan "^[[:space:]]*##.*$" # Trailing whitespace -- 2.13.6