diff options
author | Sergey Poznyakoff <gray@gnu.org> | 2021-02-11 15:20:00 +0200 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org> | 2021-02-11 15:20:00 +0200 |
commit | e4cc2baa393e89408a5432e060e77f01a883be57 (patch) | |
tree | 7dc5bbf8a0d3b3ac9bd45183e1f5c1b23ef4aca9 | |
parent | d62eb95086d2e4669d598fb66cb410840bfa9362 (diff) | |
download | xenv-e4cc2baa393e89408a5432e060e77f01a883be57.tar.gz xenv-e4cc2baa393e89408a5432e060e77f01a883be57.tar.bz2 |
Lexical indirection operator can be changed at runtime
* xenv.l: New option -S selects the sigil to use instead of the
default '$'.
* xenv.1: Document the -S option.
-rw-r--r-- | xenv.1 | 12 | ||||
-rw-r--r-- | xenv.l | 114 |
2 files changed, 102 insertions, 24 deletions
@@ -13,13 +13,14 @@ .\" .\" You should have received a copy of the GNU General Public License along .\" with xenv. If not, see <http://www.gnu.org/licenses/>. -.TH XENV 1 "February 1, 2021" "XENV" "General Commands Manual" +.TH XENV 1 "February 11, 2021" "XENV" "General Commands Manual" .SH NAME xenv \- expand shell variables in input files .SH SYNOPSIS \fBxenv\fR\ [\fB\-hnrsu\fR]\ [\fB\-D \fINAME\fR[\fB=\fIVALUE\fR]]\ + [\fB\-S \fR[\fB$@%&#\fR]]\ [\fB\-U \fINAME\fR]\ [\fIFILE\fR...] .SH DESCRIPTION @@ -166,6 +167,15 @@ undefined variable expands to an empty string. This option instructs Naturally, this affects only \fB$\fIX\fR and \fB${\fIX\fB}\fR references. .TP +\fB\-S \fICHAR\fR +Changes sigil (a character that starts variable substitution) to +\fICHAR\fR. Allowed characters are: \fI$\fR (the default), +.IR @ , +.IR % , +.IR & , +and +.IR # . +.TP .B \-s Generate synchronization directives, i.e. lines of the form \fB#line \fINUM\fR "\fIFILE\fR"\fR, which mean that the following line @@ -31,11 +31,15 @@ int undef_error_option; /* Treat undefined variables as error. */ int synclines_option; /* Generate `#line NUM "FILE"' lines. */ int retain_unexpanded_option;/* Retain unexpanded constructs in the output. */ int status = 0; /* Exit status */ +int sigil = '$'; +char const sigil_chars[] = "$@%&#"; -static int save_state; /* Saved scanner state. */ static int bracecount = 0; /* Curly brace nesting level. */ static int emit_syncline; +static void push_state(int newstate); +static void pop_state(void); + static char *findenv(char const *ident, int len); void expandenv(char const *ident, int len); @@ -134,7 +138,7 @@ IDENT [a-zA-Z_][a-zA-Z_0-9]* WS [ \t][ \t]* OWS [ \t]* %s SQ DQ -%x COMMENT FALSE +%x COMMENT FALSE SIGIL %% \n+ { ECHO; @@ -148,11 +152,11 @@ OWS [ \t]* } \| { expand_inline_flop(); } -<DQ>\" { BEGIN(save_state); ECHO; } -\" { save_state = YYSTATE; BEGIN(DQ); ECHO; } +<DQ>\" { pop_state(); ECHO; } +\" { push_state(DQ); ECHO; } -<SQ>"'" { BEGIN(save_state); ECHO; } -"'" { save_state = YYSTATE; BEGIN(SQ); ECHO; } +<SQ>"'" { pop_state(); ECHO; } +"'" { push_state(SQ); ECHO; } <INITIAL>^"#line ".*\n { if (parse_line_directive(yytext)) { @@ -160,13 +164,14 @@ OWS [ \t]* lineno++; } } -<INITIAL>"${*" { save_state = YYSTATE; BEGIN(COMMENT); } <COMMENT>[^*\n]* /* eat anything that's not a '*' */ <COMMENT>"*"+[^*}\n]* /* eat up '*'s not followed by '}'s */ <COMMENT>\n lineno++; /* Keep track of line numbers. */ -<COMMENT>"*"+"}" { BEGIN(save_state); emit_syncline = 1; } +<COMMENT>"*"+"}" { pop_state(); emit_syncline = 1; } -"$(" { +<SIGIL>{ +"{*" { pop_state(); push_state(COMMENT); } +"(" { int nesting = 1; int c; while ((c = input()) != 0) { @@ -183,22 +188,26 @@ OWS [ \t]* lineno++; fputc(c, yyout); } + pop_state(); } -\${IDENT} { expandenv(yytext + 1, yyleng - 1); } -\$\{{IDENT}\} { expandenv(yytext + 2, yyleng - 3); } -\$\{{IDENT}:?[-=?+|] { +{IDENT} { expandenv(yytext, yyleng); + pop_state(); } +\{{IDENT}\} { expandenv(yytext + 1, yyleng - 2); + pop_state(); } +\{{IDENT}:?[-=?+|] { int len; char *val; int test_null = 0; int type = yytext[yyleng-1]; int suppress; - if (yytext[yyleng-2] == ':') { + pop_state(); + if (yytext[yyleng-2] == ':') { test_null = 1; - len = yyleng - 4; - } else len = yyleng - 3; - val = findenv(yytext + 2, len); + } else + len = yyleng - 2; + val = findenv(yytext + 1, len); if (val && !(test_null && *val == 0)) { if (type == EXP_TERNARY || type == EXP_ALTER) { suppress = 0; @@ -216,21 +225,35 @@ OWS [ \t]* /* Provide default error message */ if (test_null) fprintf(stderr, "%s:%u: variable %.*s null or not set\n", - filename, lineno, len, yytext + 2); + filename, lineno, len, yytext + 1); else fprintf(stderr, "%s:%u: variable %.*s not set\n", - filename, lineno, len, yytext + 2); + filename, lineno, len, yytext + 1); YY_BREAK; } unput(c); } } bracecount++; - expand_inline_push(type, yytext + 2, len, suppress); + expand_inline_push(type, yytext + 1, len, suppress); } -<INITIAL>{ +. { char s[2]; + s[0] = sigil; + s[1] = yytext[0]; + echo(s, 2); + pop_state(); } +} + +<INITIAL,DQ,SQ>[$@%&#] { + if (yytext[0] == sigil) + push_state(SIGIL); + else + ECHO; + } + +<INITIAL>{ ^{OWS}"$$"{OWS}"unset"{WS}{IDENT}{OWS}\n { int len; char *ident = find_ident(yytext, &len, NULL); @@ -389,11 +412,42 @@ expandenv(char const *ident, int len) filename, lineno, len, ident); status = EX_DATAERR; } - if (retain_unexpanded_option) + if (retain_unexpanded_option) { + char c = sigil; + echo(&c, 1); ECHO; + } } } +#define MAX_STACK 1024 + +static int state_stack[MAX_STACK]; +static int state_tos = -1; + +static void +push_state(int newstate) +{ + if (++state_tos == MAX_STACK) { + fprintf(stderr, "%s:%u: out of state stack space\n", + filename, lineno); + exit(EX_SOFTWARE); + } + state_stack[state_tos] = YYSTATE; + BEGIN(newstate); +} + +static void +pop_state(void) +{ + if (state_tos == -1) { + fprintf(stderr, "%s:%u: out of state popup space\n", + filename, lineno); + exit(EX_SOFTWARE); + } + BEGIN(state_stack[state_tos--]); +} + struct expand_inline { int type; /* Expansion type (one of the EXP_ constants above). */ int suppress; /* Suppress output. */ @@ -409,7 +463,6 @@ struct expand_inline { }; }; -#define MAX_STACK 1024 static struct expand_inline expand_inline_stack[MAX_STACK]; static int expand_inline_tos = -1; @@ -684,6 +737,7 @@ usage(FILE *fp) fprintf(fp, " -U NAME unset environment variable NAME\n"); fprintf(fp, " -n don't produce output, only report errors\n"); fprintf(fp, " -r Retain unexpanded constructs in the output\n"); + fprintf(fp, " -S CHAR change sigil character (allowed chars: %s)\n", sigil_chars); fprintf(fp, " -s generate `#line NUM \"FILE\"' lines\n"); fprintf(fp, " -u treat unset variables as errors\n"); fprintf(fp, " -h print this help text\n"); @@ -698,7 +752,7 @@ main(int argc, char **argv) int dry_run = 0; progname = argv[0]; - while ((c = getopt(argc, argv, "D:hnrsU:u")) != EOF) { + while ((c = getopt(argc, argv, "D:hnrS:sU:u")) != EOF) { switch (c) { case 'D':{ char *p = strchr(optarg, '='); @@ -725,6 +779,20 @@ main(int argc, char **argv) case 'r': retain_unexpanded_option = 1; break; + + case 'S': + if (optarg[1]) { + fprintf(stderr, "%s: sigil too long\n", + progname); + return EX_USAGE; + } + if (!strchr(sigil_chars, optarg[0])) { + fprintf(stderr, "%s: %s is not allowed as a sigil; allowed characters are: %s\n", + progname, optarg, sigil_chars); + return EX_USAGE; + } + sigil = optarg[0]; + break; case 's': synclines_option = 1; |