aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org>2021-02-11 15:20:00 +0200
committerSergey Poznyakoff <gray@gnu.org>2021-02-11 15:20:00 +0200
commite4cc2baa393e89408a5432e060e77f01a883be57 (patch)
tree7dc5bbf8a0d3b3ac9bd45183e1f5c1b23ef4aca9
parentd62eb95086d2e4669d598fb66cb410840bfa9362 (diff)
downloadxenv-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.112
-rw-r--r--xenv.l114
2 files changed, 102 insertions, 24 deletions
diff --git a/xenv.1 b/xenv.1
index 472e240..a2dad81 100644
--- a/xenv.1
+++ b/xenv.1
@@ -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
diff --git a/xenv.l b/xenv.l
index 4554696..5de0b3b 100644
--- a/xenv.l
+++ b/xenv.l
@@ -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;

Return to:

Send suggestions and report system problems to the System administrator.