Add mirbsdksh R21, which was developed in a temporary external CVS repo-
sitory whose ChangeLog follows. mksh R21 is licenced under the MirOS li- cence, shown in "sh.h", and a two-clause UCB-style licence by Marc Espie as shown in "alloc.c". This executable is a fair bit smaller and shorter than our /bin/ksh that it is designed to eventually replace (as /bin/sh hardlink), with the old /bin/ksh to completely vanish. It is still in beta testing though, and I don't think it will compile on other operating systems. mksh R21 is a completely new port, bringing together the OpenBSD-current /bin/ksh, the MirOS-current /bin/ksh and the older mksh R20 (which still was portable, ocvs-based).
This commit is contained in:
parent
d8d708aa45
commit
cd7b8bd79b
14
Makefile
Normal file
14
Makefile
Normal file
@ -0,0 +1,14 @@
|
||||
# $MirOS: src/bin/mksh/Makefile,v 1.1 2005/05/23 03:06:05 tg Exp $
|
||||
|
||||
PROG= mksh
|
||||
SRCS= alloc.c edit.c eval.c exec.c expr.c funcs.c histrap.c \
|
||||
jobs.c lex.c main.c misc.c shf.c syn.c tree.c var.c
|
||||
|
||||
check:
|
||||
@cd ${.CURDIR} && ${MAKE} regress V=-v
|
||||
|
||||
regress: ${PROG} check.pl check.t
|
||||
perl ${.CURDIR}/check.pl -s ${.CURDIR}/check.t ${V} \
|
||||
-p ./${PROG} -C pdksh,sh,ksh,posix,posix-upu
|
||||
|
||||
.include <bsd.prog.mk>
|
124
alloc.c
Normal file
124
alloc.c
Normal file
@ -0,0 +1,124 @@
|
||||
/** $MirOS: src/bin/mksh/alloc.c,v 1.1 2005/05/23 03:06:05 tg Exp $ */
|
||||
/* $OpenBSD: alloc.c,v 1.7 2004/02/19 18:51:17 deraadt Exp $ */
|
||||
|
||||
/*-
|
||||
* Copyright (c) 2002 Marc Espie.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE OPENBSD PROJECT AND CONTRIBUTORS
|
||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OPENBSD
|
||||
* PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* area-based allocation built on malloc/free
|
||||
*/
|
||||
|
||||
#include "sh.h"
|
||||
|
||||
__RCSID("$MirOS: src/bin/mksh/alloc.c,v 1.1 2005/05/23 03:06:05 tg Exp $");
|
||||
|
||||
struct link {
|
||||
struct link *prev;
|
||||
struct link *next;
|
||||
};
|
||||
|
||||
Area *
|
||||
ainit(Area *ap)
|
||||
{
|
||||
ap->freelist = NULL;
|
||||
return ap;
|
||||
}
|
||||
|
||||
void
|
||||
afreeall(Area *ap)
|
||||
{
|
||||
struct link *l, *l2;
|
||||
|
||||
for (l = ap->freelist; l != NULL; l = l2) {
|
||||
l2 = l->next;
|
||||
free(l);
|
||||
}
|
||||
ap->freelist = NULL;
|
||||
}
|
||||
|
||||
#define L2P(l) ( (void *)(((char *)(l)) + sizeof(struct link)) )
|
||||
#define P2L(p) ( (struct link *)(((char *)(p)) - sizeof(struct link)) )
|
||||
|
||||
void *
|
||||
alloc(size_t size, Area *ap)
|
||||
{
|
||||
struct link *l;
|
||||
|
||||
l = malloc(sizeof(struct link) + size);
|
||||
if (l == NULL)
|
||||
internal_errorf(1, "unable to allocate memory");
|
||||
l->next = ap->freelist;
|
||||
l->prev = NULL;
|
||||
if (ap->freelist)
|
||||
ap->freelist->prev = l;
|
||||
ap->freelist = l;
|
||||
|
||||
return L2P(l);
|
||||
}
|
||||
|
||||
void *
|
||||
aresize(void *ptr, size_t size, Area *ap)
|
||||
{
|
||||
struct link *l, *l2, *lprev, *lnext;
|
||||
|
||||
if (ptr == NULL)
|
||||
return alloc(size, ap);
|
||||
|
||||
l = P2L(ptr);
|
||||
lprev = l->prev;
|
||||
lnext = l->next;
|
||||
|
||||
l2 = realloc(l, sizeof(struct link) + size);
|
||||
if (l2 == NULL)
|
||||
internal_errorf(1, "unable to allocate memory");
|
||||
if (lprev)
|
||||
lprev->next = l2;
|
||||
else
|
||||
ap->freelist = l2;
|
||||
if (lnext)
|
||||
lnext->prev = l2;
|
||||
|
||||
return L2P(l2);
|
||||
}
|
||||
|
||||
void
|
||||
afree(void *ptr, Area *ap)
|
||||
{
|
||||
struct link *l;
|
||||
|
||||
if (!ptr)
|
||||
return;
|
||||
|
||||
l = P2L(ptr);
|
||||
|
||||
if (l->prev)
|
||||
l->prev->next = l->next;
|
||||
else
|
||||
ap->freelist = l->next;
|
||||
if (l->next)
|
||||
l->next->prev = l->prev;
|
||||
|
||||
free(l);
|
||||
}
|
579
expr.c
Normal file
579
expr.c
Normal file
@ -0,0 +1,579 @@
|
||||
/** $MirOS: src/bin/mksh/expr.c,v 1.1 2005/05/23 03:06:07 tg Exp $ */
|
||||
/* $OpenBSD: expr.c,v 1.18 2005/03/30 17:16:37 deraadt Exp $ */
|
||||
|
||||
#include "sh.h"
|
||||
#include <ctype.h>
|
||||
|
||||
__RCSID("$MirOS: src/bin/mksh/expr.c,v 1.1 2005/05/23 03:06:07 tg Exp $");
|
||||
|
||||
/* The order of these enums is constrained by the order of opinfo[] */
|
||||
enum token {
|
||||
/* some (long) unary operators */
|
||||
O_PLUSPLUS = 0, O_MINUSMINUS,
|
||||
/* binary operators */
|
||||
O_EQ, O_NE,
|
||||
/* assignments are assumed to be in range O_ASN .. O_BORASN */
|
||||
O_ASN, O_TIMESASN, O_DIVASN, O_MODASN, O_PLUSASN, O_MINUSASN,
|
||||
O_LSHIFTASN, O_RSHIFTASN, O_BANDASN, O_BXORASN, O_BORASN,
|
||||
O_LSHIFT, O_RSHIFT,
|
||||
O_LE, O_GE, O_LT, O_GT,
|
||||
O_LAND,
|
||||
O_LOR,
|
||||
O_TIMES, O_DIV, O_MOD,
|
||||
O_PLUS, O_MINUS,
|
||||
O_BAND,
|
||||
O_BXOR,
|
||||
O_BOR,
|
||||
O_TERN,
|
||||
O_COMMA,
|
||||
/* things after this aren't used as binary operators */
|
||||
/* unary that are not also binaries */
|
||||
O_BNOT, O_LNOT,
|
||||
/* misc */
|
||||
OPEN_PAREN, CLOSE_PAREN, CTERN,
|
||||
/* things that don't appear in the opinfo[] table */
|
||||
VAR, LIT, END, BAD
|
||||
};
|
||||
#define IS_BINOP(op) (((int)op) >= (int)O_EQ && ((int)op) <= (int)O_COMMA)
|
||||
#define IS_ASSIGNOP(op) ((int)(op) >= (int)O_ASN && (int)(op) <= (int)O_BORASN)
|
||||
|
||||
enum prec {
|
||||
P_PRIMARY = 0, /* VAR, LIT, (), ~ ! - + */
|
||||
P_MULT, /* * / % */
|
||||
P_ADD, /* + - */
|
||||
P_SHIFT, /* << >> */
|
||||
P_RELATION, /* < <= > >= */
|
||||
P_EQUALITY, /* == != */
|
||||
P_BAND, /* & */
|
||||
P_BXOR, /* ^ */
|
||||
P_BOR, /* | */
|
||||
P_LAND, /* && */
|
||||
P_LOR, /* || */
|
||||
P_TERN, /* ?: */
|
||||
P_ASSIGN, /* = *= /= %= += -= <<= >>= &= ^= |= */
|
||||
P_COMMA /* , */
|
||||
};
|
||||
#define MAX_PREC P_COMMA
|
||||
|
||||
struct opinfo {
|
||||
char name[4];
|
||||
int len; /* name length */
|
||||
enum prec prec; /* precedence: lower is higher */
|
||||
};
|
||||
|
||||
/* Tokens in this table must be ordered so the longest are first
|
||||
* (eg, += before +). If you change something, change the order
|
||||
* of enum token too.
|
||||
*/
|
||||
static const struct opinfo opinfo[] = {
|
||||
{ "++", 2, P_PRIMARY }, /* before + */
|
||||
{ "--", 2, P_PRIMARY }, /* before - */
|
||||
{ "==", 2, P_EQUALITY }, /* before = */
|
||||
{ "!=", 2, P_EQUALITY }, /* before ! */
|
||||
{ "=", 1, P_ASSIGN }, /* keep assigns in a block */
|
||||
{ "*=", 2, P_ASSIGN },
|
||||
{ "/=", 2, P_ASSIGN },
|
||||
{ "%=", 2, P_ASSIGN },
|
||||
{ "+=", 2, P_ASSIGN },
|
||||
{ "-=", 2, P_ASSIGN },
|
||||
{ "<<=", 3, P_ASSIGN },
|
||||
{ ">>=", 3, P_ASSIGN },
|
||||
{ "&=", 2, P_ASSIGN },
|
||||
{ "^=", 2, P_ASSIGN },
|
||||
{ "|=", 2, P_ASSIGN },
|
||||
{ "<<", 2, P_SHIFT },
|
||||
{ ">>", 2, P_SHIFT },
|
||||
{ "<=", 2, P_RELATION },
|
||||
{ ">=", 2, P_RELATION },
|
||||
{ "<", 1, P_RELATION },
|
||||
{ ">", 1, P_RELATION },
|
||||
{ "&&", 2, P_LAND },
|
||||
{ "||", 2, P_LOR },
|
||||
{ "*", 1, P_MULT },
|
||||
{ "/", 1, P_MULT },
|
||||
{ "%", 1, P_MULT },
|
||||
{ "+", 1, P_ADD },
|
||||
{ "-", 1, P_ADD },
|
||||
{ "&", 1, P_BAND },
|
||||
{ "^", 1, P_BXOR },
|
||||
{ "|", 1, P_BOR },
|
||||
{ "?", 1, P_TERN },
|
||||
{ ",", 1, P_COMMA },
|
||||
{ "~", 1, P_PRIMARY },
|
||||
{ "!", 1, P_PRIMARY },
|
||||
{ "(", 1, P_PRIMARY },
|
||||
{ ")", 1, P_PRIMARY },
|
||||
{ ":", 1, P_PRIMARY },
|
||||
{ "", 0, P_PRIMARY } /* end of table */
|
||||
};
|
||||
|
||||
|
||||
typedef struct expr_state Expr_state;
|
||||
struct expr_state {
|
||||
const char *expression; /* expression being evaluated */
|
||||
const char *tokp; /* lexical position */
|
||||
enum token tok; /* token from token() */
|
||||
int noassign; /* don't do assigns (for ?:,&&,||) */
|
||||
bool arith; /* true if evaluating an $(())
|
||||
* expression
|
||||
*/
|
||||
struct tbl *val; /* value from token() */
|
||||
struct tbl *evaling; /* variable that is being recursively
|
||||
* expanded (EXPRINEVAL flag set)
|
||||
*/
|
||||
};
|
||||
|
||||
enum error_type {
|
||||
ET_UNEXPECTED, ET_BADLIT, ET_RECURSIVE,
|
||||
ET_LVALUE, ET_RDONLY, ET_STR
|
||||
};
|
||||
|
||||
static void evalerr(Expr_state *, enum error_type, const char *)
|
||||
__attribute__((__noreturn__));
|
||||
static struct tbl *evalexpr(Expr_state *, enum prec);
|
||||
static void token(Expr_state *);
|
||||
static struct tbl *do_ppmm(Expr_state *, enum token, struct tbl *, bool);
|
||||
static void assign_check(Expr_state *, enum token, struct tbl *);
|
||||
static struct tbl *tempvar(void);
|
||||
static struct tbl *intvar(Expr_state *, struct tbl *);
|
||||
|
||||
/*
|
||||
* parse and evaluate expression
|
||||
*/
|
||||
int
|
||||
evaluate(const char *expr, long int *rval, int error_ok, bool arith)
|
||||
{
|
||||
struct tbl v;
|
||||
int ret;
|
||||
|
||||
v.flag = DEFINED|INTEGER;
|
||||
v.type = 0;
|
||||
ret = v_evaluate(&v, expr, error_ok, arith);
|
||||
*rval = v.val.i;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* parse and evaluate expression, storing result in vp.
|
||||
*/
|
||||
int
|
||||
v_evaluate(struct tbl *vp, const char *expr, volatile int error_ok,
|
||||
bool arith)
|
||||
{
|
||||
struct tbl *v;
|
||||
Expr_state curstate;
|
||||
Expr_state * const es = &curstate;
|
||||
int i;
|
||||
|
||||
/* save state to allow recursive calls */
|
||||
curstate.expression = curstate.tokp = expr;
|
||||
curstate.noassign = 0;
|
||||
curstate.arith = arith;
|
||||
curstate.evaling = NULL;
|
||||
|
||||
newenv(E_ERRH);
|
||||
i = sigsetjmp(e->jbuf, 0);
|
||||
if (i) {
|
||||
/* Clear EXPRINEVAL in of any variables we were playing with */
|
||||
if (curstate.evaling)
|
||||
curstate.evaling->flag &= ~EXPRINEVAL;
|
||||
quitenv(NULL);
|
||||
if (i == LAEXPR) {
|
||||
if (error_ok == KSH_RETURN_ERROR)
|
||||
return 0;
|
||||
errorf(null);
|
||||
}
|
||||
unwind(i);
|
||||
/*NOTREACHED*/
|
||||
}
|
||||
|
||||
token(es);
|
||||
if (es->tok == END) {
|
||||
es->tok = LIT;
|
||||
es->val = tempvar();
|
||||
}
|
||||
v = intvar(es, evalexpr(es, MAX_PREC));
|
||||
|
||||
if (es->tok != END)
|
||||
evalerr(es, ET_UNEXPECTED, NULL);
|
||||
|
||||
if (vp->flag & INTEGER)
|
||||
setint_v(vp, v, es->arith);
|
||||
else
|
||||
/* can fail if readonly */
|
||||
setstr(vp, str_val(v), error_ok);
|
||||
|
||||
quitenv(NULL);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
evalerr(Expr_state *es, enum error_type type, const char *str)
|
||||
{
|
||||
char tbuf[2];
|
||||
const char *s;
|
||||
|
||||
es->arith = false;
|
||||
switch (type) {
|
||||
case ET_UNEXPECTED:
|
||||
switch (es->tok) {
|
||||
case VAR:
|
||||
s = es->val->name;
|
||||
break;
|
||||
case LIT:
|
||||
s = str_val(es->val);
|
||||
break;
|
||||
case END:
|
||||
s = "end of expression";
|
||||
break;
|
||||
case BAD:
|
||||
tbuf[0] = *es->tokp;
|
||||
tbuf[1] = '\0';
|
||||
s = tbuf;
|
||||
break;
|
||||
default:
|
||||
s = opinfo[(int)es->tok].name;
|
||||
}
|
||||
warningf(true, "%s: unexpected '%s'", es->expression, s);
|
||||
break;
|
||||
|
||||
case ET_BADLIT:
|
||||
warningf(true, "%s: bad number '%s'", es->expression, str);
|
||||
break;
|
||||
|
||||
case ET_RECURSIVE:
|
||||
warningf(true, "%s: expression recurses on parameter '%s'",
|
||||
es->expression, str);
|
||||
break;
|
||||
|
||||
case ET_LVALUE:
|
||||
warningf(true, "%s: %s requires lvalue",
|
||||
es->expression, str);
|
||||
break;
|
||||
|
||||
case ET_RDONLY:
|
||||
warningf(true, "%s: %s applied to read only variable",
|
||||
es->expression, str);
|
||||
break;
|
||||
|
||||
default: /* keep gcc happy */
|
||||
case ET_STR:
|
||||
warningf(true, "%s: %s", es->expression, str);
|
||||
break;
|
||||
}
|
||||
unwind(LAEXPR);
|
||||
}
|
||||
|
||||
static struct tbl *
|
||||
evalexpr(Expr_state *es, enum prec prec)
|
||||
{
|
||||
struct tbl *vl, *vr = NULL, *vasn;
|
||||
enum token op;
|
||||
long res = 0;
|
||||
|
||||
if (prec == P_PRIMARY) {
|
||||
op = es->tok;
|
||||
if (op == O_BNOT || op == O_LNOT || op == O_MINUS ||
|
||||
op == O_PLUS) {
|
||||
token(es);
|
||||
vl = intvar(es, evalexpr(es, P_PRIMARY));
|
||||
if (op == O_BNOT)
|
||||
vl->val.i = ~vl->val.i;
|
||||
else if (op == O_LNOT)
|
||||
vl->val.i = !vl->val.i;
|
||||
else if (op == O_MINUS)
|
||||
vl->val.i = -vl->val.i;
|
||||
/* op == O_PLUS is a no-op */
|
||||
} else if (op == OPEN_PAREN) {
|
||||
token(es);
|
||||
vl = evalexpr(es, MAX_PREC);
|
||||
if (es->tok != CLOSE_PAREN)
|
||||
evalerr(es, ET_STR, "missing )");
|
||||
token(es);
|
||||
} else if (op == O_PLUSPLUS || op == O_MINUSMINUS) {
|
||||
token(es);
|
||||
vl = do_ppmm(es, op, es->val, true);
|
||||
token(es);
|
||||
} else if (op == VAR || op == LIT) {
|
||||
vl = es->val;
|
||||
token(es);
|
||||
} else {
|
||||
evalerr(es, ET_UNEXPECTED, NULL);
|
||||
/*NOTREACHED*/
|
||||
}
|
||||
if (es->tok == O_PLUSPLUS || es->tok == O_MINUSMINUS) {
|
||||
vl = do_ppmm(es, es->tok, vl, false);
|
||||
token(es);
|
||||
}
|
||||
return vl;
|
||||
}
|
||||
vl = evalexpr(es, ((int) prec) - 1);
|
||||
for (op = es->tok; IS_BINOP(op) && opinfo[(int) op].prec == prec;
|
||||
op = es->tok) {
|
||||
token(es);
|
||||
vasn = vl;
|
||||
if (op != O_ASN) /* vl may not have a value yet */
|
||||
vl = intvar(es, vl);
|
||||
if (IS_ASSIGNOP(op)) {
|
||||
assign_check(es, op, vasn);
|
||||
vr = intvar(es, evalexpr(es, P_ASSIGN));
|
||||
} else if (op != O_TERN && op != O_LAND && op != O_LOR)
|
||||
vr = intvar(es, evalexpr(es, ((int) prec) - 1));
|
||||
if ((op == O_DIV || op == O_MOD || op == O_DIVASN ||
|
||||
op == O_MODASN) && vr->val.i == 0) {
|
||||
if (es->noassign)
|
||||
vr->val.i = 1;
|
||||
else
|
||||
evalerr(es, ET_STR, "zero divisor");
|
||||
}
|
||||
switch ((int) op) {
|
||||
case O_TIMES:
|
||||
case O_TIMESASN:
|
||||
res = vl->val.i * vr->val.i;
|
||||
break;
|
||||
case O_DIV:
|
||||
case O_DIVASN:
|
||||
res = vl->val.i / vr->val.i;
|
||||
break;
|
||||
case O_MOD:
|
||||
case O_MODASN:
|
||||
res = vl->val.i % vr->val.i;
|
||||
break;
|
||||
case O_PLUS:
|
||||
case O_PLUSASN:
|
||||
res = vl->val.i + vr->val.i;
|
||||
break;
|
||||
case O_MINUS:
|
||||
case O_MINUSASN:
|
||||
res = vl->val.i - vr->val.i;
|
||||
break;
|
||||
case O_LSHIFT:
|
||||
case O_LSHIFTASN:
|
||||
res = vl->val.i << vr->val.i;
|
||||
break;
|
||||
case O_RSHIFT:
|
||||
case O_RSHIFTASN:
|
||||
res = vl->val.i >> vr->val.i;
|
||||
break;
|
||||
case O_LT:
|
||||
res = vl->val.i < vr->val.i;
|
||||
break;
|
||||
case O_LE:
|
||||
res = vl->val.i <= vr->val.i;
|
||||
break;
|
||||
case O_GT:
|
||||
res = vl->val.i > vr->val.i;
|
||||
break;
|
||||
case O_GE:
|
||||
res = vl->val.i >= vr->val.i;
|
||||
break;
|
||||
case O_EQ:
|
||||
res = vl->val.i == vr->val.i;
|
||||
break;
|
||||
case O_NE:
|
||||
res = vl->val.i != vr->val.i;
|
||||
break;
|
||||
case O_BAND:
|
||||
case O_BANDASN:
|
||||
res = vl->val.i & vr->val.i;
|
||||
break;
|
||||
case O_BXOR:
|
||||
case O_BXORASN:
|
||||
res = vl->val.i ^ vr->val.i;
|
||||
break;
|
||||
case O_BOR:
|
||||
case O_BORASN:
|
||||
res = vl->val.i | vr->val.i;
|
||||
break;
|
||||
case O_LAND:
|
||||
if (!vl->val.i)
|
||||
es->noassign++;
|
||||
vr = intvar(es, evalexpr(es, ((int) prec) - 1));
|
||||
res = vl->val.i && vr->val.i;
|
||||
if (!vl->val.i)
|
||||
es->noassign--;
|
||||
break;
|
||||
case O_LOR:
|
||||
if (vl->val.i)
|
||||
es->noassign++;
|
||||
vr = intvar(es, evalexpr(es, ((int) prec) - 1));
|
||||
res = vl->val.i || vr->val.i;
|
||||
if (vl->val.i)
|
||||
es->noassign--;
|
||||
break;
|
||||
case O_TERN:
|
||||
{
|
||||
int ev = vl->val.i != 0;
|
||||
|
||||
if (!ev)
|
||||
es->noassign++;
|
||||
vl = evalexpr(es, MAX_PREC);
|
||||
if (!ev)
|
||||
es->noassign--;
|
||||
if (es->tok != CTERN)
|
||||
evalerr(es, ET_STR, "missing :");
|
||||
token(es);
|
||||
if (ev)
|
||||
es->noassign++;
|
||||
vr = evalexpr(es, P_TERN);
|
||||
if (ev)
|
||||
es->noassign--;
|
||||
vl = ev ? vl : vr;
|
||||
}
|
||||
break;
|
||||
case O_ASN:
|
||||
res = vr->val.i;
|
||||
break;
|
||||
case O_COMMA:
|
||||
res = vr->val.i;
|
||||
break;
|
||||
}
|
||||
if (IS_ASSIGNOP(op)) {
|
||||
vr->val.i = res;
|
||||
if (vasn->flag & INTEGER)
|
||||
setint_v(vasn, vr, es->arith);
|
||||
else
|
||||
setint(vasn, res);
|
||||
vl = vr;
|
||||
} else if (op != O_TERN)
|
||||
vl->val.i = res;
|
||||
}
|
||||
return vl;
|
||||
}
|
||||
|
||||
static void
|
||||
token(Expr_state *es)
|
||||
{
|
||||
const char *cp;
|
||||
int c;
|
||||
char *tvar;
|
||||
|
||||
/* skip white space */
|
||||
for (cp = es->tokp; (c = *cp), isspace(c); cp++)
|
||||
;
|
||||
es->tokp = cp;
|
||||
|
||||
if (c == '\0')
|
||||
es->tok = END;
|
||||
else if (letter(c)) {
|
||||
for (; letnum(c); c = *cp)
|
||||
cp++;
|
||||
if (c == '[') {
|
||||
int len;
|
||||
|
||||
len = array_ref_len(cp);
|
||||
if (len == 0)
|
||||
evalerr(es, ET_STR, "missing ]");
|
||||
cp += len;
|
||||
} else if (c == '(' /*)*/ ) {
|
||||
/* todo: add math functions (all take single argument):
|
||||
* abs acos asin atan cos cosh exp int log sin sinh sqrt
|
||||
* tan tanh
|
||||
*/
|
||||
;
|
||||
}
|
||||
if (es->noassign) {
|
||||
es->val = tempvar();
|
||||
es->val->flag |= EXPRLVALUE;
|
||||
} else {
|
||||
tvar = str_nsave(es->tokp, cp - es->tokp, ATEMP);
|
||||
es->val = global(tvar);
|
||||
afree(tvar, ATEMP);
|
||||
}
|
||||
es->tok = VAR;
|
||||
} else if (digit(c)) {
|
||||
for (; c != '_' && (letnum(c) || c == '#'); c = *cp++)
|
||||
;
|
||||
tvar = str_nsave(es->tokp, --cp - es->tokp, ATEMP);
|
||||
es->val = tempvar();
|
||||
es->val->flag &= ~INTEGER;
|
||||
es->val->type = 0;
|
||||
es->val->val.s = tvar;
|
||||
if (setint_v(es->val, es->val, es->arith) == NULL)
|
||||
evalerr(es, ET_BADLIT, tvar);
|
||||
afree(tvar, ATEMP);
|
||||
es->tok = LIT;
|
||||
} else {
|
||||
int i, n0;
|
||||
|
||||
for (i = 0; (n0 = opinfo[i].name[0]); i++)
|
||||
if (c == n0 &&
|
||||
strncmp(cp, opinfo[i].name, opinfo[i].len) == 0) {
|
||||
es->tok = (enum token) i;
|
||||
cp += opinfo[i].len;
|
||||
break;
|
||||
}
|
||||
if (!n0)
|
||||
es->tok = BAD;
|
||||
}
|
||||
es->tokp = cp;
|
||||
}
|
||||
|
||||
/* Do a ++ or -- operation */
|
||||
static struct tbl *
|
||||
do_ppmm(Expr_state *es, enum token op, struct tbl *vasn, bool is_prefix)
|
||||
{
|
||||
struct tbl *vl;
|
||||
int oval;
|
||||
|
||||
assign_check(es, op, vasn);
|
||||
|
||||
vl = intvar(es, vasn);
|
||||
oval = op == O_PLUSPLUS ? vl->val.i++ : vl->val.i--;
|
||||
if (vasn->flag & INTEGER)
|
||||
setint_v(vasn, vl, es->arith);
|
||||
else
|
||||
setint(vasn, vl->val.i);
|
||||
if (!is_prefix) /* undo the inc/dec */
|
||||
vl->val.i = oval;
|
||||
|
||||
return vl;
|
||||
}
|
||||
|
||||
static void
|
||||
assign_check(Expr_state *es, enum token op, struct tbl *vasn)
|
||||
{
|
||||
if (vasn->name[0] == '\0' && !(vasn->flag & EXPRLVALUE))
|
||||
evalerr(es, ET_LVALUE, opinfo[(int) op].name);
|
||||
else if (vasn->flag & RDONLY)
|
||||
evalerr(es, ET_RDONLY, opinfo[(int) op].name);
|
||||
}
|
||||
|
||||
static struct tbl *
|
||||
tempvar(void)
|
||||
{
|
||||
struct tbl *vp;
|
||||
|
||||
vp = (struct tbl*) alloc(sizeof(struct tbl), ATEMP);
|
||||
vp->flag = ISSET|INTEGER;
|
||||
vp->type = 0;
|
||||
vp->areap = ATEMP;
|
||||
vp->val.i = 0;
|
||||
vp->name[0] = '\0';
|
||||
return vp;
|
||||
}
|
||||
|
||||
/* cast (string) variable to temporary integer variable */
|
||||
static struct tbl *
|
||||
intvar(Expr_state *es, struct tbl *vp)
|
||||
{
|
||||
struct tbl *vq;
|
||||
|
||||
/* try to avoid replacing a temp var with another temp var */
|
||||
if (vp->name[0] == '\0' &&
|
||||
(vp->flag & (ISSET|INTEGER|EXPRLVALUE)) == (ISSET|INTEGER))
|
||||
return vp;
|
||||
|
||||
vq = tempvar();
|
||||
if (setint_v(vq, vp, es->arith) == NULL) {
|
||||
if (vp->flag & EXPRINEVAL)
|
||||
evalerr(es, ET_RECURSIVE, vp->name);
|
||||
es->evaling = vp;
|
||||
vp->flag |= EXPRINEVAL;
|
||||
v_evaluate(vq, str_val(vp), KSH_UNWIND_ERROR, es->arith);
|
||||
vp->flag &= ~EXPRINEVAL;
|
||||
es->evaling = NULL;
|
||||
}
|
||||
return vq;
|
||||
}
|
902
syn.c
Normal file
902
syn.c
Normal file
@ -0,0 +1,902 @@
|
||||
/** $MirOS: src/bin/mksh/syn.c,v 1.1 2005/05/23 03:06:10 tg Exp $ */
|
||||
/* $OpenBSD: syn.c,v 1.22 2005/03/30 17:16:37 deraadt Exp $ */
|
||||
|
||||
#include "sh.h"
|
||||
|
||||
__RCSID("$MirOS: src/bin/mksh/syn.c,v 1.1 2005/05/23 03:06:10 tg Exp $");
|
||||
|
||||
struct nesting_state {
|
||||
int start_token; /* token than began nesting (eg, FOR) */
|
||||
int start_line; /* line nesting began on */
|
||||
};
|
||||
|
||||
static void yyparse(void);
|
||||
static struct op *pipeline(int);
|
||||
static struct op *andor(void);
|
||||
static struct op *c_list(int);
|
||||
static struct ioword *synio(int);
|
||||
static void musthave(int, int);
|
||||
static struct op *nested(int, int, int);
|
||||
static struct op *get_command(int);
|
||||
static struct op *dogroup(void);
|
||||
static struct op *thenpart(void);
|
||||
static struct op *elsepart(void);
|
||||
static struct op *caselist(void);
|
||||
static struct op *casepart(int);
|
||||
static struct op *function_body(char *, int);
|
||||
static char ** wordlist(void);
|
||||
static struct op *block(int, struct op *, struct op *, char **);
|
||||
static struct op *newtp(int);
|
||||
static void syntaxerr(const char *) __attribute__((__noreturn__));
|
||||
static void nesting_push(struct nesting_state *, int);
|
||||
static void nesting_pop(struct nesting_state *);
|
||||
static int assign_command(char *);
|
||||
static int inalias(struct source *);
|
||||
static int dbtestp_isa(Test_env *, Test_meta);
|
||||
static const char *dbtestp_getopnd(Test_env *, Test_op, int);
|
||||
static int dbtestp_eval(Test_env *, Test_op, const char *,
|
||||
const char *, int);
|
||||
static void dbtestp_error(Test_env *, int, const char *)
|
||||
__attribute__((noreturn));
|
||||
|
||||
static struct op *outtree; /* yyparse output */
|
||||
|
||||
static struct nesting_state nesting; /* \n changed to ; */
|
||||
|
||||
static int reject; /* token(cf) gets symbol again */
|
||||
static int symbol; /* yylex value */
|
||||
|
||||
#define REJECT (reject = 1)
|
||||
#define ACCEPT (reject = 0)
|
||||
#define token(cf) \
|
||||
((reject) ? (ACCEPT, symbol) : (symbol = yylex(cf)))
|
||||
#define tpeek(cf) \
|
||||
((reject) ? (symbol) : (REJECT, symbol = yylex(cf)))
|
||||
|
||||
static void
|
||||
yyparse(void)
|
||||
{
|
||||
int c;
|
||||
|
||||
ACCEPT;
|
||||
|
||||
outtree = c_list(source->type == SSTRING);
|
||||
c = tpeek(0);
|
||||
if (c == 0 && !outtree)
|
||||
outtree = newtp(TEOF);
|
||||
else if (c != '\n' && c != 0)
|
||||
syntaxerr(NULL);
|
||||
}
|
||||
|
||||
static struct op *
|
||||
pipeline(int cf)
|
||||
{
|
||||
struct op *t, *p, *tl = NULL;
|
||||
|
||||
t = get_command(cf);
|
||||
if (t != NULL) {
|
||||
while (token(0) == '|') {
|
||||
if ((p = get_command(CONTIN)) == NULL)
|
||||
syntaxerr(NULL);
|
||||
if (tl == NULL)
|
||||
t = tl = block(TPIPE, t, p, NOWORDS);
|
||||
else
|
||||
tl = tl->right = block(TPIPE, tl->right, p, NOWORDS);
|
||||
}
|
||||
REJECT;
|
||||
}
|
||||
return (t);
|
||||
}
|
||||
|
||||
static struct op *
|
||||
andor(void)
|
||||
{
|
||||
struct op *t, *p;
|
||||
int c;
|
||||
|
||||
t = pipeline(0);
|
||||
if (t != NULL) {
|
||||
while ((c = token(0)) == LOGAND || c == LOGOR) {
|
||||
if ((p = pipeline(CONTIN)) == NULL)
|
||||
syntaxerr(NULL);
|
||||
t = block(c == LOGAND? TAND: TOR, t, p, NOWORDS);
|
||||
}
|
||||
REJECT;
|
||||
}
|
||||
return (t);
|
||||
}
|
||||
|
||||
static struct op *
|
||||
c_list(int multi)
|
||||
{
|
||||
struct op *t = NULL, *p, *tl = NULL;
|
||||
int c;
|
||||
int have_sep;
|
||||
|
||||
while (1) {
|
||||
p = andor();
|
||||
/* Token has always been read/rejected at this point, so
|
||||
* we don't worry about what flags to pass token()
|
||||
*/
|
||||
c = token(0);
|
||||
have_sep = 1;
|
||||
if (c == '\n' && (multi || inalias(source))) {
|
||||
if (!p) /* ignore blank lines */
|
||||
continue;
|
||||
} else if (!p)
|
||||
break;
|
||||
else if (c == '&' || c == COPROC)
|
||||
p = block(c == '&' ? TASYNC : TCOPROC,
|
||||
p, NOBLOCK, NOWORDS);
|
||||
else if (c != ';')
|
||||
have_sep = 0;
|
||||
if (!t)
|
||||
t = p;
|
||||
else if (!tl)
|
||||
t = tl = block(TLIST, t, p, NOWORDS);
|
||||
else
|
||||
tl = tl->right = block(TLIST, tl->right, p, NOWORDS);
|
||||
if (!have_sep)
|
||||
break;
|
||||
}
|
||||
REJECT;
|
||||
return t;
|
||||
}
|
||||
|
||||
static struct ioword *
|
||||
synio(int cf)
|
||||
{
|
||||
struct ioword *iop;
|
||||
int ishere;
|
||||
|
||||
if (tpeek(cf) != REDIR)
|
||||
return NULL;
|
||||
ACCEPT;
|
||||
iop = yylval.iop;
|
||||
ishere = (iop->flag&IOTYPE) == IOHERE;
|
||||
musthave(LWORD, ishere ? HEREDELIM : 0);
|
||||
if (ishere) {
|
||||
iop->delim = yylval.cp;
|
||||
if (*ident != 0) /* unquoted */
|
||||
iop->flag |= IOEVAL;
|
||||
if (herep >= &heres[HERES])
|
||||
yyerror("too many <<s\n");
|
||||
*herep++ = iop;
|
||||
} else
|
||||
iop->name = yylval.cp;
|
||||
return iop;
|
||||
}
|
||||
|
||||
static void
|
||||
musthave(int c, int cf)
|
||||
{
|
||||
if ((token(cf)) != c)
|
||||
syntaxerr(NULL);
|
||||
}
|
||||
|
||||
static struct op *
|
||||
nested(int type, int smark, int emark)
|
||||
{
|
||||
struct op *t;
|
||||
struct nesting_state old_nesting;
|
||||
|
||||
nesting_push(&old_nesting, smark);
|
||||
t = c_list(true);
|
||||
musthave(emark, KEYWORD|ALIAS);
|
||||
nesting_pop(&old_nesting);
|
||||
return (block(type, t, NOBLOCK, NOWORDS));
|
||||
}
|
||||
|
||||
static struct op *
|
||||
get_command(int cf)
|
||||
{
|
||||
struct op *t;
|
||||
int c, iopn = 0, syniocf;
|
||||
struct ioword *iop, **iops;
|
||||
XPtrV args, vars;
|
||||
struct nesting_state old_nesting;
|
||||
|
||||
iops = (struct ioword **) alloc(sizeofN(struct ioword *, NUFILE+1),
|
||||
ATEMP);
|
||||
XPinit(args, 16);
|
||||
XPinit(vars, 16);
|
||||
|
||||
syniocf = KEYWORD|ALIAS;
|
||||
switch (c = token(cf|KEYWORD|ALIAS|VARASN)) {
|
||||
default:
|
||||
REJECT;
|
||||
afree((void*) iops, ATEMP);
|
||||
XPfree(args);
|
||||
XPfree(vars);
|
||||
return NULL; /* empty line */
|
||||
|
||||
case LWORD:
|
||||
case REDIR:
|
||||
REJECT;
|
||||
syniocf &= ~(KEYWORD|ALIAS);
|
||||
t = newtp(TCOM);
|
||||
t->lineno = source->line;
|
||||
while (1) {
|
||||
cf = (t->u.evalflags ? ARRAYVAR : 0) |
|
||||
(XPsize(args) == 0 ? ALIAS|VARASN : CMDWORD);
|
||||
switch (tpeek(cf)) {
|
||||
case REDIR:
|
||||
if (iopn >= NUFILE)
|
||||
yyerror("too many redirections\n");
|
||||
iops[iopn++] = synio(cf);
|
||||
break;
|
||||
|
||||
case LWORD:
|
||||
ACCEPT;
|
||||
/* the iopn == 0 and XPsize(vars) == 0 are
|
||||
* dubious but at&t ksh acts this way
|
||||
*/
|
||||
if (iopn == 0 && XPsize(vars) == 0 &&
|
||||
XPsize(args) == 0 &&
|
||||
assign_command(ident))
|
||||
t->u.evalflags = DOVACHECK;
|
||||
if ((XPsize(args) == 0 || Flag(FKEYWORD)) &&
|
||||
is_wdvarassign(yylval.cp))
|
||||
XPput(vars, yylval.cp);
|
||||
else
|
||||
XPput(args, yylval.cp);
|
||||
break;
|
||||
|
||||
case '(':
|
||||
/* Check for "> foo (echo hi)", which at&t ksh
|
||||
* allows (not POSIX, but not disallowed)
|
||||
*/
|
||||
afree(t, ATEMP);
|
||||
if (XPsize(args) == 0 && XPsize(vars) == 0) {
|
||||
ACCEPT;
|
||||
goto Subshell;
|
||||
}
|
||||
/* Must be a function */
|
||||
if (iopn != 0 || XPsize(args) != 1 ||
|
||||
XPsize(vars) != 0)
|
||||
syntaxerr(NULL);
|
||||
ACCEPT;
|
||||
/*(*/
|
||||
musthave(')', 0);
|
||||
t = function_body(XPptrv(args)[0], false);
|
||||
goto Leave;
|
||||
|
||||
default:
|
||||
goto Leave;
|
||||
}
|
||||
}
|
||||
Leave:
|
||||
break;
|
||||
|
||||
Subshell:
|
||||
case '(':
|
||||
t = nested(TPAREN, '(', ')');
|
||||
break;
|
||||
|
||||
case '{': /*}*/
|
||||
t = nested(TBRACE, '{', '}');
|
||||
break;
|
||||
|
||||
case MDPAREN:
|
||||
{
|
||||
static const char let_cmd[] = {
|
||||
CHAR, 'l', CHAR, 'e',
|
||||
CHAR, 't', EOS
|
||||
};
|
||||
/* Leave KEYWORD in syniocf (allow if (( 1 )) then ...) */
|
||||
t = newtp(TCOM);
|
||||
t->lineno = source->line;
|
||||
ACCEPT;
|
||||
XPput(args, wdcopy(let_cmd, ATEMP));
|
||||
musthave(LWORD,LETEXPR);
|
||||
XPput(args, yylval.cp);
|
||||
break;
|
||||
}
|
||||
|
||||
case DBRACKET: /* [[ .. ]] */
|
||||
/* Leave KEYWORD in syniocf (allow if [[ -n 1 ]] then ...) */
|
||||
t = newtp(TDBRACKET);
|
||||
ACCEPT;
|
||||
{
|
||||
Test_env te;
|
||||
|
||||
te.flags = TEF_DBRACKET;
|
||||
te.pos.av = &args;
|
||||
te.isa = dbtestp_isa;
|
||||
te.getopnd = dbtestp_getopnd;
|
||||
te.eval = dbtestp_eval;
|
||||
te.error = dbtestp_error;
|
||||
|
||||
test_parse(&te);
|
||||
}
|
||||
break;
|
||||
|
||||
case FOR:
|
||||
case SELECT:
|
||||
t = newtp((c == FOR) ? TFOR : TSELECT);
|
||||
musthave(LWORD, ARRAYVAR);
|
||||
if (!is_wdvarname(yylval.cp, true))
|
||||
yyerror("%s: bad identifier\n",
|
||||
c == FOR ? "for" : "select");
|
||||
t->str = str_save(ident, ATEMP);
|
||||
nesting_push(&old_nesting, c);
|
||||
t->vars = wordlist();
|
||||
t->left = dogroup();
|
||||
nesting_pop(&old_nesting);
|
||||
break;
|
||||
|
||||
case WHILE:
|
||||
case UNTIL:
|
||||
nesting_push(&old_nesting, c);
|
||||
t = newtp((c == WHILE) ? TWHILE : TUNTIL);
|
||||
t->left = c_list(true);
|
||||
t->right = dogroup();
|
||||
nesting_pop(&old_nesting);
|
||||
break;
|
||||
|
||||
case CASE:
|
||||
t = newtp(TCASE);
|
||||
musthave(LWORD, 0);
|
||||
t->str = yylval.cp;
|
||||
nesting_push(&old_nesting, c);
|
||||
t->left = caselist();
|
||||
nesting_pop(&old_nesting);
|
||||
break;
|
||||
|
||||
case IF:
|
||||
nesting_push(&old_nesting, c);
|
||||
t = newtp(TIF);
|
||||
t->left = c_list(true);
|
||||
t->right = thenpart();
|
||||
musthave(FI, KEYWORD|ALIAS);
|
||||
nesting_pop(&old_nesting);
|
||||
break;
|
||||
|
||||
case BANG:
|
||||
syniocf &= ~(KEYWORD|ALIAS);
|
||||
t = pipeline(0);
|
||||
if (t == NULL)
|
||||
syntaxerr(NULL);
|
||||
t = block(TBANG, NOBLOCK, t, NOWORDS);
|
||||
break;
|
||||
|
||||
case TIME:
|
||||
syniocf &= ~(KEYWORD|ALIAS);
|
||||
t = pipeline(0);
|
||||
t = block(TTIME, t, NOBLOCK, NOWORDS);
|
||||
break;
|
||||
|
||||
case FUNCTION:
|
||||
musthave(LWORD, 0);
|
||||
t = function_body(yylval.cp, true);
|
||||
break;
|
||||
}
|
||||
|
||||
while ((iop = synio(syniocf)) != NULL) {
|
||||
if (iopn >= NUFILE)
|
||||
yyerror("too many redirections\n");
|
||||
iops[iopn++] = iop;
|
||||
}
|
||||
|
||||
if (iopn == 0) {
|
||||
afree((void*) iops, ATEMP);
|
||||
t->ioact = NULL;
|
||||
} else {
|
||||
iops[iopn++] = NULL;
|
||||
iops = (struct ioword **) aresize((void*) iops,
|
||||
sizeofN(struct ioword *, iopn), ATEMP);
|
||||
t->ioact = iops;
|
||||
}
|
||||
|
||||
if (t->type == TCOM || t->type == TDBRACKET) {
|
||||
XPput(args, NULL);
|
||||
t->args = (char **) XPclose(args);
|
||||
XPput(vars, NULL);
|
||||
t->vars = (char **) XPclose(vars);
|
||||
} else {
|
||||
XPfree(args);
|
||||
XPfree(vars);
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
static struct op *
|
||||
dogroup(void)
|
||||
{
|
||||
int c;
|
||||
struct op *list;
|
||||
|
||||
c = token(CONTIN|KEYWORD|ALIAS);
|
||||
/* A {...} can be used instead of do...done for for/select loops
|
||||
* but not for while/until loops - we don't need to check if it
|
||||
* is a while loop because it would have been parsed as part of
|
||||
* the conditional command list...
|
||||
*/
|
||||
if (c == DO)
|
||||
c = DONE;
|
||||
else if (c == '{')
|
||||
c = '}';
|
||||
else
|
||||
syntaxerr(NULL);
|
||||
list = c_list(true);
|
||||
musthave(c, KEYWORD|ALIAS);
|
||||
return list;
|
||||
}
|
||||
|
||||
static struct op *
|
||||
thenpart(void)
|
||||
{
|
||||
struct op *t;
|
||||
|
||||
musthave(THEN, KEYWORD|ALIAS);
|
||||
t = newtp(0);
|
||||
t->left = c_list(true);
|
||||
if (t->left == NULL)
|
||||
syntaxerr(NULL);
|
||||
t->right = elsepart();
|
||||
return (t);
|
||||
}
|
||||
|
||||
static struct op *
|
||||
elsepart(void)
|
||||
{
|
||||
struct op *t;
|
||||
|
||||
switch (token(KEYWORD|ALIAS|VARASN)) {
|
||||
case ELSE:
|
||||
if ((t = c_list(true)) == NULL)
|
||||
syntaxerr(NULL);
|
||||
return (t);
|
||||
|
||||
case ELIF:
|
||||
t = newtp(TELIF);
|
||||
t->left = c_list(true);
|
||||
t->right = thenpart();
|
||||
return (t);
|
||||
|
||||
default:
|
||||
REJECT;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct op *
|
||||
caselist(void)
|
||||
{
|
||||
struct op *t, *tl;
|
||||
int c;
|
||||
|
||||
c = token(CONTIN|KEYWORD|ALIAS);
|
||||
/* A {...} can be used instead of in...esac for case statements */
|
||||
if (c == IN)
|
||||
c = ESAC;
|
||||
else if (c == '{')
|
||||
c = '}';
|
||||
else
|
||||
syntaxerr(NULL);
|
||||
t = tl = NULL;
|
||||
while ((tpeek(CONTIN|KEYWORD|ESACONLY)) != c) { /* no ALIAS here */
|
||||
struct op *tc = casepart(c);
|
||||
if (tl == NULL)
|
||||
t = tl = tc, tl->right = NULL;
|
||||
else
|
||||
tl->right = tc, tl = tc;
|
||||
}
|
||||
musthave(c, KEYWORD|ALIAS);
|
||||
return (t);
|
||||
}
|
||||
|
||||
static struct op *
|
||||
casepart(int endtok)
|
||||
{
|
||||
struct op *t;
|
||||
int c;
|
||||
XPtrV ptns;
|
||||
|
||||
XPinit(ptns, 16);
|
||||
t = newtp(TPAT);
|
||||
c = token(CONTIN|KEYWORD); /* no ALIAS here */
|
||||
if (c != '(')
|
||||
REJECT;
|
||||
do {
|
||||
musthave(LWORD, 0);
|
||||
XPput(ptns, yylval.cp);
|
||||
} while ((c = token(0)) == '|');
|
||||
REJECT;
|
||||
XPput(ptns, NULL);
|
||||
t->vars = (char **) XPclose(ptns);
|
||||
musthave(')', 0);
|
||||
|
||||
t->left = c_list(true);
|
||||
/* Note: Posix requires the ;; */
|
||||
if ((tpeek(CONTIN|KEYWORD|ALIAS)) != endtok)
|
||||
musthave(BREAK, CONTIN|KEYWORD|ALIAS);
|
||||
return (t);
|
||||
}
|
||||
|
||||
static struct op *
|
||||
function_body(char *name,
|
||||
int ksh_func) /* function foo { ... } vs foo() { .. } */
|
||||
{
|
||||
char *sname, *p;
|
||||
struct op *t;
|
||||
int old_func_parse;
|
||||
|
||||
sname = wdstrip(name);
|
||||
/* Check for valid characters in name. posix and ksh93 say only
|
||||
* allow [a-zA-Z_0-9] but this allows more as old pdkshs have
|
||||
* allowed more (the following were never allowed:
|
||||
* nul space nl tab $ ' " \ ` ( ) & | ; = < >
|
||||
* C_QUOTE covers all but = and adds # [ ] ? *)
|
||||
*/
|
||||
for (p = sname; *p; p++)
|
||||
if (ctype(*p, C_QUOTE) || *p == '=')
|
||||
yyerror("%s: invalid function name\n", sname);
|
||||
|
||||
t = newtp(TFUNCT);
|
||||
t->str = sname;
|
||||
t->u.ksh_func = ksh_func;
|
||||
t->lineno = source->line;
|
||||
|
||||
/* Note that POSIX allows only compound statements after foo(), sh and
|
||||
* at&t ksh allow any command, go with the later since it shouldn't
|
||||
* break anything. However, for function foo, at&t ksh only accepts
|
||||
* an open-brace.
|
||||
*/
|
||||
if (ksh_func) {
|
||||
musthave('{', CONTIN|KEYWORD|ALIAS); /* } */
|
||||
REJECT;
|
||||
}
|
||||
|
||||
old_func_parse = e->flags & EF_FUNC_PARSE;
|
||||
e->flags |= EF_FUNC_PARSE;
|
||||
if ((t->left = get_command(CONTIN)) == NULL) {
|
||||
/*
|
||||
* Probably something like foo() followed by eof or ;.
|
||||
* This is accepted by sh and ksh88.
|
||||
* To make "typeset -f foo" work reliably (so its output can
|
||||
* be used as input), we pretend there is a colon here.
|
||||
*/
|
||||
t->left = newtp(TCOM);
|
||||
t->left->args = (char **) alloc(sizeof(char *) * 2, ATEMP);
|
||||
t->left->args[0] = alloc(sizeof(char) * 3, ATEMP);
|
||||
t->left->args[0][0] = CHAR;
|
||||
t->left->args[0][1] = ':';
|
||||
t->left->args[0][2] = EOS;
|
||||
t->left->args[1] = NULL;
|
||||
t->left->vars = (char **) alloc(sizeof(char *), ATEMP);
|
||||
t->left->vars[0] = NULL;
|
||||
t->left->lineno = 1;
|
||||
}
|
||||
if (!old_func_parse)
|
||||
e->flags &= ~EF_FUNC_PARSE;
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
static char **
|
||||
wordlist(void)
|
||||
{
|
||||
int c;
|
||||
XPtrV args;
|
||||
|
||||
XPinit(args, 16);
|
||||
/* Posix does not do alias expansion here... */
|
||||
if ((c = token(CONTIN|KEYWORD|ALIAS)) != IN) {
|
||||
if (c != ';') /* non-POSIX, but at&t ksh accepts a ; here */
|
||||
REJECT;
|
||||
return NULL;
|
||||
}
|
||||
while ((c = token(0)) == LWORD)
|
||||
XPput(args, yylval.cp);
|
||||
if (c != '\n' && c != ';')
|
||||
syntaxerr(NULL);
|
||||
if (XPsize(args) == 0) {
|
||||
XPfree(args);
|
||||
return NULL;
|
||||
} else {
|
||||
XPput(args, NULL);
|
||||
return (char **) XPclose(args);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* supporting functions
|
||||
*/
|
||||
|
||||
static struct op *
|
||||
block(int type, struct op *t1, struct op *t2, char **wp)
|
||||
{
|
||||
struct op *t;
|
||||
|
||||
t = newtp(type);
|
||||
t->left = t1;
|
||||
t->right = t2;
|
||||
t->vars = wp;
|
||||
return (t);
|
||||
}
|
||||
|
||||
const struct tokeninfo {
|
||||
const char *name;
|
||||
short val;
|
||||
short reserved;
|
||||
} tokentab[] = {
|
||||
/* Reserved words */
|
||||
{ "if", IF, true },
|
||||
{ "then", THEN, true },
|
||||
{ "else", ELSE, true },
|
||||
{ "elif", ELIF, true },
|
||||
{ "fi", FI, true },
|
||||
{ "case", CASE, true },
|
||||
{ "esac", ESAC, true },
|
||||
{ "for", FOR, true },
|
||||
{ "select", SELECT, true },
|
||||
{ "while", WHILE, true },
|
||||
{ "until", UNTIL, true },
|
||||
{ "do", DO, true },
|
||||
{ "done", DONE, true },
|
||||
{ "in", IN, true },
|
||||
{ "function", FUNCTION, true },
|
||||
{ "time", TIME, true },
|
||||
{ "{", '{', true },
|
||||
{ "}", '}', true },
|
||||
{ "!", BANG, true },
|
||||
{ "[[", DBRACKET, true },
|
||||
/* Lexical tokens (0[EOF], LWORD and REDIR handled specially) */
|
||||
{ "&&", LOGAND, false },
|
||||
{ "||", LOGOR, false },
|
||||
{ ";;", BREAK, false },
|
||||
{ "((", MDPAREN, false },
|
||||
{ "|&", COPROC, false },
|
||||
/* and some special cases... */
|
||||
{ "newline", '\n', false },
|
||||
{ NULL, 0, false }
|
||||
};
|
||||
|
||||
void
|
||||
initkeywords(void)
|
||||
{
|
||||
struct tokeninfo const *tt;
|
||||
struct tbl *p;
|
||||
|
||||
tinit(&keywords, APERM, 32); /* must be 2^n (currently 20 keywords) */
|
||||
for (tt = tokentab; tt->name; tt++) {
|
||||
if (tt->reserved) {
|
||||
p = tenter(&keywords, tt->name, hash(tt->name));
|
||||
p->flag |= DEFINED|ISSET;
|
||||
p->type = CKEYWD;
|
||||
p->val.i = tt->val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
syntaxerr(const char *what)
|
||||
{
|
||||
char redir[6]; /* 2<<- is the longest redirection, I think */
|
||||
const char *s;
|
||||
struct tokeninfo const *tt;
|
||||
int c;
|
||||
|
||||
if (!what)
|
||||
what = "unexpected";
|
||||
REJECT;
|
||||
c = token(0);
|
||||
Again:
|
||||
switch (c) {
|
||||
case 0:
|
||||
if (nesting.start_token) {
|
||||
c = nesting.start_token;
|
||||
source->errline = nesting.start_line;
|
||||
what = "unmatched";
|
||||
goto Again;
|
||||
}
|
||||
/* don't quote the EOF */
|
||||
yyerror("syntax error: unexpected EOF\n");
|
||||
/*NOTREACHED*/
|
||||
|
||||
case LWORD:
|
||||
s = snptreef(NULL, 32, "%S", yylval.cp);
|
||||
break;
|
||||
|
||||
case REDIR:
|
||||
s = snptreef(redir, sizeof(redir), "%R", yylval.iop);
|
||||
break;
|
||||
|
||||
default:
|
||||
for (tt = tokentab; tt->name; tt++)
|
||||
if (tt->val == c)
|
||||
break;
|
||||
if (tt->name)
|
||||
s = tt->name;
|
||||
else {
|
||||
if (c > 0 && c < 256) {
|
||||
redir[0] = c;
|
||||
redir[1] = '\0';
|
||||
} else
|
||||
shf_snprintf(redir, sizeof(redir),
|
||||
"?%d", c);
|
||||
s = redir;
|
||||
}
|
||||
}
|
||||
yyerror("syntax error: '%s' %s\n", s, what);
|
||||
}
|
||||
|
||||
static void
|
||||
nesting_push(struct nesting_state *save, int tok)
|
||||
{
|
||||
*save = nesting;
|
||||
nesting.start_token = tok;
|
||||
nesting.start_line = source->line;
|
||||
}
|
||||
|
||||
static void
|
||||
nesting_pop(struct nesting_state *saved)
|
||||
{
|
||||
nesting = *saved;
|
||||
}
|
||||
|
||||
static struct op *
|
||||
newtp(int type)
|
||||
{
|
||||
struct op *t;
|
||||
|
||||
t = (struct op *) alloc(sizeof(*t), ATEMP);
|
||||
t->type = type;
|
||||
t->u.evalflags = 0;
|
||||
t->args = t->vars = NULL;
|
||||
t->ioact = NULL;
|
||||
t->left = t->right = NULL;
|
||||
t->str = NULL;
|
||||
return (t);
|
||||
}
|
||||
|
||||
struct op *
|
||||
compile(Source *s)
|
||||
{
|
||||
nesting.start_token = 0;
|
||||
nesting.start_line = 0;
|
||||
herep = heres;
|
||||
source = s;
|
||||
yyparse();
|
||||
return outtree;
|
||||
}
|
||||
|
||||
/* This kludge exists to take care of sh/at&t ksh oddity in which
|
||||
* the arguments of alias/export/readonly/typeset have no field
|
||||
* splitting, file globbing, or (normal) tilde expansion done.
|
||||
* at&t ksh seems to do something similar to this since
|
||||
* $ touch a=a; typeset a=[ab]; echo "$a"
|
||||
* a=[ab]
|
||||
* $ x=typeset; $x a=[ab]; echo "$a"
|
||||
* a=a
|
||||
* $
|
||||
*/
|
||||
static int
|
||||
assign_command(char *s)
|
||||
{
|
||||
char c = *s;
|
||||
|
||||
if (Flag(FPOSIX) || !*s)
|
||||
return 0;
|
||||
return (c == 'a' && strcmp(s, "alias") == 0) ||
|
||||
(c == 'e' && strcmp(s, "export") == 0) ||
|
||||
(c == 'r' && strcmp(s, "readonly") == 0) ||
|
||||
(c == 't' && strcmp(s, "typeset") == 0);
|
||||
}
|
||||
|
||||
/* Check if we are in the middle of reading an alias */
|
||||
static int
|
||||
inalias(struct source *s)
|
||||
{
|
||||
for (; s && s->type == SALIAS; s = s->next)
|
||||
if (!(s->flags & SF_ALIASEND))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Order important - indexed by Test_meta values
|
||||
* Note that ||, &&, ( and ) can't appear in as unquoted strings
|
||||
* in normal shell input, so these can be interpreted unambiguously
|
||||
* in the evaluation pass.
|
||||
*/
|
||||
static const char dbtest_or[] = { CHAR, '|', CHAR, '|', EOS };
|
||||
static const char dbtest_and[] = { CHAR, '&', CHAR, '&', EOS };
|
||||
static const char dbtest_not[] = { CHAR, '!', EOS };
|
||||
static const char dbtest_oparen[] = { CHAR, '(', EOS };
|
||||
static const char dbtest_cparen[] = { CHAR, ')', EOS };
|
||||
const char *const dbtest_tokens[] = {
|
||||
dbtest_or, dbtest_and, dbtest_not,
|
||||
dbtest_oparen, dbtest_cparen
|
||||
};
|
||||
const char db_close[] = { CHAR, ']', CHAR, ']', EOS };
|
||||
const char db_lthan[] = { CHAR, '<', EOS };
|
||||
const char db_gthan[] = { CHAR, '>', EOS };
|
||||
|
||||
/* Test if the current token is a whatever. Accepts the current token if
|
||||
* it is. Returns 0 if it is not, non-zero if it is (in the case of
|
||||
* TM_UNOP and TM_BINOP, the returned value is a Test_op).
|
||||
*/
|
||||
static int
|
||||
dbtestp_isa(Test_env *te, Test_meta meta)
|
||||
{
|
||||
int c = tpeek(ARRAYVAR | (meta == TM_BINOP ? 0 : CONTIN));
|
||||
int uqword = 0;
|
||||
char *save = NULL;
|
||||
int ret = 0;
|
||||
|
||||
/* unquoted word? */
|
||||
uqword = c == LWORD && *ident;
|
||||
|
||||
if (meta == TM_OR)
|
||||
ret = c == LOGOR;
|
||||
else if (meta == TM_AND)
|
||||
ret = c == LOGAND;
|
||||
else if (meta == TM_NOT)
|
||||
ret = uqword && strcmp(yylval.cp, dbtest_tokens[(int) TM_NOT]) == 0;
|
||||
else if (meta == TM_OPAREN)
|
||||
ret = c == '(' /*)*/;
|
||||
else if (meta == TM_CPAREN)
|
||||
ret = c == /*(*/ ')';
|
||||
else if (meta == TM_UNOP || meta == TM_BINOP) {
|
||||
if (meta == TM_BINOP && c == REDIR &&
|
||||
(yylval.iop->flag == IOREAD || yylval.iop->flag == IOWRITE)) {
|
||||
ret = 1;
|
||||
save = wdcopy(yylval.iop->flag == IOREAD ?
|
||||
db_lthan : db_gthan, ATEMP);
|
||||
} else if (uqword && (ret = test_isop(te, meta, ident)))
|
||||
save = yylval.cp;
|
||||
} else /* meta == TM_END */
|
||||
ret = uqword && strcmp(yylval.cp, db_close) == 0;
|
||||
if (ret) {
|
||||
ACCEPT;
|
||||
if (meta != TM_END) {
|
||||
if (!save)
|
||||
save = wdcopy(dbtest_tokens[(int) meta], ATEMP);
|
||||
XPput(*te->pos.av, save);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const char *
|
||||
dbtestp_getopnd(Test_env *te, Test_op op __attribute__((unused)),
|
||||
int do_eval __attribute__((unused)))
|
||||
{
|
||||
int c = tpeek(ARRAYVAR);
|
||||
|
||||
if (c != LWORD)
|
||||
return NULL;
|
||||
|
||||
ACCEPT;
|
||||
XPput(*te->pos.av, yylval.cp);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static int
|
||||
dbtestp_eval(Test_env *te __attribute__((unused)),
|
||||
Test_op op __attribute__((unused)),
|
||||
const char *opnd1 __attribute__((unused)),
|
||||
const char *opnd2 __attribute__((unused)),
|
||||
int do_eval __attribute__((unused)))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
dbtestp_error(Test_env *te, int offset, const char *msg)
|
||||
{
|
||||
te->flags |= TEF_ERROR;
|
||||
|
||||
if (offset < 0) {
|
||||
REJECT;
|
||||
/* Kludgy to say the least... */
|
||||
symbol = LWORD;
|
||||
yylval.cp = *(XPptrv(*te->pos.av) + XPsize(*te->pos.av) +
|
||||
offset);
|
||||
}
|
||||
syntaxerr(msg);
|
||||
}
|
691
tree.c
Normal file
691
tree.c
Normal file
@ -0,0 +1,691 @@
|
||||
/** $MirOS: src/bin/mksh/tree.c,v 1.1 2005/05/23 03:06:10 tg Exp $ */
|
||||
/* $OpenBSD: tree.c,v 1.17 2005/03/30 17:16:37 deraadt Exp $ */
|
||||
|
||||
#include "sh.h"
|
||||
|
||||
__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.1 2005/05/23 03:06:10 tg Exp $");
|
||||
|
||||
#define INDENT 4
|
||||
|
||||
#define tputc(c, shf) shf_putchar(c, shf);
|
||||
static void ptree(struct op *, int, struct shf *);
|
||||
static void pioact(struct shf *, int, struct ioword *);
|
||||
static void tputC(int, struct shf *);
|
||||
static void tputS(char *, struct shf *);
|
||||
static void vfptreef(struct shf *, int, const char *, va_list);
|
||||
static struct ioword **iocopy(struct ioword **, Area *);
|
||||
static void iofree(struct ioword **, Area *);
|
||||
|
||||
/*
|
||||
* print a command tree
|
||||
*/
|
||||
|
||||
static void
|
||||
ptree(struct op *t, int indent, struct shf *shf)
|
||||
{
|
||||
char **w;
|
||||
struct ioword **ioact;
|
||||
struct op *t1;
|
||||
|
||||
Chain:
|
||||
if (t == NULL)
|
||||
return;
|
||||
switch (t->type) {
|
||||
case TCOM:
|
||||
if (t->vars)
|
||||
for (w = t->vars; *w != NULL; )
|
||||
fptreef(shf, indent, "%S ", *w++);
|
||||
else
|
||||
fptreef(shf, indent, "#no-vars# ");
|
||||
if (t->args)
|
||||
for (w = t->args; *w != NULL; )
|
||||
fptreef(shf, indent, "%S ", *w++);
|
||||
else
|
||||
fptreef(shf, indent, "#no-args# ");
|
||||
break;
|
||||
case TEXEC:
|
||||
t = t->left;
|
||||
goto Chain;
|
||||
case TPAREN:
|
||||
fptreef(shf, indent + 2, "( %T) ", t->left);
|
||||
break;
|
||||
case TPIPE:
|
||||
fptreef(shf, indent, "%T| ", t->left);
|
||||
t = t->right;
|
||||
goto Chain;
|
||||
case TLIST:
|
||||
fptreef(shf, indent, "%T%;", t->left);
|
||||
t = t->right;
|
||||
goto Chain;
|
||||
case TOR:
|
||||
case TAND:
|
||||
fptreef(shf, indent, "%T%s %T",
|
||||
t->left, (t->type==TOR) ? "||" : "&&", t->right);
|
||||
break;
|
||||
case TBANG:
|
||||
fptreef(shf, indent, "! ");
|
||||
t = t->right;
|
||||
goto Chain;
|
||||
case TDBRACKET:
|
||||
{
|
||||
int i;
|
||||
|
||||
fptreef(shf, indent, "[[");
|
||||
for (i = 0; t->args[i]; i++)
|
||||
fptreef(shf, indent, " %S", t->args[i]);
|
||||
fptreef(shf, indent, " ]] ");
|
||||
break;
|
||||
}
|
||||
case TSELECT:
|
||||
fptreef(shf, indent, "select %s ", t->str);
|
||||
/* fall through */
|
||||
case TFOR:
|
||||
if (t->type == TFOR)
|
||||
fptreef(shf, indent, "for %s ", t->str);
|
||||
if (t->vars != NULL) {
|
||||
fptreef(shf, indent, "in ");
|
||||
for (w = t->vars; *w; )
|
||||
fptreef(shf, indent, "%S ", *w++);
|
||||
fptreef(shf, indent, "%;");
|
||||
}
|
||||
fptreef(shf, indent + INDENT, "do%N%T", t->left);
|
||||
fptreef(shf, indent, "%;done ");
|
||||
break;
|
||||
case TCASE:
|
||||
fptreef(shf, indent, "case %S in", t->str);
|
||||
for (t1 = t->left; t1 != NULL; t1 = t1->right) {
|
||||
fptreef(shf, indent, "%N(");
|
||||
for (w = t1->vars; *w != NULL; w++)
|
||||
fptreef(shf, indent, "%S%c", *w,
|
||||
(w[1] != NULL) ? '|' : ')');
|
||||
fptreef(shf, indent + INDENT, "%;%T%N;;", t1->left);
|
||||
}
|
||||
fptreef(shf, indent, "%Nesac ");
|
||||
break;
|
||||
case TIF:
|
||||
case TELIF:
|
||||
/* 3 == strlen("if ") */
|
||||
fptreef(shf, indent + 3, "if %T", t->left);
|
||||
for (;;) {
|
||||
t = t->right;
|
||||
if (t->left != NULL) {
|
||||
fptreef(shf, indent, "%;");
|
||||
fptreef(shf, indent + INDENT, "then%N%T",
|
||||
t->left);
|
||||
}
|
||||
if (t->right == NULL || t->right->type != TELIF)
|
||||
break;
|
||||
t = t->right;
|
||||
fptreef(shf, indent, "%;");
|
||||
/* 5 == strlen("elif ") */
|
||||
fptreef(shf, indent + 5, "elif %T", t->left);
|
||||
}
|
||||
if (t->right != NULL) {
|
||||
fptreef(shf, indent, "%;");
|
||||
fptreef(shf, indent + INDENT, "else%;%T", t->right);
|
||||
}
|
||||
fptreef(shf, indent, "%;fi ");
|
||||
break;
|
||||
case TWHILE:
|
||||
case TUNTIL:
|
||||
/* 6 == strlen("while"/"until") */
|
||||
fptreef(shf, indent + 6, "%s %T",
|
||||
(t->type==TWHILE) ? "while" : "until",
|
||||
t->left);
|
||||
fptreef(shf, indent, "%;do");
|
||||
fptreef(shf, indent + INDENT, "%;%T", t->right);
|
||||
fptreef(shf, indent, "%;done ");
|
||||
break;
|
||||
case TBRACE:
|
||||
fptreef(shf, indent + INDENT, "{%;%T", t->left);
|
||||
fptreef(shf, indent, "%;} ");
|
||||
break;
|
||||
case TCOPROC:
|
||||
fptreef(shf, indent, "%T|& ", t->left);
|
||||
break;
|
||||
case TASYNC:
|
||||
fptreef(shf, indent, "%T& ", t->left);
|
||||
break;
|
||||
case TFUNCT:
|
||||
fptreef(shf, indent,
|
||||
t->u.ksh_func ? "function %s %T" : "%s() %T",
|
||||
t->str, t->left);
|
||||
break;
|
||||
case TTIME:
|
||||
fptreef(shf, indent, "time %T", t->left);
|
||||
break;
|
||||
default:
|
||||
fptreef(shf, indent, "<botch>");
|
||||
break;
|
||||
}
|
||||
if ((ioact = t->ioact) != NULL) {
|
||||
int need_nl = 0;
|
||||
|
||||
while (*ioact != NULL)
|
||||
pioact(shf, indent, *ioact++);
|
||||
/* Print here documents after everything else... */
|
||||
for (ioact = t->ioact; *ioact != NULL; ) {
|
||||
struct ioword *iop = *ioact++;
|
||||
|
||||
/* heredoc is 0 when tracing (set -x) */
|
||||
if ((iop->flag & IOTYPE) == IOHERE && iop->heredoc) {
|
||||
tputc('\n', shf);
|
||||
shf_puts(iop->heredoc, shf);
|
||||
fptreef(shf, indent, "%s",
|
||||
evalstr(iop->delim, 0));
|
||||
need_nl = 1;
|
||||
}
|
||||
}
|
||||
/* Last delimiter must be followed by a newline (this often
|
||||
* leads to an extra blank line, but its not worth worrying
|
||||
* about)
|
||||
*/
|
||||
if (need_nl)
|
||||
tputc('\n', shf);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
pioact(struct shf *shf, int indent, struct ioword *iop)
|
||||
{
|
||||
int flag = iop->flag;
|
||||
int type = flag & IOTYPE;
|
||||
int expected;
|
||||
|
||||
expected = (type == IOREAD || type == IORDWR || type == IOHERE) ? 0 :
|
||||
(type == IOCAT || type == IOWRITE) ? 1 :
|
||||
(type == IODUP && (iop->unit == !(flag & IORDUP))) ? iop->unit :
|
||||
iop->unit + 1;
|
||||
if (iop->unit != expected)
|
||||
tputc('0' + iop->unit, shf);
|
||||
|
||||
switch (type) {
|
||||
case IOREAD:
|
||||
fptreef(shf, indent, "< ");
|
||||
break;
|
||||
case IOHERE:
|
||||
if (flag&IOSKIP)
|
||||
fptreef(shf, indent, "<<- ");
|
||||
else
|
||||
fptreef(shf, indent, "<< ");
|
||||
break;
|
||||
case IOCAT:
|
||||
fptreef(shf, indent, ">> ");
|
||||
break;
|
||||
case IOWRITE:
|
||||
if (flag&IOCLOB)
|
||||
fptreef(shf, indent, ">| ");
|
||||
else
|
||||
fptreef(shf, indent, "> ");
|
||||
break;
|
||||
case IORDWR:
|
||||
fptreef(shf, indent, "<> ");
|
||||
break;
|
||||
case IODUP:
|
||||
if (flag & IORDUP)
|
||||
fptreef(shf, indent, "<&");
|
||||
else
|
||||
fptreef(shf, indent, ">&");
|
||||
break;
|
||||
}
|
||||
/* name/delim are 0 when printing syntax errors */
|
||||
if (type == IOHERE) {
|
||||
if (iop->delim)
|
||||
fptreef(shf, indent, "%S ", iop->delim);
|
||||
} else if (iop->name)
|
||||
fptreef(shf, indent, (iop->flag & IONAMEXP) ? "%s " : "%S ",
|
||||
iop->name);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* variants of fputc, fputs for ptreef and snptreef
|
||||
*/
|
||||
|
||||
static void
|
||||
tputC(int c, struct shf *shf)
|
||||
{
|
||||
if ((c&0x60) == 0) { /* C0|C1 */
|
||||
tputc((c&0x80) ? '$' : '^', shf);
|
||||
tputc(((c&0x7F)|0x40), shf);
|
||||
} else if ((c&0x7F) == 0x7F) { /* DEL */
|
||||
tputc((c&0x80) ? '$' : '^', shf);
|
||||
tputc('?', shf);
|
||||
} else
|
||||
tputc(c, shf);
|
||||
}
|
||||
|
||||
static void
|
||||
tputS(char *wp, struct shf *shf)
|
||||
{
|
||||
int c, quoted=0;
|
||||
|
||||
/* problems:
|
||||
* `...` -> $(...)
|
||||
* 'foo' -> "foo"
|
||||
* could change encoding to:
|
||||
* OQUOTE ["'] ... CQUOTE ["']
|
||||
* COMSUB [(`] ...\0 (handle $ ` \ and maybe " in `...` case)
|
||||
*/
|
||||
while (1)
|
||||
switch ((c = *wp++)) {
|
||||
case EOS:
|
||||
return;
|
||||
case CHAR:
|
||||
tputC(*wp++, shf);
|
||||
break;
|
||||
case QCHAR:
|
||||
c = *wp++;
|
||||
if (!quoted || (c == '"' || c == '`' || c == '$'))
|
||||
tputc('\\', shf);
|
||||
tputC(c, shf);
|
||||
break;
|
||||
case COMSUB:
|
||||
tputc('$', shf);
|
||||
tputc('(', shf);
|
||||
while (*wp != 0)
|
||||
tputC(*wp++, shf);
|
||||
tputc(')', shf);
|
||||
wp++;
|
||||
break;
|
||||
case EXPRSUB:
|
||||
tputc('$', shf);
|
||||
tputc('(', shf);
|
||||
tputc('(', shf);
|
||||
while (*wp != 0)
|
||||
tputC(*wp++, shf);
|
||||
tputc(')', shf);
|
||||
tputc(')', shf);
|
||||
wp++;
|
||||
break;
|
||||
case OQUOTE:
|
||||
quoted = 1;
|
||||
tputc('"', shf);
|
||||
break;
|
||||
case CQUOTE:
|
||||
quoted = 0;
|
||||
tputc('"', shf);
|
||||
break;
|
||||
case OSUBST:
|
||||
tputc('$', shf);
|
||||
if (*wp++ == '{')
|
||||
tputc('{', shf);
|
||||
while ((c = *wp++) != 0)
|
||||
tputC(c, shf);
|
||||
break;
|
||||
case CSUBST:
|
||||
if (*wp++ == '}')
|
||||
tputc('}', shf);
|
||||
break;
|
||||
case OPAT:
|
||||
tputc(*wp++, shf);
|
||||
tputc('(', shf);
|
||||
break;
|
||||
case SPAT:
|
||||
tputc('|', shf);
|
||||
break;
|
||||
case CPAT:
|
||||
tputc(')', shf);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* this is the _only_ way to reliably handle
|
||||
* variable args with an ANSI compiler
|
||||
*/
|
||||
/* VARARGS */
|
||||
int
|
||||
fptreef(struct shf *shf, int indent, const char *fmt, ...)
|
||||
{
|
||||
va_list va;
|
||||
|
||||
va_start(va, fmt);
|
||||
|
||||
vfptreef(shf, indent, fmt, va);
|
||||
va_end(va);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* VARARGS */
|
||||
char *
|
||||
snptreef(char *s, int n, const char *fmt, ...)
|
||||
{
|
||||
va_list va;
|
||||
struct shf shf;
|
||||
|
||||
shf_sopen(s, n, SHF_WR | (s ? 0 : SHF_DYNAMIC), &shf);
|
||||
|
||||
va_start(va, fmt);
|
||||
vfptreef(&shf, 0, fmt, va);
|
||||
va_end(va);
|
||||
|
||||
return shf_sclose(&shf); /* null terminates */
|
||||
}
|
||||
|
||||
static void
|
||||
vfptreef(struct shf *shf, int indent, const char *fmt, va_list va)
|
||||
{
|
||||
int c;
|
||||
|
||||
while ((c = *fmt++)) {
|
||||
if (c == '%') {
|
||||
long n;
|
||||
char *p;
|
||||
int neg;
|
||||
|
||||
switch ((c = *fmt++)) {
|
||||
case 'c':
|
||||
tputc(va_arg(va, int), shf);
|
||||
break;
|
||||
case 's':
|
||||
p = va_arg(va, char *);
|
||||
while (*p)
|
||||
tputc(*p++, shf);
|
||||
break;
|
||||
case 'S': /* word */
|
||||
p = va_arg(va, char *);
|
||||
tputS(p, shf);
|
||||
break;
|
||||
case 'd': case 'u': /* decimal */
|
||||
n = (c == 'd') ? (long)va_arg(va, int) :
|
||||
(long)va_arg(va, unsigned int);
|
||||
neg = c=='d' && n<0;
|
||||
p = ulton((neg) ? -n : n, 10);
|
||||
if (neg)
|
||||
*--p = '-';
|
||||
while (*p)
|
||||
tputc(*p++, shf);
|
||||
break;
|
||||
case 'T': /* format tree */
|
||||
ptree(va_arg(va, struct op *), indent, shf);
|
||||
break;
|
||||
case ';': /* newline or ; */
|
||||
case 'N': /* newline or space */
|
||||
if (shf->flags & SHF_STRING) {
|
||||
if (c == ';')
|
||||
tputc(';', shf);
|
||||
tputc(' ', shf);
|
||||
} else {
|
||||
int i;
|
||||
|
||||
tputc('\n', shf);
|
||||
for (i = indent; i >= 8; i -= 8)
|
||||
tputc('\t', shf);
|
||||
for (; i > 0; --i)
|
||||
tputc(' ', shf);
|
||||
}
|
||||
break;
|
||||
case 'R':
|
||||
pioact(shf, indent, va_arg(va, struct ioword *));
|
||||
break;
|
||||
default:
|
||||
tputc(c, shf);
|
||||
break;
|
||||
}
|
||||
} else
|
||||
tputc(c, shf);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* copy tree (for function definition)
|
||||
*/
|
||||
|
||||
struct op *
|
||||
tcopy(struct op *t, Area *ap)
|
||||
{
|
||||
struct op *r;
|
||||
char **tw, **rw;
|
||||
|
||||
if (t == NULL)
|
||||
return NULL;
|
||||
|
||||
r = (struct op *) alloc(sizeof(struct op), ap);
|
||||
|
||||
r->type = t->type;
|
||||
r->u.evalflags = t->u.evalflags;
|
||||
|
||||
r->str = t->type == TCASE ? wdcopy(t->str, ap) : str_save(t->str, ap);
|
||||
|
||||
if (t->vars == NULL)
|
||||
r->vars = NULL;
|
||||
else {
|
||||
for (tw = t->vars; *tw++ != NULL; )
|
||||
;
|
||||
rw = r->vars = (char **)
|
||||
alloc((tw - t->vars + 1) * sizeof(*tw), ap);
|
||||
for (tw = t->vars; *tw != NULL; )
|
||||
*rw++ = wdcopy(*tw++, ap);
|
||||
*rw = NULL;
|
||||
}
|
||||
|
||||
if (t->args == NULL)
|
||||
r->args = NULL;
|
||||
else {
|
||||
for (tw = t->args; *tw++ != NULL; )
|
||||
;
|
||||
rw = r->args = (char **)
|
||||
alloc((tw - t->args + 1) * sizeof(*tw), ap);
|
||||
for (tw = t->args; *tw != NULL; )
|
||||
*rw++ = wdcopy(*tw++, ap);
|
||||
*rw = NULL;
|
||||
}
|
||||
|
||||
r->ioact = (t->ioact == NULL) ? NULL : iocopy(t->ioact, ap);
|
||||
|
||||
r->left = tcopy(t->left, ap);
|
||||
r->right = tcopy(t->right, ap);
|
||||
r->lineno = t->lineno;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
char *
|
||||
wdcopy(const char *wp, Area *ap)
|
||||
{
|
||||
size_t len = wdscan(wp, EOS) - wp;
|
||||
return memcpy(alloc(len, ap), wp, len);
|
||||
}
|
||||
|
||||
/* return the position of prefix c in wp plus 1 */
|
||||
char *
|
||||
wdscan(const char *wp, int c)
|
||||
{
|
||||
int nest = 0;
|
||||
|
||||
while (1)
|
||||
switch (*wp++) {
|
||||
case EOS:
|
||||
return (char *) wp;
|
||||
case CHAR:
|
||||
case QCHAR:
|
||||
wp++;
|
||||
break;
|
||||
case COMSUB:
|
||||
case EXPRSUB:
|
||||
while (*wp++ != 0)
|
||||
;
|
||||
break;
|
||||
case OQUOTE:
|
||||
case CQUOTE:
|
||||
break;
|
||||
case OSUBST:
|
||||
nest++;
|
||||
while (*wp++ != '\0')
|
||||
;
|
||||
break;
|
||||
case CSUBST:
|
||||
wp++;
|
||||
if (c == CSUBST && nest == 0)
|
||||
return (char *) wp;
|
||||
nest--;
|
||||
break;
|
||||
case OPAT:
|
||||
nest++;
|
||||
wp++;
|
||||
break;
|
||||
case SPAT:
|
||||
case CPAT:
|
||||
if (c == wp[-1] && nest == 0)
|
||||
return (char *) wp;
|
||||
if (wp[-1] == CPAT)
|
||||
nest--;
|
||||
break;
|
||||
default:
|
||||
internal_errorf(0,
|
||||
"wdscan: unknown char 0x%x (carrying on)",
|
||||
wp[-1]);
|
||||
}
|
||||
}
|
||||
|
||||
/* return a copy of wp without any of the mark up characters and
|
||||
* with quote characters (" ' \) stripped.
|
||||
* (string is allocated from ATEMP)
|
||||
*/
|
||||
char *
|
||||
wdstrip(const char *wp)
|
||||
{
|
||||
struct shf shf;
|
||||
int c;
|
||||
|
||||
shf_sopen(NULL, 32, SHF_WR | SHF_DYNAMIC, &shf);
|
||||
|
||||
/* problems:
|
||||
* `...` -> $(...)
|
||||
* x${foo:-"hi"} -> x${foo:-hi}
|
||||
* x${foo:-'hi'} -> x${foo:-hi}
|
||||
*/
|
||||
while (1)
|
||||
switch ((c = *wp++)) {
|
||||
case EOS:
|
||||
return shf_sclose(&shf); /* null terminates */
|
||||
case CHAR:
|
||||
case QCHAR:
|
||||
shf_putchar(*wp++, &shf);
|
||||
break;
|
||||
case COMSUB:
|
||||
shf_putchar('$', &shf);
|
||||
shf_putchar('(', &shf);
|
||||
while (*wp != 0)
|
||||
shf_putchar(*wp++, &shf);
|
||||
shf_putchar(')', &shf);
|
||||
break;
|
||||
case EXPRSUB:
|
||||
shf_putchar('$', &shf);
|
||||
shf_putchar('(', &shf);
|
||||
shf_putchar('(', &shf);
|
||||
while (*wp != 0)
|
||||
shf_putchar(*wp++, &shf);
|
||||
shf_putchar(')', &shf);
|
||||
shf_putchar(')', &shf);
|
||||
break;
|
||||
case OQUOTE:
|
||||
break;
|
||||
case CQUOTE:
|
||||
break;
|
||||
case OSUBST:
|
||||
shf_putchar('$', &shf);
|
||||
if (*wp++ == '{')
|
||||
shf_putchar('{', &shf);
|
||||
while ((c = *wp++) != 0)
|
||||
shf_putchar(c, &shf);
|
||||
break;
|
||||
case CSUBST:
|
||||
if (*wp++ == '}')
|
||||
shf_putchar('}', &shf);
|
||||
break;
|
||||
case OPAT:
|
||||
shf_putchar(*wp++, &shf);
|
||||
shf_putchar('(', &shf);
|
||||
break;
|
||||
case SPAT:
|
||||
shf_putchar('|', &shf);
|
||||
break;
|
||||
case CPAT:
|
||||
shf_putchar(')', &shf);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static struct ioword **
|
||||
iocopy(struct ioword **iow, Area *ap)
|
||||
{
|
||||
struct ioword **ior;
|
||||
int i;
|
||||
|
||||
for (ior = iow; *ior++ != NULL; )
|
||||
;
|
||||
ior = (struct ioword **) alloc((ior - iow + 1) * sizeof(*ior), ap);
|
||||
|
||||
for (i = 0; iow[i] != NULL; i++) {
|
||||
struct ioword *p, *q;
|
||||
|
||||
p = iow[i];
|
||||
q = (struct ioword *) alloc(sizeof(*p), ap);
|
||||
ior[i] = q;
|
||||
*q = *p;
|
||||
if (p->name != NULL)
|
||||
q->name = wdcopy(p->name, ap);
|
||||
if (p->delim != NULL)
|
||||
q->delim = wdcopy(p->delim, ap);
|
||||
if (p->heredoc != NULL)
|
||||
q->heredoc = str_save(p->heredoc, ap);
|
||||
}
|
||||
ior[i] = NULL;
|
||||
|
||||
return ior;
|
||||
}
|
||||
|
||||
/*
|
||||
* free tree (for function definition)
|
||||
*/
|
||||
|
||||
void
|
||||
tfree(struct op *t, Area *ap)
|
||||
{
|
||||
char **w;
|
||||
|
||||
if (t == NULL)
|
||||
return;
|
||||
|
||||
if (t->str != NULL)
|
||||
afree((void*)t->str, ap);
|
||||
|
||||
if (t->vars != NULL) {
|
||||
for (w = t->vars; *w != NULL; w++)
|
||||
afree((void*)*w, ap);
|
||||
afree((void*)t->vars, ap);
|
||||
}
|
||||
|
||||
if (t->args != NULL) {
|
||||
for (w = t->args; *w != NULL; w++)
|
||||
afree((void*)*w, ap);
|
||||
afree((void*)t->args, ap);
|
||||
}
|
||||
|
||||
if (t->ioact != NULL)
|
||||
iofree(t->ioact, ap);
|
||||
|
||||
tfree(t->left, ap);
|
||||
tfree(t->right, ap);
|
||||
|
||||
afree((void*)t, ap);
|
||||
}
|
||||
|
||||
static void
|
||||
iofree(struct ioword **iow, Area *ap)
|
||||
{
|
||||
struct ioword **iop;
|
||||
struct ioword *p;
|
||||
|
||||
for (iop = iow; (p = *iop++) != NULL; ) {
|
||||
if (p->name != NULL)
|
||||
afree((void*)p->name, ap);
|
||||
if (p->delim != NULL)
|
||||
afree((void*)p->delim, ap);
|
||||
if (p->heredoc != NULL)
|
||||
afree((void*)p->heredoc, ap);
|
||||
afree((void*)p, ap);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user