rc

[fork] interactive rc shell
Log | Files | Refs | README | LICENSE

commit d0ee32e585437b76f095379ec84fd753a5d30a3c
parent d260301ae1c54fb68c0849e1895c0958f55942ef
Author: Bert Münnich <ber.t@posteo.de>
Date:   Sat, 27 Feb 2016 20:52:33 +0100

Parse equals sign as keyword

The parser treats unexpected keyword tokens (eg. if, while, ~) as literals and
not as syntax errors. It does not do this for the equals sign, but it could, as
it only has a special meaning at the beginning of a command, much like the
keywords. Doing this allows one to write commands like this:

    dd if'='/dev/zero of'='data bs'='1024 count'='1

without the quotes around the equals signs. All shells from the Bourne family
do this.

For this to work, the lexer must not automatically terminate words at equals
signs, instead it has to generate a free caret around it like for the twiddle
and other such special characters.

In the parser's grammar we need to get rid of the left recursion in the rules
for 'simple' to turn the reduce/reduce conflict at 'first' on '=' (empty ->
'optcaret' or 'first' -> 'simple') into a shift/reduce conflict, which then
can be solved with precedence/associativity so that the reduction of the
empty 'optcaret' wins over shifting '='.

Diffstat:
Mlex.c | 6+++---
Mparse.y | 16++++++++++++----
Mrc.1 | 15++++++++++-----
3 files changed, 25 insertions(+), 12 deletions(-)

diff --git a/lex.c b/lex.c @@ -178,9 +178,9 @@ top: while ((c = gchar()) == ' ' || c == '\t') y->word.q = FALSE; return WORD; } - if (c == '`' || c == '!' || c == '@' || c == '~' || c == '$' || c == '\'') { + if (c == '`' || c == '!' || c == '@' || c == '~' || c == '$' || c == '\'' || c == '=') { checkfreecaret; - if (c == '!' || c == '@' || c == '~') + if (c == '!' || c == '@' || c == '~' || c == '=') w = KW; } switch (c) { @@ -255,9 +255,9 @@ top: while ((c = gchar()) == ' ' || c == '\t') case ';': case '^': case ')': - case '=': case '{': case '}': w = NW; + case '=': return c; case '&': w = NW; diff --git a/parse.y b/parse.y @@ -16,6 +16,7 @@ Node *parsetree; /* not using yylval because bison declares it as an auto */ %token ANDAND BACKBACK BANG CASE COUNT DUP ELSE END FLAT FN FOR IF IN %token OROR PIPE REDIR SREDIR SUB SUBSHELL SWITCH TWIDDLE WHILE WORD HUH +%left '^' '=' %left WHILE ')' ELSE %left ANDAND OROR '\n' %left BANG SUBSHELL @@ -41,6 +42,7 @@ Node *parsetree; /* not using yylval because bison declares it as an auto */ %type <keyword> keyword %type <node> assign body brace case cbody cmd cmdsa cmdsan comword epilog first line nlwords paren redir sword simple iftail word words + arg args %start rc @@ -72,7 +74,7 @@ brace : '{' body '}' { $$ = $2; } paren : '(' body ')' { $$ = $2; } -assign : first '=' word { $$ = mk(nAssign,$1,$3); } +assign : first optcaret '=' optcaret word { $$ = mk(nAssign,$1,$5); } epilog : { $$ = NULL; } | redir epilog { $$ = mk(nEpilog,$1,$2); } @@ -115,12 +117,17 @@ cmd : /* empty */ %prec WHILE { $$ = NULL; } | FN words brace { $$ = mk(nNewfn,$2,$3); } | FN words { $$ = mk(nRmfn,$2); } -optcaret : /* empty */ +optcaret : /* empty */ %prec '^' | '^' simple : first - | simple word { $$ = ($2 != NULL ? mk(nArgs,$1,$2) : $1); } - | simple redir { $$ = mk(nArgs,$1,$2); } + | first args { $$ = ($2 != NULL ? mk(nArgs,$1,$2) : $1); } + +args : arg + | args arg { $$ = ($2 != NULL ? mk(nArgs,$1,$2) : $1); } + +arg : word + | redir first : comword | first '^' sword { $$ = mk(nConcat,$1,$3); } @@ -154,6 +161,7 @@ keyword : FOR { $$ = "for"; } | TWIDDLE { $$ = "~"; } | BANG { $$ = "!"; } | SUBSHELL { $$ = "@"; } + | '=' { $$ = "="; } words : { $$ = NULL; } | words word { $$ = ($1 != NULL ? ($2 != NULL ? mk(nLappend,$1,$2) : $1) : $2); } diff --git a/rc.1 b/rc.1 @@ -395,7 +395,7 @@ interprets several characters specially; special characters automatically terminate words. The following characters are special: .Ds -.Cr "# ; & | ^ $ = \` ' { } ( ) < >" +.Cr "# ; & | ^ $ \` ' { } ( ) < >" .De .PP The single quote @@ -1936,6 +1936,7 @@ grammar, edited to remove semantic actions. %term ANDAND BACKBACK BANG CASE COUNT DUP ELSE END FLAT FN FOR IF IN %term OROR PIPE REDIR SUB SUBSHELL SWITCH TWIDDLE WHILE WORD HUH +%left '^' '=' %left WHILE ')' ELSE %left ANDAND OROR '\en' %left BANG SUBSHELL @@ -1964,7 +1965,7 @@ brace: '{' body '}' paren: '(' body ')' -assign: first '=' word +assign: first optcaret '=' optcaret word epilog: /* empty */ | redir epilog @@ -1996,9 +1997,13 @@ cmd : /* empty */ %prec WHILE | FN words brace | FN words -optcaret: /* empty */ | '^' +optcaret: /* empty */ %prec '^' | '^' -simple: first | simple word | simple redir +simple: first | first args + +args: arg | args arg + +arg: word | redir first: comword | first '^' sword @@ -2018,7 +2023,7 @@ comword: '$' sword | WORD keyword: FOR | IN | WHILE | IF | SWITCH - | FN | ELSE | CASE | TWIDDLE | BANG | SUBSHELL + | FN | ELSE | CASE | TWIDDLE | BANG | SUBSHELL | '=' words: /* empty */ | words word