trampoline/cmd/rc/parse.c

553 lines
10 KiB
C

#include "rc.h"
#include "io.h"
#include "fns.h"
static tree* body(int tok, int *ptok);
static tree* brace(int tok);
static tree* cmd(int tok, int *ptok);
static tree* cmd2(int tok, int *ptok);
static tree* cmd3(int tok, int *ptok);
static tree* cmds(int tok, int *ptok, int nlok);
static tree* epilog(int tok, int *ptok);
static int iswordtok(int tok);
static tree* line(int tok, int *ptok);
static tree* paren(int tok);
static tree* yyredir(int tok, int *ptok);
static tree* yyword(int tok, int *ptok, int eqok);
static tree* word1(int tok, int *ptok);
static tree* words(int tok, int *ptok);
static jmp_buf yyjmp;
static int
dropnl(int tok)
{
while(tok == ' ' || tok == '\n')
tok = yylex();
return tok;
}
static int
dropsp(int tok)
{
while(tok == ' ')
tok = yylex();
return tok;
}
static void
syntax(int tok)
{
USED(tok);
yyerror("syntax error");
longjmp(yyjmp, 1);
}
int
parse(void)
{
tree *t;
int tok;
if(setjmp(yyjmp))
return 1;
// rc: { return 1;}
// | line '\n' {return !compile($1);}
tok = dropsp(yylex());
if(tok == EOF)
return 1;
t = line(tok, &tok);
if(tok != '\n')
yyerror("missing newline at end of line");
yylval.tree = t;
return !compile(t);
}
static tree*
line(int tok, int *ptok)
{
return cmds(tok, ptok, 0);
}
static tree*
body(int tok, int *ptok)
{
return cmds(tok, ptok, 1);
}
static tree*
cmds(int tok, int *ptok, int nlok)
{
tree *t, **last, *t2;
// line: cmd
// | cmdsa line {$$=tree2(';', $1, $2);}
// cmdsa: cmd ';'
// | cmd '&' {$$=tree1('&', $1);}
// body: cmd
// | cmdsan body {$$=tree2(';', $1, $2);}
// cmdsan: cmdsa
// | cmd '\n'
t = nil;
last = nil;
for(;;) {
t2 = cmd(tok, &tok);
if(tok == '&')
t2 = tree1('&', t2);
if(t2 != nil) {
// slot into list t
if(last == nil) {
t = t2;
last = &t;
} else {
*last = tree2(';', *last, t2);
last = &(*last)->child[1];
}
}
if(tok != ';' && tok != '&' && (!nlok || tok != '\n'))
break;
tok = yylex();
}
*ptok = tok;
return t;
}
static tree*
brace(int tok)
{
tree *t;
// brace: '{' body '}' {$$=tree1(BRACE, $2);}
tok = dropsp(tok);
if(tok != '{')
syntax(tok);
t = body(yylex(), &tok);
if(tok != '}')
syntax(tok);
return tree1(BRACE, t);
}
static tree*
paren(int tok)
{
tree *t;
// paren: '(' body ')' {$$=tree1(PCMD, $2);}
tok = dropsp(tok);
if(tok != '(')
syntax(tok);
t = body(yylex(), &tok);
if(tok != ')')
syntax(tok);
return tree1(PCMD, t);
}
static tree*
epilog(int tok, int *ptok)
{
tree *t, *r;
// epilog: {$$=0;}
// | redir epilog {$$=mung2($1, $1->child[0], $2);}
if(tok != REDIR && tok != DUP) {
*ptok = tok;
return nil;
}
r = yyredir(tok, &tok);
t = epilog(tok, &tok);
*ptok = tok;
return mung2(r, r->child[0], t);
}
static tree*
yyredir(int tok, int *ptok)
{
tree *r, *w;
// redir: REDIR word {$$=mung1($1, $1->rtype==HERE?heredoc($2):$2);}
// | DUP
switch(tok) {
default:
syntax(tok);
case DUP:
r = yylval.tree;
*ptok = dropsp(yylex());
break;
case REDIR:
r = yylval.tree;
w = yyword(yylex(), &tok, 1);
*ptok = dropsp(tok);
r = mung1(r, r->rtype==HERE?heredoc(w):w);
break;
}
return r;
}
static tree*
cmd(int tok, int *ptok)
{
int op;
tree *t1, *t2;
// | cmd ANDAND cmd {$$=tree2(ANDAND, $1, $3);}
// | cmd OROR cmd {$$=tree2(OROR, $1, $3);}
tok = dropsp(tok);
t1 = cmd2(tok, &tok);
while(tok == ANDAND || tok == OROR) {
op = tok;
t2 = cmd2(dropnl(yylex()), &tok);
t1 = tree2(op, t1, t2);
}
*ptok = tok;
return t1;
}
static tree*
cmd2(int tok, int *ptok)
{
tree *t1, *t2, *t3;
// | cmd PIPE cmd {$$=mung2($2, $1, $3);}
t1 = cmd3(tok, &tok);
while(tok == PIPE) {
t2 = yylval.tree;
t3 = cmd3(dropnl(yylex()), &tok);
t1 = mung2(t2, t1, t3);
}
*ptok = tok;
return t1;
}
static tree*
cmd3(int tok, int *ptok)
{
tree *t1, *t2, *t3, *t4;
tok = dropsp(tok);
switch(tok) {
case ';':
case '&':
case '\n':
*ptok = tok;
return nil;
case IF:
// | IF paren {skipnl();} cmd {$$=mung2($1, $2, $4);}
// | IF NOT {skipnl();} cmd {$$=mung1($2, $4);}
t1 = yylval.tree;
tok = dropsp(yylex());
if(tok == NOT) {
t1 = yylval.tree;
t2 = cmd(dropnl(yylex()), ptok);
return mung1(t1, t2);
}
t2 = paren(tok);
t3 = cmd(dropnl(yylex()), ptok);
return mung2(t1, t2, t3);
case FOR:
// | FOR '(' word IN words ')' {skipnl();} cmd
// {$$=mung3($1, $3, $5 ? $5 : tree1(PAREN, $5), $8);}
// | FOR '(' word ')' {skipnl();} cmd
// {$$=mung3($1, $3, (tree *)0, $6);}
t1 = yylval.tree;
tok = dropsp(yylex());
if(tok != '(')
syntax(tok);
t2 = yyword(yylex(), &tok, 1);
switch(tok) {
default:
syntax(tok);
case ')':
t3 = nil;
break;
case IN:
t3 = words(yylex(), &tok);
if(t3 == nil)
t3 = tree1(PAREN, nil);
if(tok != ')')
syntax(tok);
break;
}
t4 = cmd(dropnl(yylex()), ptok);
return mung3(t1, t2, t3, t4);
case WHILE:
// | WHILE paren {skipnl();} cmd
// {$$=mung2($1, $2, $4);}
t1 = yylval.tree;
t2 = paren(yylex());
t3 = cmd(dropnl(yylex()), ptok);
return mung2(t1, t2, t3);
case SWITCH:
// | SWITCH word {skipnl();} brace
// {$$=tree2(SWITCH, $2, $4);}
t1 = yyword(yylex(), &tok, 1);
tok = dropnl(tok); // doesn't work in yacc grammar but works here!
t2 = brace(tok);
*ptok = dropsp(yylex());
return tree2(SWITCH, t1, t2);
// Note: cmd: a && for(x) y && b is a && {for (x) {y && b}}.
return cmd(tok, ptok);
case FN:
// | FN words brace {$$=tree2(FN, $2, $3);}
// | FN words {$$=tree1(FN, $2);}
t1 = words(yylex(), &tok);
if(tok != '{') {
*ptok = tok;
return tree1(FN, t1);
}
t2 = brace(tok);
*ptok = dropsp(yylex());
return tree2(FN, t1, t2);
case TWIDDLE:
// | TWIDDLE word words {$$=mung2($1, $2, $3);}
t1 = yylval.tree;
t2 = yyword(yylex(), &tok, 1);
t3 = words(tok, ptok);
return mung2(t1, t2, t3);
case BANG:
case SUBSHELL:
// | BANG cmd {$$=mung1($1, $2);}
// | SUBSHELL cmd {$$=mung1($1, $2);}
// Note: cmd2: ! x | y is !{x | y} not {!x} | y.
t1 = yylval.tree;
return mung1(t1, cmd2(yylex(), ptok));
case REDIR:
case DUP:
// | redir cmd %prec BANG {$$=mung2($1, $1->child[0], $2);}
// Note: cmd2: {>x echo a | tr a-z A-Z} writes A to x.
t1 = yyredir(tok, &tok);
t2 = cmd2(tok, ptok);
return mung2(t1, t1->child[0], t2);
case '{':
// | brace epilog {$$=epimung($1, $2);}
t1 = brace(tok);
tok = dropsp(yylex());
t2 = epilog(tok, ptok);
return epimung(t1, t2);
}
if(!iswordtok(tok)) {
*ptok = tok;
return nil;
}
// cmd: ...
// | simple {$$=simplemung($1);}
// | assign cmd %prec BANG {$$=mung3($1, $1->child[0], $1->child[1], $2);}
// assign: first '=' word {$$=tree2('=', $1, $3);}
// Note: first is same as word except for disallowing all the leading keywords,
// but all those keywords have been picked off in the switch above.
// Except NOT, but disallowing that in yacc was likely a mistake anyway:
// there's no ambiguity in not=1 or not x y z.
t1 = yyword(tok, &tok, 0);
if(tok == '=') {
// assignment
// Note: cmd2: {x=1 true | echo $x} echoes 1.
t1 = tree2('=', t1, yyword(yylex(), &tok, 1));
t2 = cmd2(tok, ptok);
return mung3(t1, t1->child[0], t1->child[1], t2);
}
// simple: first
// | simple word {$$=tree2(ARGLIST, $1, $2);}
// | simple redir {$$=tree2(ARGLIST, $1, $2);}
for(;;) {
if(tok == REDIR || tok == DUP) {
t1 = tree2(ARGLIST, t1, yyredir(tok, &tok));
} else if(iswordtok(tok)) {
t1 = tree2(ARGLIST, t1, yyword(tok, &tok, 1));
} else {
break;
}
}
*ptok = tok;
return simplemung(t1);
}
static tree*
words(int tok, int *ptok)
{
tree *t;
// words: {$$=(tree*)0;}
// | words word {$$=tree2(WORDS, $1, $2);}
t = nil;
tok = dropsp(tok);
while(iswordtok(tok))
t = tree2(WORDS, t, yyword(tok, &tok, 1));
*ptok = tok;
return t;
}
static tree*
yyword(int tok, int *ptok, int eqok)
{
tree *t;
// word: keyword {lastword=1; $1->type=WORD;}
// | comword
// | word '^' word {$$=tree2('^', $1, $3);}
// comword: '$' word {$$=tree1('$', $2);}
// | '$' word SUB words ')' {$$=tree2(SUB, $2, $4);}
// | '"' word {$$=tree1('"', $2);}
// | COUNT word {$$=tree1(COUNT, $2);}
// | WORD
// | '`' brace {$$=tree1('`', $2);}
// | '(' words ')' {$$=tree1(PAREN, $2);}
// | REDIR brace {$$=mung1($1, $2); $$->type=PIPEFD;}
// keyword: FOR|IN|WHILE|IF|NOT|TWIDDLE|BANG|SUBSHELL|SWITCH|FN
//
// factored into:
//
// word: word1
// | word '^' word1
//
// word1: keyword | comword
t = word1(tok, &tok);
if(tok == '=' && !eqok)
goto out;
for(;;) {
if(iswordtok(tok)) {
// No free carats around parens.
if(t->type == PAREN || tok == '(')
syntax(tok);
t = tree2('^', t, word1(tok, &tok));
continue;
}
tok = dropsp(tok);
if(tok == '^') {
t = tree2('^', t, word1(yylex(), &tok));
continue;
}
break;
}
out:
*ptok = dropsp(tok);
return t;
}
static tree*
word1(int tok, int *ptok)
{
tree *w, *sub, *t;
tok = dropsp(tok);
switch(tok) {
default:
syntax(tok);
case WORD:
case FOR:
case IN:
case WHILE:
case IF:
case NOT:
case TWIDDLE:
case BANG:
case SUBSHELL:
case SWITCH:
case FN:
// | WORD
// keyword: FOR|IN|WHILE|IF|NOT|TWIDDLE|BANG|SUBSHELL|SWITCH|FN
t = yylval.tree;
t->type = WORD;
*ptok = yylex();
return t;
case '=':
*ptok = yylex();
return token("=", WORD);
case '$':
// comword: '$' word1 {$$=tree1('$', $2);}
// | '$' word1 SUB words ')' {$$=tree2(SUB, $2, $4);}
w = word1(yylex(), &tok);
if(tok == '(') {
sub = words(yylex(), &tok);
if(tok != ')')
syntax(tok);
*ptok = yylex();
return tree2(SUB, w, sub);
}
*ptok = tok;
return tree1('$', w);
case '"':
// | '"' word1 {$$=tree1('"', $2);}
return tree1('"', word1(yylex(), ptok));
case COUNT:
// | COUNT word1 {$$=tree1(COUNT, $2);}
return tree1(COUNT, word1(yylex(), ptok));
case '`':
// | '`' brace {$$=tree1('`', $2);}
t = tree1('`', brace(yylex()));
*ptok = yylex();
return t;
case '(':
// | '(' words ')' {$$=tree1(PAREN, $2);}
t = tree1(PAREN, words(yylex(), &tok));
if(tok != ')')
syntax(tok);
*ptok = yylex();
return t;
case REDIRW:
// | REDIRW brace {$$=mung1($1, $2); $$->type=PIPEFD;}
t = yylval.tree;
t = mung1(t, brace(yylex()));
t->type = PIPEFD;
*ptok = yylex();
return t;
}
}
static int
iswordtok(int tok)
{
switch(tok) {
case FOR:
case IN:
case WHILE:
case IF:
case NOT:
case TWIDDLE:
case BANG:
case SUBSHELL:
case SWITCH:
case FN:
case '$':
case '"':
case COUNT:
case WORD:
case '`':
case '(':
case REDIRW:
case '=':
return 1;
}
return 0;
}